Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Val <102326092+vdeva@users.noreply.github.com>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
Guillaume LE GOFF 2026-06-12 13:16:04 +02:00 committed by GitHub
parent 702d0f412e
commit cafb6d4147
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
247 changed files with 12401 additions and 3029 deletions

View file

@ -74,34 +74,17 @@ jobs:
- name: Install Python (Linux) - name: Install Python (Linux)
if: ${{ matrix.os == 'linux' }} if: ${{ matrix.os == 'linux' }}
run: | shell: bash
uv python install 3.12 run: bash ./scripts/ci/setup-linux-pyinstaller-build.sh
# Install patchelf >= 0.18 (yum/EPEL8 ships 0.12 which lacks --clear-execstack)
PATCHELF_VERSION=0.18.0
curl -sL "https://github.com/NixOS/patchelf/releases/download/${PATCHELF_VERSION}/patchelf-${PATCHELF_VERSION}-$(uname -m).tar.gz" \
| tar xz -C /usr/local
# python-build-standalone ships libpython with GNU_STACK RWE (executable stack)
# which is rejected by hardened Linux kernels — clear it with patchelf
find "$(uv python dir)" -name 'libpython*.so*' -exec patchelf --clear-execstack {} \;
- name: Sync dependencies - name: Build PyInstaller binaries
run: uv sync --no-dev --group build shell: bash
run: bash ./scripts/ci/build-pyinstaller-binaries.sh vibe-acp.spec vibe.spec
- name: Build ACP with PyInstaller
run: uv run --no-dev --group build pyinstaller vibe-acp.spec
- name: Build CLI with PyInstaller
run: uv run --no-dev --group build pyinstaller vibe.spec
- name: Clear executable stack on bundled libraries - name: Clear executable stack on bundled libraries
if: ${{ matrix.os == 'linux' }} if: ${{ matrix.os == 'linux' }}
run: | shell: bash
find dist/vibe-acp-dir/_internal -name '*.so*' -type f -print0 \ run: bash ./scripts/ci/clear-linux-execstack.sh dist/vibe-acp-dir dist/vibe-dir
| xargs -0 -I{} patchelf --clear-execstack {}
patchelf --clear-execstack dist/vibe-acp-dir/vibe-acp || true
find dist/vibe-dir/_internal -name '*.so*' -type f -print0 \
| xargs -0 -I{} patchelf --clear-execstack {}
patchelf --clear-execstack dist/vibe-dir/vibe || true
- name: Get package version - name: Get package version
id: get_version id: get_version
@ -193,6 +176,12 @@ jobs:
- name: Run ACP smoke tests - name: Run ACP smoke tests
run: ${{ matrix.container && 'python3.11' || 'python' }} tests/acp/smoke_binary.py dist/vibe-acp-dir run: ${{ matrix.container && 'python3.11' || 'python' }} tests/acp/smoke_binary.py dist/vibe-acp-dir
- name: Run CLI smoke tests
shell: bash
env:
PYTHON_BIN: ${{ matrix.container && 'python3.11' || 'python' }}
run: bash ./scripts/ci/smoke-pyinstaller-cli.sh dist/vibe-dir
attach-to-release: attach-to-release:
needs: [build-and-upload, smoke-test] needs: [build-and-upload, smoke-test]
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -94,6 +94,7 @@ Always go through `uv` — never invoke bare `python` or `pip`.
- Never use `git commit --amend`, `git push --force`, or `git push --force-with-lease`. - Never use `git commit --amend`, `git push --force`, or `git push --force-with-lease`.
- Always create new commits and push with a plain `git push`. - Always create new commits and push with a plain `git push`.
- If a push is rejected due to upstream changes, rebase onto the updated remote branch — never merge and never force-push. - If a push is rejected due to upstream changes, rebase onto the updated remote branch — never merge and never force-push.
- Run git commands through `uv run` (e.g. `uv run git commit`, `uv run git push`) so pre-commit hooks resolve the project's venv — bare `git commit` fails pre-commit with `reportMissingImports` because pyright can't find third-party packages.
## Editor tip ## Editor tip

View file

@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.15.0] - 2026-06-12
### Added
- **[Experimental]** `before_tool` and `after_tool` hooks: shell scripts declared in `hooks.toml` that fire around every tool call; hooks can deny the call, rewrite tool inputs, or append context to the output — enable with `enable_experimental_hooks = true`
- Message queue: messages typed while the agent or a `!bash` command is running are queued and shown above the input; Esc pauses the queue, Ctrl+C drops the last queued message (LIFO), Enter flushes the queue when paused
- Tool result output is now collapsed by default to keep responses scannable
- Common read-only shell commands (`ls`, `cat`, `pwd`, etc.) are allowed without approval by default
- Session deletion available directly from the resume picker
- `[mcp_servers.auth]` block in config for per-server MCP authentication
- Collapsed web tool output now shows the URL and search query at a glance
- `max_turns` support exposed over ACP via `set_config_option`
### Changed
- Compaction now re-injects prior user messages so the agent retains the original task goals across context resets
- **[Breaking — experimental hooks]** `post_agent_turn` retries no longer use exit code `2`; hooks must now exit `0` and return `{"decision": "deny", "reason": "..."}` on stdout to trigger a retry — exit code `2` is treated as a failure
- Model refusal stop reason is now surfaced to the user instead of stopping silently
### Fixed
- CLI UI no longer goes blank during app switches
- User messages are preserved correctly across repeated compactions
- Non-UTF-8 input is handled gracefully across all CLI surfaces
- `--resume` now shows history when launched from a dangerous directory
- Startup no longer crashes in unowned folders or when a git ancestor is unreadable
- Plain-string answers from `web_search` are handled without crashing
- NaN SGR mouse reports no longer leak into the chat input
## [2.14.1] - 2026-06-08 ## [2.14.1] - 2026-06-08
### Added ### Added

View file

@ -136,9 +136,9 @@ Valid values are `default`, `plan`, `accept-edits`, `auto-approve`,
custom agent file in `~/.vibe/agents/` or the project's `.vibe/agents/` custom agent file in `~/.vibe/agents/` or the project's `.vibe/agents/`
directory. Subagents such as `explore` are not accepted. directory. Subagents such as `explore` are not accepted.
> Note: `default_agent` only applies to interactive sessions. In > Note: `default_agent` applies in both interactive and programmatic
> programmatic mode (`-p` / `--prompt`), Vibe falls back to `auto-approve` > (`-p` / `--prompt`) sessions. Pass `--auto-approve` when a run should
> when `--agent` is not provided, so `default_agent` is ignored. > approve all tool calls without prompting.
### Subagents and Task Delegation ### Subagents and Task Delegation
@ -237,6 +237,7 @@ Simply run `vibe` to enter the interactive chat loop.
- **Todo View Toggle**: Press `Ctrl+T` to toggle the todo list view. - **Todo View Toggle**: Press `Ctrl+T` to toggle the todo list view.
- **Debug Console**: Press `Ctrl+\` to toggle the debug console. - **Debug Console**: Press `Ctrl+\` to toggle the debug console.
- **Agent Selection**: Press `Shift+Tab` to cycle through agents (default, plan, ...). - **Agent Selection**: Press `Shift+Tab` to cycle through agents (default, plan, ...).
- **Exit**: Type `/exit`, `exit`, `quit`, `:q`, or `:quit` in the input box, or press `Ctrl+C` / `Ctrl+D` twice within ~1 second.
You can start Vibe with a prompt using the following command: You can start Vibe with a prompt using the following command:
@ -260,7 +261,13 @@ You can run Vibe non-interactively by piping input or using the `--prompt` flag.
vibe --prompt "Refactor the main function in cli/main.py to be more modular." vibe --prompt "Refactor the main function in cli/main.py to be more modular."
``` ```
By default, it uses `auto-approve` mode. By default, it uses your configured `default_agent` (`default` unless changed).
To approve all tool calls without prompting, pass `--auto-approve` (also
available for interactive sessions):
```bash
vibe --prompt "Refactor the main function in cli/main.py to be more modular." --auto-approve
```
#### Programmatic Mode Options #### Programmatic Mode Options
@ -269,6 +276,8 @@ When using `--prompt`, you can specify additional options:
- **`--max-turns N`**: Limit the maximum number of assistant turns. The session will stop after N turns. - **`--max-turns N`**: Limit the maximum number of assistant turns. The session will stop after N turns.
- **`--max-price DOLLARS`**: Set a maximum cost limit in dollars. The session will be interrupted if the cost exceeds this limit. - **`--max-price DOLLARS`**: Set a maximum cost limit in dollars. The session will be interrupted if the cost exceeds this limit.
- **`--max-tokens N`**: Set a maximum cumulative LLM token budget for the session, counting both prompt and completion tokens. The session will be interrupted if usage exceeds this limit. - **`--max-tokens N`**: Set a maximum cumulative LLM token budget for the session, counting both prompt and completion tokens. The session will be interrupted if usage exceeds this limit.
- **`--agent NAME`**: Select the agent profile for this run.
- **`--auto-approve`**: Shortcut for `--agent auto-approve`. Approves all tool calls without prompting, including in interactive sessions.
- **`--enabled-tools TOOL`**: Enable specific tools. In programmatic mode, this disables all other tools. Can be specified multiple times. Supports exact names, glob patterns (e.g., `bash*`), or regex with `re:` prefix (e.g., `re:^serena_.*$`). - **`--enabled-tools TOOL`**: Enable specific tools. In programmatic mode, this disables all other tools. Can be specified multiple times. Supports exact names, glob patterns (e.g., `bash*`), or regex with `re:` prefix (e.g., `re:^serena_.*$`).
- **`--output FORMAT`**: Set the output format. Options: - **`--output FORMAT`**: Set the output format. Options:
- `text` (default): Human-readable text output - `text` (default): Human-readable text output
@ -605,6 +614,76 @@ startup_timeout_sec = 15
tool_timeout_sec = 120 tool_timeout_sec = 120
``` ```
### Hooks (Experimental)
Hooks wire arbitrary shell commands into Vibe's lifecycle to gate, audit, or rewrite agent behavior. **Experimental**, gated behind:
```toml
# config.toml
enable_experimental_hooks = true # or env VIBE_ENABLE_EXPERIMENTAL_HOOKS=true
```
Declared in `<project>/.vibe/hooks.toml` (project, loaded first; trusted only) and `~/.vibe/hooks.toml` (user-global, loaded second; duplicates by `name` lose to the project entry):
```toml
[[hooks]]
name = "deny-rm-rf"
type = "before_tool"
match = "bash" # tool-name matcher (fnmatch glob + `re:` regex escape, case-insensitive)
command = "uv run python /path/to/guard-bash"
timeout = 60.0 # seconds; default 60 for all hooks
strict = false # tool hooks only: turn failures into denials (before) / text-clears (after)
description = "Reject dangerous shell commands."
```
Subagents inherit the parent's hook config so policies apply transitively.
#### Common ground
Every hook is spawned with a JSON invocation on **stdin** (UTF-8) containing the session context: `session_id`, `parent_session_id`, `transcript_path`, `cwd`, plus `hook_event_name` discriminating the hook type. Tool hooks add tool-specific fields (below).
Every hook signals back via its **exit code** and **stdout**. The contract on stdout is strict: either empty (do nothing), or a JSON object matching the schema below. Use **stderr** for diagnostics / debug logs.
- **Exit `0`, empty stdout** — passthrough.
- **Exit `0`, valid JSON object on stdout** — structured response. Universal top-level fields:
- `system_message` (string, optional) — shown to the user in the UI.
- `decision` (`"allow"` | `"deny"`, optional, default `"allow"`) — the effect of `"deny"` depends on the hook type.
- `reason` (string, optional) — accompanies `decision: "deny"`.
- Event-specific payload under `hook_specific_output`.
- **Exit `0`, non-empty but non-conforming stdout** (free-form text, broken JSON, JSON scalar/array, schema mismatch) — treated as a hook failure with the parse error as the message. Warning by default; escalated to deny / clear under `strict = true` on a tool hook.
- **Any non-zero exit / timeout / spawn failure** — same failure path. Diagnostic taken from stderr (falling back to stdout, then the exit code).
Unknown JSON fields are tolerated at every level (forward-compatible). Fields that aren't meaningful for the current hook type are silently ignored.
#### `post_agent_turn`
Fires after every assistant turn that ends without pending tool calls.
- **Receives** (in addition to the session context): no extra fields.
- **Can return**:
- `decision: "deny"` + `reason``reason` is injected as a new user message asking for a retry. Capped at **3 retries per hook per user turn**; further denies become terminal warnings.
- `system_message` — UI-only.
#### `before_tool`
Fires per tool call, **before** the user permission prompt. First deny short-circuits remaining `before_tool` hooks for that call.
- **Receives** (in addition to the session context): `tool_name`, `tool_call_id`, `tool_input` (the model's raw arguments).
- **Can return**:
- `decision: "deny"` + `reason` — denies the tool call; `reason` becomes the tool error the LLM sees.
- `hook_specific_output.tool_input` (object) — **full replacement** of the model's arguments. Re-validated against the tool's schema (validation failure → synthesized denial). Rewrites compose left-to-right across hooks. The rewritten arguments are also what the permission prompt displays, what the tool runs with, and what subsequent LLM turns see on the assistant message.
- `system_message` — UI-only.
#### `after_tool`
Fires per tool call **if and only if the tool body actually ran**. `tool_status` is `success`, `failure`, or `cancelled` (cancellation during the tool body — cancellation is shielded so audit hooks still run). Does not fire when the tool never executed: `before_tool` denial, user denial at the approval prompt, permission `NEVER`, or cancellation before the body started.
- **Receives** (in addition to the session context): `tool_name`, `tool_call_id`, `tool_input` (post-rewrite), `tool_status`, `tool_output` (structured result dict; null on failure), `tool_output_text` (the running text the LLM will see, mutable by prior hooks), `tool_error`, `duration_ms`.
- **Can return**:
- `decision: "deny"` + `reason` — replaces `tool_output_text` with `reason`. Pipeline continues; subsequent hooks see the replacement.
- `hook_specific_output.additional_context` (string) — **appended** (with a `\n` separator) to `tool_output_text`. Composes with a same-hook deny: deny replaces first, then `additional_context` is appended to the replacement.
- `system_message` — UI-only.
### Session Management ### Session Management
#### Session Continuation and Resumption #### Session Continuation and Resumption
@ -612,12 +691,17 @@ tool_timeout_sec = 120
Vibe supports continuing from previous sessions: Vibe supports continuing from previous sessions:
- **`--continue`** or **`-c`**: Continue from the most recent saved session - **`--continue`** or **`-c`**: Continue from the most recent saved session
- **`--resume`**: Open an interactive session picker
- **`--resume SESSION_ID`**: Resume a specific session by ID (supports partial matching) - **`--resume SESSION_ID`**: Resume a specific session by ID (supports partial matching)
- **`/resume`** or **`/continue`**: Open the session picker from inside Vibe; press `D` twice to delete a local saved session. The active session cannot be deleted from this picker.
```bash ```bash
# Continue from last session # Continue from last session
vibe --continue vibe --continue
# Open session picker
vibe --resume
# Resume specific session # Resume specific session
vibe --resume abc123 vibe --resume abc123
``` ```

View file

@ -1,7 +1,7 @@
id = "mistral-vibe" id = "mistral-vibe"
name = "Mistral Vibe" name = "Mistral Vibe"
description = "Mistral's open-source coding assistant" description = "Mistral's open-source coding assistant"
version = "2.14.1" version = "2.15.0"
schema_version = 1 schema_version = 1
authors = ["Mistral AI"] authors = ["Mistral AI"]
repository = "https://github.com/mistralai/mistral-vibe" repository = "https://github.com/mistralai/mistral-vibe"
@ -11,21 +11,21 @@ name = "Mistral Vibe"
icon = "./icons/mistral_vibe.svg" icon = "./icons/mistral_vibe.svg"
[agent_servers.mistral-vibe.targets.darwin-aarch64] [agent_servers.mistral-vibe.targets.darwin-aarch64]
archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.1/vibe-acp-darwin-aarch64-2.14.1.zip" archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.15.0/vibe-acp-darwin-aarch64-2.15.0.zip"
cmd = "./vibe-acp" cmd = "./vibe-acp"
[agent_servers.mistral-vibe.targets.darwin-x86_64] [agent_servers.mistral-vibe.targets.darwin-x86_64]
archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.1/vibe-acp-darwin-x86_64-2.14.1.zip" archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.15.0/vibe-acp-darwin-x86_64-2.15.0.zip"
cmd = "./vibe-acp" cmd = "./vibe-acp"
[agent_servers.mistral-vibe.targets.linux-aarch64] [agent_servers.mistral-vibe.targets.linux-aarch64]
archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.1/vibe-acp-linux-aarch64-2.14.1.zip" archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.15.0/vibe-acp-linux-aarch64-2.15.0.zip"
cmd = "./vibe-acp" cmd = "./vibe-acp"
[agent_servers.mistral-vibe.targets.linux-x86_64] [agent_servers.mistral-vibe.targets.linux-x86_64]
archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.1/vibe-acp-linux-x86_64-2.14.1.zip" archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.15.0/vibe-acp-linux-x86_64-2.15.0.zip"
cmd = "./vibe-acp" cmd = "./vibe-acp"
[agent_servers.mistral-vibe.targets.windows-x86_64] [agent_servers.mistral-vibe.targets.windows-x86_64]
archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.14.1/vibe-acp-windows-x86_64-2.14.1.zip" archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.15.0/vibe-acp-windows-x86_64-2.15.0.zip"
cmd = "./vibe-acp.exe" cmd = "./vibe-acp.exe"

View file

@ -1,6 +1,6 @@
[project] [project]
name = "mistral-vibe" name = "mistral-vibe"
version = "2.14.1" version = "2.15.0"
description = "Minimal CLI coding agent by Mistral" description = "Minimal CLI coding agent by Mistral"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
@ -155,6 +155,7 @@ required-version = ">=0.8.0"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"cryptography>=44.0.0",
"debugpy>=1.8.19", "debugpy>=1.8.19",
"pre-commit>=4.2.0", "pre-commit>=4.2.0",
"pyinstrument>=5.1.2", "pyinstrument>=5.1.2",

View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <pyinstaller-spec> [<pyinstaller-spec> ...]" >&2
exit 2
fi
uv sync --no-dev --group build
for spec in "$@"; do
uv run --no-dev --group build pyinstaller "${spec}"
done

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <pyinstaller-bundle-dir> [<pyinstaller-bundle-dir> ...]" >&2
exit 2
fi
for bundle_dir in "$@"; do
internal_dir="${bundle_dir}/_internal"
binary_name="$(basename "${bundle_dir}")"
binary_name="${binary_name%-dir}"
binary_path="${bundle_dir}/${binary_name}"
find "${internal_dir}" -name '*.so*' -type f -print0 | while IFS= read -r -d '' library; do
patchelf --clear-execstack "${library}"
done
if [ -f "${binary_path}" ]; then
patchelf --clear-execstack "${binary_path}" || true
fi
done

View file

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
# Build in manylinux for glibc compatibility, then clear executable-stack
# flags rejected by hardened Linux kernels.
python_version="${PYTHON_VERSION:-3.12}"
patchelf_version="${PATCHELF_VERSION:-0.18.0}"
uv python install "${python_version}"
curl -sL "https://github.com/NixOS/patchelf/releases/download/${patchelf_version}/patchelf-${patchelf_version}-$(uname -m).tar.gz" \
| tar xz -C /usr/local
find "$(uv python dir)" -name 'libpython*.so*' -type f -exec patchelf --clear-execstack {} \;

View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
binary_dir="${1:-dist/vibe-dir}"
if [ -n "${PYTHON_BIN:-}" ]; then
read -r -a python_cmd <<< "${PYTHON_BIN}"
elif command -v uv >/dev/null 2>&1; then
python_cmd=(uv run python)
elif [ -x ".venv/bin/python" ]; then
python_cmd=(.venv/bin/python)
elif [ -x ".venv/Scripts/python.exe" ]; then
python_cmd=(.venv/Scripts/python.exe)
else
python_cmd=(python)
fi
"${python_cmd[@]}" tests/cli/smoke_binary.py "${binary_dir}"

View file

@ -127,7 +127,7 @@ class TestAcpHooksLoading:
assert result.hooks[0].name == "lint" assert result.hooks[0].name == "lint"
assert result.hooks[0].type == HookType.POST_AGENT_TURN assert result.hooks[0].type == HookType.POST_AGENT_TURN
assert result.hooks[0].command == "eslint ." assert result.hooks[0].command == "eslint ."
assert result.hooks[0].timeout == 30.0 assert result.hooks[0].timeout == 60.0
assert result.issues == [] assert result.issues == []
async def test_new_session_hooks_enabled_invalid_toml( async def test_new_session_hooks_enabled_invalid_toml(
@ -167,5 +167,5 @@ class TestAcpHooksLoading:
assert result.hooks[0].name == "lint" assert result.hooks[0].name == "lint"
assert result.hooks[0].type == HookType.POST_AGENT_TURN assert result.hooks[0].type == HookType.POST_AGENT_TURN
assert result.hooks[0].command == "eslint ." assert result.hooks[0].command == "eslint ."
assert result.hooks[0].timeout == 30.0 assert result.hooks[0].timeout == 60.0
assert result.issues == [] assert result.issues == []

View file

@ -67,7 +67,7 @@ class TestACPInitialize:
), ),
) )
assert response.agent_info == Implementation( assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.14.1" name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.15.0"
) )
assert response.auth_methods is not None assert response.auth_methods is not None
@ -101,7 +101,7 @@ class TestACPInitialize:
), ),
) )
assert response.agent_info == Implementation( assert response.agent_info == Implementation(
name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.14.1" name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.15.0"
) )
assert response.auth_methods is not None assert response.auth_methods is not None

View file

@ -151,6 +151,7 @@ class TestWebSearchFieldMeta:
def test_result_locations_are_source_urls_with_titles(self) -> None: def test_result_locations_are_source_urls_with_titles(self) -> None:
result = WebSearchResult( result = WebSearchResult(
query="python",
answer="found it", answer="found it",
sources=[ sources=[
WebSearchSource(title="Docs", url="https://docs.python.org"), WebSearchSource(title="Docs", url="https://docs.python.org"),

View file

@ -498,6 +498,83 @@ class TestAdapterParseResponse:
assert chunk.message.content == "Hello!" assert chunk.message.content == "Hello!"
assert chunk.usage.prompt_tokens == 10 assert chunk.usage.prompt_tokens == 10
def test_non_streaming_captures_refusal_stop_reason(self, adapter, provider):
data = {
"content": [],
"usage": {"input_tokens": 10, "output_tokens": 2},
"stop_reason": "refusal",
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.reason == "refusal"
def test_streaming_message_delta_captures_refusal_stop_reason(
self, adapter, provider
):
data = {
"type": "message_delta",
"delta": {"stop_reason": "refusal", "stop_sequence": None},
"usage": {"output_tokens": 7},
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.reason == "refusal"
assert chunk.usage.completion_tokens == 7
def test_streaming_message_delta_end_turn_stop_reason(self, adapter, provider):
data = {
"type": "message_delta",
"delta": {"stop_reason": "end_turn", "stop_sequence": None},
"usage": {"output_tokens": 3},
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.reason == "end_turn"
def test_non_streaming_captures_refusal_stop_details(self, adapter, provider):
data = {
"content": [],
"usage": {"input_tokens": 10, "output_tokens": 2},
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request was declined.",
},
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.category == "cyber"
assert chunk.stop.explanation == "This request was declined."
def test_non_streaming_without_stop_details_is_none(self, adapter, provider):
data = {
"content": [],
"usage": {"input_tokens": 10, "output_tokens": 2},
"stop_reason": "end_turn",
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.category is None
assert chunk.stop.explanation is None
def test_streaming_message_delta_captures_refusal_stop_details(
self, adapter, provider
):
data = {
"type": "message_delta",
"delta": {
"stop_reason": "refusal",
"stop_sequence": None,
"stop_details": {"type": "refusal", "category": "bio"},
},
"usage": {"output_tokens": 7},
}
chunk = adapter.parse_response(data, provider)
assert chunk.stop is not None
assert chunk.stop.category == "bio"
assert chunk.stop.explanation is None
def test_streaming_text_delta(self, adapter, provider): def test_streaming_text_delta(self, adapter, provider):
data = { data = {
"type": "content_block_delta", "type": "content_block_delta",

View file

@ -228,3 +228,67 @@ class TestBannerConnectorsCount:
assert banner._initial_state.connectors_connected == 3 assert banner._initial_state.connectors_connected == 3
assert banner._initial_state.connectors_total == 5 assert banner._initial_state.connectors_total == 5
class TestBannerHooksCount:
def test_hooks_count_passed_through(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(
config=_make_mock_config(), skill_manager=skill_manager, hooks_count=4
)
assert banner._initial_state.hooks_count == 4
def test_hooks_count_defaults_to_zero(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(config=_make_mock_config(), skill_manager=skill_manager)
assert banner._initial_state.hooks_count == 0
def test_format_meta_counts_shows_hooks_when_present(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(config=_make_mock_config(), skill_manager=skill_manager)
banner.state = BannerState(models_count=1, skills_count=0, hooks_count=3)
result = banner._format_meta_counts()
assert "3 hooks" in result
def test_format_meta_counts_singular_hook(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(config=_make_mock_config(), skill_manager=skill_manager)
banner.state = BannerState(models_count=1, skills_count=0, hooks_count=1)
result = banner._format_meta_counts()
assert "1 hook" in result
assert "1 hooks" not in result
def test_format_meta_counts_hides_hooks_when_zero(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(config=_make_mock_config(), skill_manager=skill_manager)
banner.state = BannerState(models_count=1, skills_count=0, hooks_count=0)
result = banner._format_meta_counts()
assert "hook" not in result
def test_set_state_updates_hooks_count(self) -> None:
skill_manager = Mock(spec=SkillManager)
skill_manager.custom_skills_count = 0
banner = Banner(
config=_make_mock_config(), skill_manager=skill_manager, hooks_count=0
)
banner.set_state(
config=_make_mock_config(), skill_manager=skill_manager, hooks_count=7
)
assert banner.state.hooks_count == 7

View file

@ -268,8 +268,16 @@ def test_installed_bundle_launches(binary_dir: Path, binary_name: str) -> None:
env = _isolated_env(vibe_home) env = _isolated_env(vibe_home)
env["PATH"] = f"{install_dir}{os.pathsep}{env.get('PATH', '')}" env["PATH"] = f"{install_dir}{os.pathsep}{env.get('PATH', '')}"
command = binary_name
if platform.system() == "Windows":
# subprocess on Windows does not resolve executables through a PATH
# value supplied only via env, so resolve it against that PATH first.
if (resolved := shutil.which(binary_name, path=env["PATH"])) is None:
_fail(f"installed binary not found on PATH: {binary_name}")
command = resolved
result = subprocess.run( result = subprocess.run(
[binary_name, "--version"], [command, "--version"],
capture_output=True, capture_output=True,
cwd=workdir, cwd=workdir,
env=env, env=env,

View file

@ -101,6 +101,7 @@ class TestCommandRegistry:
assert result is not None assert result is not None
_, cmd, _ = result _, cmd, _ = result
assert cmd.handler == "_show_session_picker" assert cmd.handler == "_show_session_picker"
assert cmd.description == "Browse, resume, or delete saved sessions"
def test_rename_command_registration(self) -> None: def test_rename_command_registration(self) -> None:
registry = CommandRegistry() registry = CommandRegistry()
@ -143,3 +144,47 @@ class TestCommandRegistry:
assert cmd_name == "loop" assert cmd_name == "loop"
assert cmd.handler == "_loop_command" assert cmd.handler == "_loop_command"
assert cmd_args == "30s ping" assert cmd_args == "30s ping"
def test_exit_command_accepts_bare_synonyms(self) -> None:
registry = CommandRegistry()
for alias in ["/exit", "exit", "quit", ":q", ":quit"]:
assert registry.get_command_name(alias) == "exit", alias
result = registry.parse_command(alias)
assert result is not None, alias
cmd_name, cmd, _ = result
assert cmd_name == "exit"
assert cmd.handler == "_exit_app"
assert cmd.exits is True
def test_bare_exit_synonym_with_trailing_text_is_not_a_command(self) -> None:
registry = CommandRegistry()
assert registry.parse_command("exit the function early") is None
assert registry.parse_command("quit your job") is None
def test_bare_exit_synonym_in_multiline_message_is_not_a_command(self) -> None:
registry = CommandRegistry()
assert registry.parse_command("exit\nplease refactor this module") is None
def test_slash_exit_still_parses_with_trailing_text(self) -> None:
registry = CommandRegistry()
result = registry.parse_command("/exit now")
assert result is not None
cmd_name, _, cmd_args = result
assert cmd_name == "exit"
assert cmd_args == "now"
def test_exit_command_synonyms_are_case_insensitive(self) -> None:
registry = CommandRegistry()
for alias in ["EXIT", "Quit", " exit ", ":Q"]:
assert registry.get_command_name(alias) == "exit", alias
def test_exit_synonyms_excluded_when_command_disabled(self) -> None:
registry = CommandRegistry(excluded_commands=["exit"])
for alias in ["/exit", "exit", "quit", ":q", ":quit"]:
assert registry.get_command_name(alias) is None, alias
def test_help_text_lists_exit_synonyms(self) -> None:
registry = CommandRegistry()
help_text = registry.get_help_text()
for alias in ["`/exit`", "`exit`", "`quit`", "`:q`", "`:quit`"]:
assert alias in help_text, alias

31
tests/cli/test_help.py Normal file
View file

@ -0,0 +1,31 @@
from __future__ import annotations
import pytest
from vibe.cli.entrypoint import parse_arguments
def test_help_shows_auto_approve_flag(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("sys.argv", ["vibe", "--help"])
with pytest.raises(SystemExit) as exc_info:
parse_arguments()
assert exc_info.value.code == 0
output = capsys.readouterr().out
assert "--auto-approve" in output
assert "Shortcut for --agent auto-approve" in output
def test_auto_approve_conflicts_with_agent(
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
monkeypatch.setattr("sys.argv", ["vibe", "--agent", "plan", "--auto-approve"])
with pytest.raises(SystemExit) as exc_info:
parse_arguments()
assert exc_info.value.code == 2
assert "not allowed with argument --agent" in capsys.readouterr().err

View file

@ -7,8 +7,10 @@ from vibe.core.agents.models import BuiltinAgentName
from vibe.core.config import VibeConfig from vibe.core.config import VibeConfig
def _make_args(*, agent: str | None, prompt: str | None) -> argparse.Namespace: def _make_args(
return argparse.Namespace(agent=agent, prompt=prompt) *, agent: str | None, prompt: str | None, auto_approve: bool = False
) -> argparse.Namespace:
return argparse.Namespace(agent=agent, prompt=prompt, auto_approve=auto_approve)
def test_uses_args_agent_when_provided() -> None: def test_uses_args_agent_when_provided() -> None:
@ -51,3 +53,10 @@ def test_programmatic_mode_keeps_explicit_agent_arg() -> None:
args = _make_args(agent="accept-edits", prompt="hello") args = _make_args(agent="accept-edits", prompt="hello")
assert get_initial_agent_name(args, config) == "accept-edits" assert get_initial_agent_name(args, config) == "accept-edits"
def test_auto_approve_flag_selects_auto_approve_agent() -> None:
config = VibeConfig.model_construct(default_agent=BuiltinAgentName.PLAN)
args = _make_args(agent=None, prompt="hello", auto_approve=True)
assert get_initial_agent_name(args, config) == BuiltinAgentName.AUTO_APPROVE

View file

@ -0,0 +1,230 @@
from __future__ import annotations
from pathlib import Path
import pytest
from tests.conftest import build_test_vibe_app, build_test_vibe_config
from vibe.cli.textual_ui.widgets.messages import ErrorMessage, UserCommandMessage
from vibe.cli.textual_ui.widgets.session_picker import SessionPickerApp
from vibe.core.config import SessionLoggingConfig
from vibe.core.session.resume_sessions import short_session_id
def _enabled_session_config(save_dir: Path) -> SessionLoggingConfig:
return SessionLoggingConfig(enabled=True, save_dir=str(save_dir))
class FakeSessionPicker:
def __init__(self, *, has_sessions_after_remove: bool = True) -> None:
self.has_sessions = True
self.removed_option_ids: list[str] = []
self.cleared_pending_option_ids: list[str] = []
self._has_sessions_after_remove = has_sessions_after_remove
def remove_session(self, option_id: str) -> bool:
self.removed_option_ids.append(option_id)
self.has_sessions = self._has_sessions_after_remove
return True
def clear_pending_delete(self, option_id: str) -> bool:
self.cleared_pending_option_ids.append(option_id)
return True
@pytest.mark.asyncio
async def test_session_delete_request_deletes_local_session(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
config = build_test_vibe_config(
session_logging=_enabled_session_config(tmp_path), enable_connectors=False
)
app = build_test_vibe_app(config=config)
picker = FakeSessionPicker()
mounted_widgets: list[object] = []
deleted_sessions: list[tuple[str, SessionLoggingConfig]] = []
async def delete_session(
session_id: str, session_config: SessionLoggingConfig
) -> None:
deleted_sessions.append((session_id, session_config))
async def mount_and_scroll(widget: object, after: object | None = None) -> None:
mounted_widgets.append(widget)
monkeypatch.setattr("vibe.cli.textual_ui.app.delete_saved_session", delete_session)
monkeypatch.setattr(app, "query_one", lambda _selector: picker)
monkeypatch.setattr(app, "_mount_and_scroll", mount_and_scroll)
await app.on_session_picker_app_session_delete_requested(
SessionPickerApp.SessionDeleteRequested(
"local:deleted-session", "local", "deleted-session"
)
)
assert deleted_sessions == [("deleted-session", config.session_logging)]
assert picker.removed_option_ids == ["local:deleted-session"]
assert any(
isinstance(widget, UserCommandMessage)
and widget._content
== f"Deleted session `{short_session_id('deleted-session')}`."
for widget in mounted_widgets
)
@pytest.mark.asyncio
async def test_session_delete_request_keeps_picker_on_delete_error(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
config = build_test_vibe_config(
session_logging=_enabled_session_config(tmp_path), enable_connectors=False
)
app = build_test_vibe_app(config=config)
picker = FakeSessionPicker()
mounted_widgets: list[object] = []
async def delete_session(
session_id: str, session_config: SessionLoggingConfig
) -> None:
raise RuntimeError("disk said no")
async def mount_and_scroll(widget: object, after: object | None = None) -> None:
mounted_widgets.append(widget)
monkeypatch.setattr("vibe.cli.textual_ui.app.delete_saved_session", delete_session)
monkeypatch.setattr(app, "query_one", lambda _selector: picker)
monkeypatch.setattr(app, "_mount_and_scroll", mount_and_scroll)
await app.on_session_picker_app_session_delete_requested(
SessionPickerApp.SessionDeleteRequested(
"local:deleted-session", "local", "deleted-session"
)
)
assert picker.removed_option_ids == []
assert picker.cleared_pending_option_ids == ["local:deleted-session"]
assert any(
isinstance(widget, ErrorMessage)
and widget._error == "Failed to delete session: disk said no"
for widget in mounted_widgets
)
@pytest.mark.asyncio
async def test_session_delete_request_returns_to_input_when_picker_becomes_empty(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
config = build_test_vibe_config(
session_logging=_enabled_session_config(tmp_path), enable_connectors=False
)
app = build_test_vibe_app(config=config)
picker = FakeSessionPicker(has_sessions_after_remove=False)
mounted_widgets: list[object] = []
switched_to_input = False
async def delete_session(
session_id: str, session_config: SessionLoggingConfig
) -> None:
pass
async def mount_and_scroll(widget: object, after: object | None = None) -> None:
mounted_widgets.append(widget)
async def switch_to_input() -> None:
nonlocal switched_to_input
switched_to_input = True
monkeypatch.setattr("vibe.cli.textual_ui.app.delete_saved_session", delete_session)
monkeypatch.setattr(app, "query_one", lambda _selector: picker)
monkeypatch.setattr(app, "_mount_and_scroll", mount_and_scroll)
monkeypatch.setattr(app, "_switch_to_input_app", switch_to_input)
await app.on_session_picker_app_session_delete_requested(
SessionPickerApp.SessionDeleteRequested(
"local:deleted-session", "local", "deleted-session"
)
)
assert switched_to_input is True
assert picker.removed_option_ids == ["local:deleted-session"]
assert [
widget._content
for widget in mounted_widgets
if isinstance(widget, UserCommandMessage)
] == [
f"Deleted session `{short_session_id('deleted-session')}`.",
"No saved sessions left for this directory.",
]
@pytest.mark.asyncio
async def test_session_delete_request_rejects_remote_sessions(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
config = build_test_vibe_config(
session_logging=_enabled_session_config(tmp_path), enable_connectors=False
)
app = build_test_vibe_app(config=config)
mounted_widgets: list[object] = []
async def delete_session(
session_id: str, session_config: SessionLoggingConfig
) -> None:
pytest.fail("remote sessions should not be deleted")
async def mount_and_scroll(widget: object, after: object | None = None) -> None:
mounted_widgets.append(widget)
monkeypatch.setattr("vibe.cli.textual_ui.app.delete_saved_session", delete_session)
monkeypatch.setattr(app, "_mount_and_scroll", mount_and_scroll)
await app.on_session_picker_app_session_delete_requested(
SessionPickerApp.SessionDeleteRequested(
"remote:remote-session", "remote", "remote-session"
)
)
assert any(
isinstance(widget, ErrorMessage)
and widget._error == "Deleting remote sessions is not supported."
for widget in mounted_widgets
)
@pytest.mark.asyncio
async def test_session_delete_request_rejects_current_session(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
config = build_test_vibe_config(
session_logging=_enabled_session_config(tmp_path), enable_connectors=False
)
app = build_test_vibe_app(config=config)
picker = FakeSessionPicker()
mounted_widgets: list[object] = []
deleted_sessions: list[str] = []
async def delete_session(
session_id: str, session_config: SessionLoggingConfig
) -> None:
deleted_sessions.append(session_id)
async def mount_and_scroll(widget: object, after: object | None = None) -> None:
mounted_widgets.append(widget)
monkeypatch.setattr("vibe.cli.textual_ui.app.delete_saved_session", delete_session)
monkeypatch.setattr(app, "query_one", lambda _selector: picker)
monkeypatch.setattr(app, "_mount_and_scroll", mount_and_scroll)
await app.on_session_picker_app_session_delete_requested(
SessionPickerApp.SessionDeleteRequested(
f"local:{app.agent_loop.session_id}", "local", app.agent_loop.session_id
)
)
assert deleted_sessions == []
assert picker.cleared_pending_option_ids == [f"local:{app.agent_loop.session_id}"]
assert any(
isinstance(widget, ErrorMessage)
and widget._error == "Deleting the current session is not supported."
for widget in mounted_widgets
)

View file

@ -145,6 +145,35 @@ async def test_ui_displays_multiple_user_assistant_turns(
assert assistant_messages[1]._content == "Second answer" assert assistant_messages[1]._content == "Second answer"
@pytest.mark.asyncio
async def test_ui_displays_messages_when_resuming_in_dangerous_directory(
monkeypatch: pytest.MonkeyPatch, vibe_config: VibeConfig
) -> None:
monkeypatch.setattr(
"vibe.cli.textual_ui.app.is_dangerous_directory",
lambda: (True, "You are in the home directory"),
)
agent_loop = build_test_agent_loop(config=vibe_config)
agent_loop.messages.extend([
LLMMessage(role=Role.user, content="Hello from a previous run"),
LLMMessage(role=Role.assistant, content="Welcome back!"),
])
app = build_test_vibe_app(agent_loop=agent_loop)
async with app.run_test() as pilot:
await pilot.pause(0.5)
user_messages = app.query(UserMessage)
assistant_messages = app.query(AssistantMessage)
assert len(user_messages) == 1
assert user_messages[0]._content == "Hello from a previous run"
assert len(assistant_messages) == 1
assert assistant_messages[0]._content == "Welcome back!"
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_ui_rebuilds_history_when_whats_new_is_shown( async def test_ui_rebuilds_history_when_whats_new_is_shown(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path monkeypatch: pytest.MonkeyPatch, tmp_path: Path

View file

@ -129,3 +129,30 @@ async def test_skill_without_args_does_not_prepend_invocation_line(
vibe_app_with_skills, pilot, "Do the thing." vibe_app_with_skills, pilot, "Do the thing."
) )
assert "/my-skill" not in message._content assert "/my-skill" not in message._content
@pytest.mark.asyncio
async def test_popped_queued_skill_does_not_fire_telemetry(
vibe_app_with_skills: VibeApp, monkeypatch: pytest.MonkeyPatch
) -> None:
async with vibe_app_with_skills.run_test() as pilot:
events: list[tuple[str, str]] = []
monkeypatch.setattr(
vibe_app_with_skills.agent_loop.telemetry_client,
"send_slash_command_used",
lambda name, kind: events.append((name, kind)),
)
chat_input = vibe_app_with_skills.query_one(ChatInputContainer)
vibe_app_with_skills._agent_running = True
try:
chat_input.post_message(ChatInputContainer.Submitted("/my-skill"))
await pilot.pause(0.1)
assert len(vibe_app_with_skills._input_queue) == 1
await pilot.press("ctrl+c")
await pilot.pause(0.1)
assert len(vibe_app_with_skills._input_queue) == 0
assert events == []
finally:
vibe_app_with_skills._agent_running = False

View file

@ -4,38 +4,55 @@ from unittest.mock import AsyncMock
import pytest import pytest
from tests.stubs.fake_tool import FakeTool, FakeToolArgs
import vibe.cli.textual_ui.handlers.event_handler as event_handler_module import vibe.cli.textual_ui.handlers.event_handler as event_handler_module
from vibe.cli.textual_ui.handlers.event_handler import EventHandler from vibe.cli.textual_ui.handlers.event_handler import EventHandler
from vibe.cli.textual_ui.widgets.messages import HookSystemMessageLine
from vibe.core.hooks.models import ( from vibe.core.hooks.models import (
HookEndEvent, HookEndEvent,
HookMessageSeverity, HookMessageSeverity,
HookRunEndEvent, HookRunEndEvent,
HookRunStartEvent, HookRunStartEvent,
HookStartEvent,
HookType,
) )
from vibe.core.types import ToolCallEvent, ToolResultEvent
class FakeHookRunContainer: class FakeHookRunContainer:
def __init__(self) -> None: def __init__(self) -> None:
self.display = False self.display = False
self.remove = AsyncMock() self.remove = AsyncMock()
self.messages: list[HookSystemMessageLine] = []
self.classes: set[str] = set()
async def add_message(self, _widget: object) -> None: def add_class(self, cls: str) -> None:
self.classes.add(cls)
async def add_message(self, widget: HookSystemMessageLine) -> None:
self.display = True self.display = True
self.messages.append(widget)
@pytest.fixture
def hook_container_factory(
monkeypatch: pytest.MonkeyPatch,
) -> list[FakeHookRunContainer]:
created: list[FakeHookRunContainer] = []
def make_container() -> FakeHookRunContainer:
container = FakeHookRunContainer()
created.append(container)
return container
monkeypatch.setattr(event_handler_module, "HookRunContainer", make_container)
return created
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_hook_run_end_removes_empty_container( async def test_hook_run_end_removes_empty_container(
monkeypatch: pytest.MonkeyPatch, hook_container_factory: list[FakeHookRunContainer],
) -> None: ) -> None:
created_containers: list[FakeHookRunContainer] = []
def make_container() -> FakeHookRunContainer:
container = FakeHookRunContainer()
created_containers.append(container)
return container
monkeypatch.setattr(event_handler_module, "HookRunContainer", make_container)
mount_callback = AsyncMock() mount_callback = AsyncMock()
handler = EventHandler( handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False mount_callback=mount_callback, get_tools_collapsed=lambda: False
@ -44,30 +61,22 @@ async def test_hook_run_end_removes_empty_container(
await handler.handle_event(HookRunStartEvent()) await handler.handle_event(HookRunStartEvent())
await handler.handle_event(HookRunEndEvent()) await handler.handle_event(HookRunEndEvent())
assert len(created_containers) == 1 assert len(hook_container_factory) == 1
created_containers[0].remove.assert_awaited_once() hook_container_factory[0].remove.assert_awaited_once()
assert handler._hook_run_container is None assert "agent_turn" not in handler._hook_containers
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_hook_run_end_keeps_container_with_messages( async def test_hook_run_end_keeps_container_with_messages(
monkeypatch: pytest.MonkeyPatch, hook_container_factory: list[FakeHookRunContainer],
) -> None: ) -> None:
created_containers: list[FakeHookRunContainer] = []
def make_container() -> FakeHookRunContainer:
container = FakeHookRunContainer()
created_containers.append(container)
return container
monkeypatch.setattr(event_handler_module, "HookRunContainer", make_container)
mount_callback = AsyncMock() mount_callback = AsyncMock()
handler = EventHandler( handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False mount_callback=mount_callback, get_tools_collapsed=lambda: False
) )
await handler.handle_event(HookRunStartEvent()) await handler.handle_event(HookRunStartEvent())
await handler.handle_event(HookStartEvent(hook_name="post-turn"))
await handler.handle_event( await handler.handle_event(
HookEndEvent( HookEndEvent(
hook_name="post-turn", status=HookMessageSeverity.OK, content="Hook output" hook_name="post-turn", status=HookMessageSeverity.OK, content="Hook output"
@ -75,7 +84,279 @@ async def test_hook_run_end_keeps_container_with_messages(
) )
await handler.handle_event(HookRunEndEvent()) await handler.handle_event(HookRunEndEvent())
assert len(created_containers) == 1 assert len(hook_container_factory) == 1
created_containers[0].remove.assert_not_awaited() hook_container_factory[0].remove.assert_not_awaited()
assert created_containers[0].display is True assert hook_container_factory[0].display is True
assert handler._hook_run_container is None assert "agent_turn" not in handler._hook_containers
def _tool_call_event(call_id: str) -> ToolCallEvent:
return ToolCallEvent(
tool_name="stub_tool",
tool_class=FakeTool,
args=FakeToolArgs(),
tool_call_id=call_id,
)
def _tool_result_event(call_id: str) -> ToolResultEvent:
return ToolResultEvent(
tool_name="stub_tool",
tool_class=FakeTool,
result=None,
cancelled=False,
tool_call_id=call_id,
)
@pytest.mark.asyncio
async def test_before_tool_container_scoped_to_tool_call_id(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
"""Two concurrent tool calls each get their own before_tool container."""
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
# Two tool calls arrive
await handler.handle_event(_tool_call_event("call_A"))
await handler.handle_event(_tool_call_event("call_B"))
# before_tool starts for both, interleaved
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_A"
)
)
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_B"
)
)
# Two distinct containers were created.
assert len(hook_container_factory) == 2
assert "before_tool:call_A" in handler._hook_containers
assert "before_tool:call_B" in handler._hook_containers
assert (
handler._hook_containers["before_tool:call_A"]
is not handler._hook_containers["before_tool:call_B"]
)
# End them — both are empty, both should be removed.
await handler.handle_event(
HookRunEndEvent(scope=HookType.BEFORE_TOOL, tool_call_id="call_A")
)
await handler.handle_event(
HookRunEndEvent(scope=HookType.BEFORE_TOOL, tool_call_id="call_B")
)
assert "before_tool:call_A" not in handler._hook_containers
assert "before_tool:call_B" not in handler._hook_containers
@pytest.mark.asyncio
async def test_after_tool_container_anchors_after_tool_result(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(_tool_call_event("call_X"))
await handler.handle_event(_tool_result_event("call_X"))
# After-tool start mounts after the tool result, which is the current
# anchor for this tool_call_id.
await handler.handle_event(
HookRunStartEvent(
scope=HookType.AFTER_TOOL, tool_name="stub_tool", tool_call_id="call_X"
)
)
assert "after_tool:call_X" in handler._hook_containers
# mount_callback was called with after=<tool_result_widget> for the after_tool
# container. Inspect the last call's kwargs.
last_call = mount_callback.call_args_list[-1]
assert last_call.kwargs.get("after") is not None
@pytest.mark.asyncio
async def test_before_tool_container_for_unknown_tool_call_id(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="bash", tool_call_id="unknown"
)
)
last_call = mount_callback.call_args_list[-1]
assert last_call.kwargs.get("after") is None
assert last_call.kwargs.get("before") is None
@pytest.mark.asyncio
async def test_before_tool_container_mounts_before_tool_call_widget(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(_tool_call_event("call_Y"))
tool_call_widget = handler._tool_call_anchors["call_Y"]
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_Y"
)
)
last_call = mount_callback.call_args_list[-1]
assert last_call.kwargs.get("before") is tool_call_widget
assert last_call.kwargs.get("after") is None
@pytest.mark.asyncio
async def test_before_tool_run_end_keeps_tool_call_widget_as_anchor(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(_tool_call_event("call_Z"))
tool_call_widget = handler._tool_call_anchors["call_Z"]
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_Z"
)
)
await handler.handle_event(HookStartEvent(hook_name="before"))
await handler.handle_event(
HookEndEvent(hook_name="before", status=HookMessageSeverity.OK, content="ok")
)
await handler.handle_event(
HookRunEndEvent(scope=HookType.BEFORE_TOOL, tool_call_id="call_Z")
)
# Even though the before_tool container has content and stays in the DOM,
# the anchor for the next widget (the tool result) must remain the call
# widget — the container lives *above* the call, not below it.
assert handler._tool_call_anchors["call_Z"] is tool_call_widget
@pytest.mark.asyncio
async def test_hook_end_routes_to_matching_container_when_chains_interleave(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(_tool_call_event("call_A"))
await handler.handle_event(_tool_call_event("call_B"))
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_A"
)
)
assert len(hook_container_factory) == 1
container_a = hook_container_factory[-1]
await handler.handle_event(HookStartEvent(hook_name="hook_a"))
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_B"
)
)
assert len(hook_container_factory) == 2
container_b = hook_container_factory[-1]
assert container_a is not container_b
await handler.handle_event(
HookEndEvent(
hook_name="hook_a",
status=HookMessageSeverity.OK,
content="from A",
scope=HookType.BEFORE_TOOL,
tool_call_id="call_A",
)
)
await handler.handle_event(HookStartEvent(hook_name="hook_b"))
await handler.handle_event(
HookEndEvent(
hook_name="hook_b",
status=HookMessageSeverity.OK,
content="from B",
scope=HookType.BEFORE_TOOL,
tool_call_id="call_B",
)
)
assert len(container_a.messages) == 1
assert len(container_b.messages) == 1
assert container_a.messages[0]._content == "from A"
assert container_b.messages[0]._content == "from B"
@pytest.mark.asyncio
async def test_hook_end_after_other_chain_ended_still_routes_correctly(
hook_container_factory: list[FakeHookRunContainer],
) -> None:
mount_callback = AsyncMock()
handler = EventHandler(
mount_callback=mount_callback, get_tools_collapsed=lambda: False
)
await handler.handle_event(_tool_call_event("call_A"))
await handler.handle_event(_tool_call_event("call_B"))
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_A"
)
)
container_a = hook_container_factory[-1]
await handler.handle_event(HookStartEvent(hook_name="hook_a"))
await handler.handle_event(
HookRunStartEvent(
scope=HookType.BEFORE_TOOL, tool_name="stub_tool", tool_call_id="call_B"
)
)
await handler.handle_event(HookStartEvent(hook_name="hook_b"))
await handler.handle_event(
HookEndEvent(
hook_name="hook_b",
status=HookMessageSeverity.OK,
content="from B",
scope=HookType.BEFORE_TOOL,
tool_call_id="call_B",
)
)
await handler.handle_event(
HookRunEndEvent(scope=HookType.BEFORE_TOOL, tool_call_id="call_B")
)
await handler.handle_event(
HookEndEvent(
hook_name="hook_a",
status=HookMessageSeverity.OK,
content="from A",
scope=HookType.BEFORE_TOOL,
tool_call_id="call_A",
)
)
assert len(container_a.messages) == 1
assert container_a.messages[0]._content == "from A"

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import pytest import pytest
from vibe.cli.textual_ui.message_queue import MessageQueue, QueuedItemKind from vibe.cli.textual_ui.message_queue import MessageQueue, QueuedItem, QueuedItemKind
def test_empty_queue_is_falsy() -> None: def test_empty_queue_is_falsy() -> None:
@ -38,6 +38,17 @@ def test_pop_last_returns_newest() -> None:
assert [item.content for item in queue.items] == ["a", "b"] assert [item.content for item in queue.items] == ["a", "b"]
def test_pop_last_resumes_when_queue_becomes_empty() -> None:
queue = MessageQueue()
queue.append_prompt("a")
queue.pause()
queue.pop_last()
assert not queue
assert not queue.paused
def test_pop_first_returns_oldest() -> None: def test_pop_first_returns_oldest() -> None:
queue = MessageQueue() queue = MessageQueue()
queue.append_prompt("a") queue.append_prompt("a")
@ -74,11 +85,9 @@ def test_pause_and_resume() -> None:
def test_pause_is_idempotent() -> None: def test_pause_is_idempotent() -> None:
queue = MessageQueue() queue = MessageQueue()
calls = []
queue.set_change_listener(lambda: calls.append(None))
queue.pause() queue.pause()
queue.pause() queue.pause()
assert len(calls) == 1 assert queue.paused
def test_clear_resets_state() -> None: def test_clear_resets_state() -> None:
@ -90,34 +99,30 @@ def test_clear_resets_state() -> None:
assert not queue.paused assert not queue.paused
def test_change_listener_fires_on_mutations() -> None: def test_prepend_prompts_inserts_at_head_preserving_order() -> None:
queue = MessageQueue() queue = MessageQueue()
calls: list[None] = [] queue.append_prompt("x")
queue.set_change_listener(lambda: calls.append(None)) queue.append_prompt("y")
queue.prepend_prompts([
queue.append_prompt("a") QueuedItem(QueuedItemKind.PROMPT, "a"),
assert len(calls) == 1 QueuedItem(QueuedItemKind.PROMPT, "b"),
])
queue.append_bash("ls") assert [item.content for item in queue.items] == ["a", "b", "x", "y"]
assert len(calls) == 2
queue.pop_last()
assert len(calls) == 3
queue.pause()
assert len(calls) == 4
queue.resume()
assert len(calls) == 5
def test_change_listener_can_be_cleared() -> None: def test_prepend_prompts_empty_is_noop() -> None:
queue = MessageQueue() queue = MessageQueue()
calls: list[None] = [] queue.append_prompt("x")
queue.set_change_listener(lambda: calls.append(None)) queue.prepend_prompts([])
queue.set_change_listener(None) assert [item.content for item in queue.items] == ["x"]
queue.append_prompt("a")
assert calls == []
def test_append_prompt_with_skill_name() -> None:
queue = MessageQueue()
queue.append_prompt("expanded prompt", skill_name="my-skill")
item = queue.items[0]
assert item.skill_name == "my-skill"
assert item.content == "expanded prompt"
def test_items_returns_copy() -> None: def test_items_returns_copy() -> None:

View file

@ -0,0 +1,200 @@
from __future__ import annotations
import time
import pytest
from tests.conftest import build_test_vibe_app
from vibe.cli.textual_ui.app import VibeApp
from vibe.cli.textual_ui.widgets.chat_input.container import ChatInputContainer
from vibe.cli.textual_ui.widgets.messages import (
BashOutputMessage,
QueueHeaderMessage,
WarningMessage,
)
@pytest.fixture
def vibe_app() -> VibeApp:
return build_test_vibe_app()
async def _wait_until(pilot, predicate, timeout: float = 2.0) -> bool:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
if predicate():
return True
await pilot.pause(0.05)
return False
@pytest.mark.asyncio
async def test_no_queue_header_when_empty(vibe_app: VibeApp) -> None:
async with vibe_app.run_test():
headers = list(vibe_app.query(QueueHeaderMessage))
assert headers == []
@pytest.mark.asyncio
async def test_bash_submitted_during_running_bash_is_queued(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 0.3"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=1.0)
chat_input.value = "!echo queued"
await pilot.press("enter")
assert len(vibe_app._input_queue) == 1
assert vibe_app._input_queue.items[0].content == "echo queued"
headers = list(vibe_app.query(QueueHeaderMessage))
assert len(headers) == 1
queued_bashes = [w for w in vibe_app.query(BashOutputMessage) if w._queued]
assert len(queued_bashes) == 1
@pytest.mark.asyncio
async def test_slash_command_rejected_with_warning_when_busy(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 0.3"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=1.0)
chat_input.value = "/help"
await pilot.press("enter")
assert not list(vibe_app.query(WarningMessage))
assert any(
"Slash commands cannot be queued" in notification.message
for notification in vibe_app._notifications
)
assert len(vibe_app._input_queue) == 0
assert chat_input.value.startswith("/help")
@pytest.mark.asyncio
async def test_ctrl_c_pops_last_queued_item_lifo(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 2"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
chat_input.value = "!echo first"
await pilot.press("enter")
chat_input.value = "!echo second"
await pilot.press("enter")
assert len(vibe_app._input_queue) == 2
await pilot.press("ctrl+c")
assert len(vibe_app._input_queue) == 1
assert vibe_app._input_queue.items[0].content == "echo first"
await pilot.press("escape")
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=5.0)
@pytest.mark.asyncio
async def test_escape_pauses_queue_when_job_running(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 2"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
chat_input.value = "!echo queued"
await pilot.press("enter")
assert len(vibe_app._input_queue) == 1
await pilot.press("escape")
assert vibe_app._input_queue.paused
assert len(vibe_app._input_queue) == 1
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=5.0)
@pytest.mark.asyncio
async def test_drain_runs_queued_bashes_in_fifo_order(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 0.2"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=1.0)
chat_input.value = "!echo first"
await pilot.press("enter")
chat_input.value = "!echo second"
await pilot.press("enter")
await _wait_until(
pilot,
lambda: (
len(list(vibe_app.query(BashOutputMessage))) == 3
and all(not m._pending for m in vibe_app.query(BashOutputMessage))
),
timeout=5.0,
)
msgs = list(vibe_app.query(BashOutputMessage))
assert len(msgs) == 3
assert vibe_app._input_queue.paused is False
assert len(vibe_app._input_queue) == 0
@pytest.mark.asyncio
async def test_enter_on_empty_input_flushes_paused_queue(vibe_app: VibeApp) -> None:
async with vibe_app.run_test() as pilot:
chat_input = vibe_app.query_one(ChatInputContainer)
chat_input.value = "!sleep 2"
await pilot.press("enter")
await _wait_until(pilot, lambda: vibe_app._bash_task is not None, timeout=2.0)
chat_input.value = "!echo queued"
await pilot.press("enter")
assert len(vibe_app._input_queue) == 1
await pilot.press("escape")
assert vibe_app._input_queue.paused
await _wait_until(pilot, lambda: vibe_app._bash_task is None, timeout=10.0)
chat_input.value = ""
await pilot.press("enter")
await _wait_until(
pilot,
lambda: (
not vibe_app._input_queue.paused and len(vibe_app._input_queue) == 0
),
timeout=10.0,
)
assert not vibe_app._input_queue.paused
assert len(vibe_app._input_queue) == 0
@pytest.mark.asyncio
async def test_quit_warning_shows_queue_count(vibe_app: VibeApp) -> None:
async with vibe_app.run_test():
vibe_app._input_queue.append_prompt("a")
vibe_app._input_queue.append_prompt("b")
warning = vibe_app._queue.quit_warning_extra()
assert warning == "2 queued messages will be discarded"
vibe_app._input_queue.pop_last()
warning = vibe_app._queue.quit_warning_extra()
assert warning == "1 queued message will be discarded"
vibe_app._input_queue.pop_last()
assert vibe_app._queue.quit_warning_extra() == ""

View file

@ -92,11 +92,12 @@ class TestActionInterruptOrQuit:
mock_container.value = "" mock_container.value = ""
with ( with (
patch.object(app, "_get_chat_input", return_value=mock_container), patch.object(app, "_get_chat_input", return_value=mock_container),
patch.object(app, "_try_interrupt", return_value=False), patch.object(app, "_try_interrupt_no_job_steps", return_value=False),
patch.object(app, "_try_interrupt_running_job", return_value=False),
patch.object(app._quit_manager, "request_confirmation") as mock_confirm, patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
): ):
app.action_interrupt_or_quit() app.action_interrupt_or_quit()
mock_confirm.assert_called_once_with("Ctrl+C") mock_confirm.assert_called_once_with("Ctrl+C", "")
def test_quits_on_confirmed(self, app: VibeApp) -> None: def test_quits_on_confirmed(self, app: VibeApp) -> None:
app._quit_manager._confirm_time = time.monotonic() app._quit_manager._confirm_time = time.monotonic()
@ -111,7 +112,9 @@ class TestActionInterruptOrQuit:
def test_interrupts_before_requesting_confirmation(self, app: VibeApp) -> None: def test_interrupts_before_requesting_confirmation(self, app: VibeApp) -> None:
with ( with (
patch.object(app, "_get_chat_input", return_value=None), patch.object(app, "_get_chat_input", return_value=None),
patch.object(app, "_try_interrupt", return_value=True) as mock_interrupt, patch.object(
app, "_try_interrupt_no_job_steps", return_value=True
) as mock_interrupt,
patch.object(app._quit_manager, "request_confirmation") as mock_confirm, patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
): ):
app.action_interrupt_or_quit() app.action_interrupt_or_quit()
@ -123,11 +126,12 @@ class TestActionInterruptOrQuit:
) -> None: ) -> None:
with ( with (
patch.object(app, "_get_chat_input", return_value=None), patch.object(app, "_get_chat_input", return_value=None),
patch.object(app, "_try_interrupt", return_value=False), patch.object(app, "_try_interrupt_no_job_steps", return_value=False),
patch.object(app, "_try_interrupt_running_job", return_value=False),
patch.object(app._quit_manager, "request_confirmation") as mock_confirm, patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
): ):
app.action_interrupt_or_quit() app.action_interrupt_or_quit()
mock_confirm.assert_called_once_with("Ctrl+C") mock_confirm.assert_called_once_with("Ctrl+C", "")
class TestActionDeleteRightOrQuit: class TestActionDeleteRightOrQuit:
@ -148,7 +152,7 @@ class TestActionDeleteRightOrQuit:
patch.object(app._quit_manager, "request_confirmation") as mock_confirm, patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
): ):
app.action_delete_right_or_quit() app.action_delete_right_or_quit()
mock_confirm.assert_called_once_with("Ctrl+D") mock_confirm.assert_called_once_with("Ctrl+D", "")
def test_quits_on_confirmed(self, app: VibeApp) -> None: def test_quits_on_confirmed(self, app: VibeApp) -> None:
app._quit_manager._confirm_time = time.monotonic() app._quit_manager._confirm_time = time.monotonic()
@ -166,4 +170,15 @@ class TestActionDeleteRightOrQuit:
patch.object(app._quit_manager, "request_confirmation") as mock_confirm, patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
): ):
app.action_delete_right_or_quit() app.action_delete_right_or_quit()
mock_confirm.assert_called_once_with("Ctrl+D") mock_confirm.assert_called_once_with("Ctrl+D", "")
def test_shows_queue_warning_when_queue_non_empty(self, app: VibeApp) -> None:
app._input_queue.append_prompt("queued")
with (
patch.object(app, "_get_chat_input", return_value=None),
patch.object(app._quit_manager, "request_confirmation") as mock_confirm,
):
app.action_delete_right_or_quit()
mock_confirm.assert_called_once_with(
"Ctrl+D", "1 queued message will be discarded"
)

View file

@ -1,6 +1,14 @@
from __future__ import annotations from __future__ import annotations
from vibe.cli.textual_ui.widgets.tool_widgets import _strip_line_numbers from pydantic import BaseModel
from vibe.cli.textual_ui.widgets.collapsible import CollapsibleSection
from vibe.cli.textual_ui.widgets.tool_widgets import (
ToolResultWidget,
_fenced_code_block,
_strip_line_numbers,
get_result_widget,
)
def test_strips_numbered_prefixes() -> None: def test_strips_numbered_prefixes() -> None:
@ -16,3 +24,57 @@ def test_leaves_warning_lines_untouched() -> None:
def test_preserves_arrows_inside_content() -> None: def test_preserves_arrows_inside_content() -> None:
content = " 1→a → b → c" content = " 1→a → b → c"
assert _strip_line_numbers(content) == "a → b → c" assert _strip_line_numbers(content) == "a → b → c"
def test_fence_uses_three_backticks_for_plain_content() -> None:
block = _fenced_code_block("hello\nworld", "py")
assert block == "```py\nhello\nworld\n```"
def test_fence_outgrows_embedded_triple_backticks() -> None:
content = "before\n```\n[click me](http://evil)\n```\nafter"
block = _fenced_code_block(content, "md")
fence = "````"
assert block == f"{fence}md\n{content}\n{fence}"
assert block.startswith(fence)
assert block.endswith(fence)
def test_fence_outgrows_longest_backtick_run() -> None:
content = "a ```` b ``` c"
block = _fenced_code_block(content, "")
assert block.startswith("`````")
assert block.endswith("`````")
def test_fence_strips_newlines_from_ext() -> None:
block = _fenced_code_block("safe", "x\n[click](http://evil)")
assert block == "```xclickhttpevil\nsafe\n```"
assert "\n[click]" not in block
def test_fence_strips_backticks_from_ext() -> None:
block = _fenced_code_block("safe", "py`\n```md")
first_line = block.split("\n", 1)[0]
assert first_line == "```pymd"
def test_fence_caps_ext_length() -> None:
block = _fenced_code_block("safe", "a" * 500)
first_line = block.split("\n", 1)[0]
assert first_line == "```" + "a" * 32
def test_unknown_tool_uses_default_widget() -> None:
widget = get_result_widget("unknown_tool", None, True, "done")
assert type(widget) is ToolResultWidget
def test_default_widget_renders_fields_collapsibly() -> None:
class _Result(BaseModel):
server: str
text: str
widget = ToolResultWidget(_Result(server="s", text="hello"), True, "ok")
children = list(widget.compose())
assert any(isinstance(child, CollapsibleSection) for child in children)

View file

@ -1,8 +1,11 @@
from __future__ import annotations from __future__ import annotations
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
from typing import cast
import pytest import pytest
from rich.text import Text
from textual.widgets import OptionList
from vibe.cli.textual_ui.widgets.session_picker import ( from vibe.cli.textual_ui.widgets.session_picker import (
SessionPickerApp, SessionPickerApp,
@ -48,6 +51,12 @@ def sample_latest_messages() -> dict[str, str]:
} }
def assert_delete_state(picker: SessionPickerApp, *, kind: str, option_id: str) -> None:
assert picker._delete_state is not None
assert picker._delete_state.kind == kind
assert picker._delete_state.option_id == option_id
class TestFormatRelativeTime: class TestFormatRelativeTime:
def test_just_now(self) -> None: def test_just_now(self) -> None:
now = datetime.now(UTC).isoformat() now = datetime.now(UTC).isoformat()
@ -99,6 +108,7 @@ class TestSessionPickerAppInit:
) )
assert picker._sessions == sample_sessions assert picker._sessions == sample_sessions
assert picker._latest_messages == sample_latest_messages assert picker._latest_messages == sample_latest_messages
assert picker._current_session_id is None
def test_id_is_sessionpicker_app(self) -> None: def test_id_is_sessionpicker_app(self) -> None:
picker = SessionPickerApp(sessions=[], latest_messages={}) picker = SessionPickerApp(sessions=[], latest_messages={})
@ -107,6 +117,29 @@ class TestSessionPickerAppInit:
def test_can_focus_children_is_true(self) -> None: def test_can_focus_children_is_true(self) -> None:
assert SessionPickerApp.can_focus_children is True assert SessionPickerApp.can_focus_children is True
def test_delete_confirmation_state_starts_empty(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
assert picker._delete_state is None
def test_has_sessions_tracks_session_list(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
assert picker.has_sessions is True
empty_picker = SessionPickerApp(sessions=[], latest_messages={})
assert empty_picker.has_sessions is False
class TestSessionPickerMessages: class TestSessionPickerMessages:
def test_session_selected_stores_option_id(self) -> None: def test_session_selected_stores_option_id(self) -> None:
@ -129,16 +162,377 @@ class TestSessionPickerMessages:
msg = SessionPickerApp.Cancelled() msg = SessionPickerApp.Cancelled()
assert isinstance(msg, SessionPickerApp.Cancelled) assert isinstance(msg, SessionPickerApp.Cancelled)
def test_session_delete_requested_stores_session_info(self) -> None:
msg = SessionPickerApp.SessionDeleteRequested(
"local:test-session-id", "local", "test-session-id"
)
assert msg.option_id == "local:test-session-id"
assert msg.source == "local"
assert msg.session_id == "test-session-id"
class TestSessionPickerAppBindings: class TestSessionPickerAppBindings:
def _get_binding_keys(self) -> list[str]: def _get_binding_keys(self) -> list[str]:
keys = [] keys = []
for binding in SessionPickerApp.BINDINGS: for binding in SessionPickerApp.BINDINGS:
if isinstance(binding, tuple) and len(binding) >= 1: if isinstance(binding, tuple) and len(binding) >= 1:
keys.append(binding[0]) keys.extend(binding[0].split(","))
else: else:
keys.append(binding.key) keys.extend(binding.key.split(","))
return keys return keys
def test_has_escape_binding(self) -> None: def test_has_escape_binding(self) -> None:
assert "escape" in self._get_binding_keys() assert "escape" in self._get_binding_keys()
def test_has_delete_binding(self) -> None:
assert "d" in self._get_binding_keys()
assert "D" in self._get_binding_keys()
class TestSessionPickerSessionRemoval:
def test_first_delete_request_enters_confirmation(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
assert_delete_state(picker, kind="confirmation", option_id="local:session-a")
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert (
"Press D again to delete" in option_list.replaced_prompts[-1].prompt.plain
)
assert posted_messages == []
def test_second_delete_request_posts_delete_message(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_request_delete()
assert_delete_state(picker, kind="pending", option_id="local:session-a")
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert "Deleting..." in option_list.replaced_prompts[-1].prompt.plain
assert len(posted_messages) == 1
message = posted_messages[0]
assert isinstance(message, SessionPickerApp.SessionDeleteRequested)
assert message.option_id == "local:session-a"
assert message.source == "local"
assert message.session_id == "session-a"
def test_delete_confirmation_is_consumed_after_request(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_request_delete()
picker.action_request_delete()
assert len(posted_messages) == 1
assert_delete_state(picker, kind="pending", option_id="local:session-a")
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert "Deleting..." in option_list.replaced_prompts[-1].prompt.plain
def test_delete_request_shows_feedback_for_remote_sessions(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
assert_delete_state(picker, kind="feedback", option_id="remote:session-c")
assert option_list.replaced_prompts[-1].option_id == "remote:session-c"
assert (
"Can't delete remote session"
in option_list.replaced_prompts[-1].prompt.plain
)
assert posted_messages == []
def test_delete_request_shows_feedback_for_current_session(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions,
latest_messages=sample_latest_messages,
current_session_id="session-a",
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
assert_delete_state(picker, kind="feedback", option_id="local:session-a")
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert (
"Can't delete current session"
in option_list.replaced_prompts[-1].prompt.plain
)
assert posted_messages == []
picker.action_request_delete()
assert_delete_state(picker, kind="feedback", option_id="local:session-a")
assert posted_messages == []
def test_pending_delete_blocks_resume_selection(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_request_delete()
picker.on_option_list_option_selected(
cast(OptionList.OptionSelected, FakeOptionEvent("local:session-a"))
)
picker.on_option_list_option_selected(
cast(OptionList.OptionSelected, FakeOptionEvent("local:session-b"))
)
assert len(posted_messages) == 1
assert isinstance(posted_messages[0], SessionPickerApp.SessionDeleteRequested)
assert_delete_state(picker, kind="pending", option_id="local:session-a")
def test_clear_pending_delete_restores_session_option(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_request_delete()
assert picker.clear_pending_delete("local:session-a") is True
assert picker._delete_state is None
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert "Help me fix this bug" in option_list.replaced_prompts[-1].prompt.plain
def test_highlighting_another_session_clears_confirmation(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
picker.action_request_delete()
picker.on_option_list_option_highlighted(
cast(OptionList.OptionHighlighted, FakeOptionEvent("local:session-b"))
)
assert picker._delete_state is None
assert option_list.replaced_prompts[-1].option_id == "local:session-a"
assert "Help me fix this bug" in option_list.replaced_prompts[-1].prompt.plain
def test_highlighting_another_session_clears_delete_feedback(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
picker.action_request_delete()
picker.on_option_list_option_highlighted(
cast(OptionList.OptionHighlighted, FakeOptionEvent("local:session-b"))
)
assert picker._delete_state is None
assert option_list.replaced_prompts[-1].option_id == "remote:session-c"
assert (
"Add unit tests for the API"
in option_list.replaced_prompts[-1].prompt.plain
)
def test_escape_clears_confirmation_before_cancelling(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_cancel()
assert picker._delete_state is None
assert posted_messages == []
picker.action_cancel()
assert len(posted_messages) == 1
assert isinstance(posted_messages[0], SessionPickerApp.Cancelled)
def test_escape_clears_delete_feedback_before_cancelling(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="remote:session-c")
posted_messages: list[object] = []
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
monkeypatch.setattr(picker, "post_message", posted_messages.append)
picker.action_request_delete()
picker.action_cancel()
assert picker._delete_state is None
assert posted_messages == []
picker.action_cancel()
assert len(posted_messages) == 1
assert isinstance(posted_messages[0], SessionPickerApp.Cancelled)
def test_remove_session_updates_picker_state(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList(highlighted_option_id="local:session-a")
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
picker.action_request_delete()
assert_delete_state(picker, kind="confirmation", option_id="local:session-a")
assert picker.remove_session("local:session-a") is True
assert [session.option_id for session in picker._sessions] == [
"local:session-b",
"remote:session-c",
]
assert "local:session-a" not in picker._latest_messages
assert picker._delete_state is None
assert option_list.removed_option_ids == ["local:session-a"]
def test_remove_missing_session_returns_false(
self,
sample_sessions: list[ResumeSessionInfo],
sample_latest_messages: dict[str, str],
monkeypatch: pytest.MonkeyPatch,
) -> None:
picker = SessionPickerApp(
sessions=sample_sessions, latest_messages=sample_latest_messages
)
option_list = FakeOptionList()
monkeypatch.setattr(picker, "query_one", lambda _selector: option_list)
assert picker.remove_session("local:missing") is False
assert picker._sessions == sample_sessions
assert picker._latest_messages == sample_latest_messages
assert option_list.removed_option_ids == []
class FakeOption:
def __init__(self, option_id: str) -> None:
self.id = option_id
class FakeOptionEvent:
def __init__(self, option_id: str) -> None:
self.option = FakeOption(option_id)
class ReplacedPrompt:
def __init__(self, option_id: str, prompt: Text) -> None:
self.option_id = option_id
self.prompt = prompt
class FakeOptionList:
def __init__(self, highlighted_option_id: str | None = None) -> None:
self.highlighted_option = (
FakeOption(highlighted_option_id)
if highlighted_option_id is not None
else None
)
self.removed_option_ids: list[str] = []
self.replaced_prompts: list[ReplacedPrompt] = []
def remove_option(self, option_id: str) -> None:
self.removed_option_ids.append(option_id)
def replace_option_prompt(self, option_id: str, prompt: Text) -> None:
self.replaced_prompts.append(ReplacedPrompt(option_id, prompt))

View file

@ -0,0 +1,75 @@
from __future__ import annotations
import pytest
from textual import events
from vibe.cli.textual_ui.terminal_input_filter import (
FilteringXTermParser,
patch_driver_parser,
strip_malformed_mouse,
)
NOISE = [
"\x1b[<32;NaN;NaNM", # malformed SGR mouse (extended button on focus change)
"\x1b[<35;NaN;NaNm",
]
PRESERVED = [
"\x1b[A", # arrow up
"\x1b[15~", # F5
"\x1b[97u", # plain kitty key 'a'
"\x1b[<0;5;5M", # valid mouse down
"\x1b[<0;5;5m", # valid mouse up
"\x1b[<128;10;10M", # valid extended-button mouse
"\x1b[<0;-1;-1M", # negative SGR-Pixels coords (Textual handles these)
"\x1b[24;80R", # cursor position report
"\x1b[I", # focus in
]
@pytest.mark.parametrize("seq", NOISE)
def test_strip_removes_malformed_mouse(seq: str) -> None:
assert strip_malformed_mouse(seq) == ""
@pytest.mark.parametrize("seq", PRESERVED)
def test_strip_preserves_real_input(seq: str) -> None:
assert strip_malformed_mouse(seq) == seq
def test_strip_keeps_real_key_after_noise() -> None:
assert strip_malformed_mouse("\x1b[<32;NaN;NaNM\x1b[A") == "\x1b[A"
@pytest.mark.parametrize("seq", NOISE)
def test_parser_emits_no_keys_for_noise(seq: str) -> None:
tokens = list(FilteringXTermParser().feed(seq))
assert [t for t in tokens if isinstance(t, events.Key) and t.character] == []
def test_parser_still_emits_key_for_arrow() -> None:
tokens = list(FilteringXTermParser().feed("\x1b[A"))
assert any(isinstance(t, events.Key) and t.key == "up" for t in tokens)
def test_all_noise_chunk_does_not_trip_eof() -> None:
parser = FilteringXTermParser()
assert list(parser.feed("\x1b[<32;NaN;NaNM")) == []
# The parser must still be alive for the next real chunk.
assert any(
isinstance(t, events.Key) and t.key == "up" for t in parser.feed("\x1b[A")
)
def test_patch_driver_parser_rebinds_module_global() -> None:
import sys
from textual.drivers.linux_driver import LinuxDriver
namespace = sys.modules[LinuxDriver.__module__].__dict__
original = namespace["XTermParser"]
try:
patch_driver_parser(LinuxDriver)
assert namespace["XTermParser"] is FilteringXTermParser
finally:
namespace["XTermParser"] = original

View file

@ -52,7 +52,6 @@ def test_resumed_user_message_with_images_renders_footer(tmp_path: Path) -> None
[msg], [msg],
tool_call_map={}, tool_call_map={},
start_index=0, start_index=0,
tools_collapsed=False,
history_widget_indices=WeakKeyDictionary(), history_widget_indices=WeakKeyDictionary(),
) )
@ -70,7 +69,6 @@ def test_resumed_user_message_with_images_only_still_mounts(tmp_path: Path) -> N
[msg], [msg],
tool_call_map={}, tool_call_map={},
start_index=0, start_index=0,
tools_collapsed=False,
history_widget_indices=WeakKeyDictionary(), history_widget_indices=WeakKeyDictionary(),
) )

View file

@ -111,8 +111,25 @@ class TestAgentManager:
include_prompt_detail=False, include_prompt_detail=False,
disabled_agents=["plan"], disabled_agents=["plan"],
) )
with pytest.raises(ValueError, match="not available"): with pytest.raises(ValueError, match="disabled_agents") as exc_info:
AgentManager(lambda: config, initial_agent="plan") AgentManager(lambda: config, initial_agent="plan")
message = str(exc_info.value)
assert "default_agent" not in message
assert message.startswith("Agent 'plan'")
def test_explicit_agent_excluded_by_enabled_agents_does_not_blame_default(
self,
) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["default"],
)
with pytest.raises(ValueError, match="enabled_agents") as exc_info:
AgentManager(lambda: config, initial_agent="plan")
message = str(exc_info.value)
assert "default_agent" not in message
assert message.startswith("Agent 'plan'")
def test_initial_agent_raises_when_agent_does_not_exist(self) -> None: def test_initial_agent_raises_when_agent_does_not_exist(self) -> None:
config = build_test_vibe_config( config = build_test_vibe_config(
@ -120,3 +137,55 @@ class TestAgentManager:
) )
with pytest.raises(ValueError, match="not found"): with pytest.raises(ValueError, match="not found"):
AgentManager(lambda: config, initial_agent="nonexistent-agent") AgentManager(lambda: config, initial_agent="nonexistent-agent")
def test_default_agent_excluded_by_enabled_agents_raises_config_contradiction(
self,
) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["plan"],
)
with pytest.raises(ValueError, match="enabled_agents") as exc_info:
AgentManager(lambda: config)
message = str(exc_info.value)
assert "default" in message
assert "default_agent" in message
def test_default_agent_excluded_by_disabled_agents_raises_config_contradiction(
self,
) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
disabled_agents=["default"],
)
with pytest.raises(ValueError, match="disabled_agents") as exc_info:
AgentManager(lambda: config)
assert "default_agent" in str(exc_info.value)
def test_disabled_agents_ignored_entirely_when_enabled_agents_set(
self, caplog: pytest.LogCaptureFixture
) -> None:
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["plan"],
disabled_agents=["plan"],
)
with caplog.at_level("WARNING"):
manager = AgentManager(lambda: config, initial_agent="plan")
assert manager.active_profile.name == "plan"
assert caplog.text == ""
def test_install_required_agent_reports_install_not_disabled_agents(self) -> None:
# 'lean' is install_required and enabled but not installed: the message
# must point to installation, not blame disabled_agents.
config = build_test_vibe_config(
include_project_context=False,
include_prompt_detail=False,
enabled_agents=["lean"],
)
with pytest.raises(ValueError, match="requires installation") as exc_info:
AgentManager(lambda: config, initial_agent="lean")
assert "disabled_agents" not in str(exc_info.value)

View file

@ -1,46 +0,0 @@
from __future__ import annotations
from dataclasses import asdict
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from vibe.core.auth import EncryptedPayload, decrypt, encrypt
def _generate_test_key_pair() -> tuple[bytes, bytes]:
private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
public_pem = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
return private_pem, public_pem
class TestEncryptDecrypt:
def test_encrypt_decrypt_roundtrip(self) -> None:
private_pem, public_pem = _generate_test_key_pair()
plaintext = "ghp_test_token_12345"
encrypted = encrypt(plaintext, public_pem)
assert encrypted.encrypted_key != plaintext
assert encrypted.ciphertext != plaintext
decrypted = decrypt(encrypted, private_pem)
assert decrypted == plaintext
def test_encrypted_payload_serialization(self) -> None:
payload = EncryptedPayload(
encrypted_key="enc_key", nonce="nonce123", ciphertext="cipher"
)
data = asdict(payload)
restored = EncryptedPayload(**data)
assert restored == payload

View file

@ -1,286 +0,0 @@
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
from vibe.core.auth.github import (
DeviceFlowHandle,
DeviceFlowInfo,
GitHubAuthError,
GitHubAuthProvider,
)
class TestDeviceFlowModels:
def test_device_flow_info(self) -> None:
info = DeviceFlowInfo(
user_code="ABC-123", verification_uri="https://example.com"
)
assert info.user_code == "ABC-123"
assert info.verification_uri == "https://example.com"
def test_device_flow_handle(self) -> None:
info = DeviceFlowInfo(
user_code="ABC-123", verification_uri="https://example.com"
)
handle = DeviceFlowHandle(device_code="dc_123", expires_in=900, info=info)
assert handle.device_code == "dc_123"
assert handle.expires_in == 900
assert handle.info.user_code == "ABC-123"
class TestGitHubAuthProviderContextManager:
@pytest.mark.asyncio
async def test_creates_client_on_enter(self) -> None:
provider = GitHubAuthProvider()
assert provider._client is None
async with provider:
assert provider._client is not None
assert provider._client is None
@pytest.mark.asyncio
async def test_uses_provided_client(self) -> None:
external_client = httpx.AsyncClient()
provider = GitHubAuthProvider(client=external_client)
async with provider:
assert provider._client is external_client
assert provider._client is external_client
await external_client.aclose()
class TestGitHubAuthProviderGetToken:
def test_returns_token_from_keyring(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
mock_keyring.get_password.return_value = "ghp_test_token"
provider = GitHubAuthProvider()
token = provider.get_token()
assert token == "ghp_test_token"
def test_returns_none_on_keyring_error(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
import keyring.errors
mock_keyring.errors = keyring.errors
mock_keyring.get_password.side_effect = keyring.errors.KeyringError("error")
provider = GitHubAuthProvider()
token = provider.get_token()
assert token is None
def test_returns_none_when_no_token(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
mock_keyring.get_password.return_value = None
provider = GitHubAuthProvider()
token = provider.get_token()
assert token is None
class TestGitHubAuthProviderHasToken:
def test_returns_true_when_token_exists(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
mock_keyring.get_password.return_value = "ghp_token"
provider = GitHubAuthProvider()
assert provider.has_token() is True
def test_returns_false_when_no_token(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
mock_keyring.get_password.return_value = None
provider = GitHubAuthProvider()
assert provider.has_token() is False
class TestGitHubAuthProviderStartDeviceFlow:
@pytest.fixture
def mock_client(self) -> MagicMock:
return MagicMock(spec=httpx.AsyncClient)
@pytest.fixture
def provider(self, mock_client: MagicMock) -> GitHubAuthProvider:
return GitHubAuthProvider(client=mock_client)
@pytest.mark.asyncio
async def test_start_device_flow_success(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.is_success = True
mock_response.json.return_value = {
"device_code": "dc_123",
"user_code": "ABC-123",
"verification_uri": "https://github.com/login/device",
"expires_in": 900,
}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.webbrowser") as mock_browser:
handle = await provider.start_device_flow(open_browser=True)
mock_browser.open.assert_called_once_with("https://github.com/login/device")
assert handle.device_code == "dc_123"
assert handle.info.user_code == "ABC-123"
assert handle.expires_in == 900
@pytest.mark.asyncio
async def test_start_device_flow_without_browser(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.is_success = True
mock_response.json.return_value = {
"device_code": "dc_123",
"user_code": "ABC-123",
"verification_uri": "https://github.com/login/device",
"expires_in": 900,
}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.webbrowser") as mock_browser:
await provider.start_device_flow(open_browser=False)
mock_browser.open.assert_not_called()
@pytest.mark.asyncio
async def test_start_device_flow_failure(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.is_success = False
mock_response.text = "Bad request"
mock_client.post = AsyncMock(return_value=mock_response)
with pytest.raises(GitHubAuthError, match="Failed to initiate device flow"):
await provider.start_device_flow()
class TestGitHubAuthProviderPollForToken:
@pytest.fixture
def mock_client(self) -> MagicMock:
return MagicMock(spec=httpx.AsyncClient)
@pytest.fixture
def provider(self, mock_client: MagicMock) -> GitHubAuthProvider:
return GitHubAuthProvider(client=mock_client)
@pytest.mark.asyncio
async def test_poll_returns_token_on_success(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"access_token": "ghp_new_token"}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock):
token = await provider._poll_for_token(
mock_client, "dc_123", expires_in=10, interval=1
)
assert token == "ghp_new_token"
@pytest.mark.asyncio
async def test_poll_handles_slow_down(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
responses = [
MagicMock(
json=MagicMock(return_value={"error": "slow_down", "interval": 5})
),
MagicMock(json=MagicMock(return_value={"access_token": "ghp_token"})),
]
mock_client.post = AsyncMock(side_effect=responses)
with patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock):
token = await provider._poll_for_token(
mock_client, "dc_123", expires_in=30, interval=1
)
assert token == "ghp_token"
@pytest.mark.asyncio
async def test_poll_raises_on_expired_token(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"error": "expired_token"}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock):
with pytest.raises(GitHubAuthError, match="expired_token"):
await provider._poll_for_token(
mock_client, "dc_123", expires_in=10, interval=1
)
@pytest.mark.asyncio
async def test_poll_raises_on_access_denied(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"error": "access_denied"}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock):
with pytest.raises(GitHubAuthError, match="access_denied"):
await provider._poll_for_token(
mock_client, "dc_123", expires_in=10, interval=1
)
@pytest.mark.asyncio
async def test_poll_raises_on_timeout(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"error": "authorization_pending"}
mock_client.post = AsyncMock(return_value=mock_response)
with patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock):
with pytest.raises(GitHubAuthError, match="timed out"):
await provider._poll_for_token(
mock_client, "dc_123", expires_in=2, interval=1
)
class TestGitHubAuthProviderSaveToken:
def test_save_token_success(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
provider = GitHubAuthProvider()
provider._save_token("ghp_token")
mock_keyring.set_password.assert_called_once_with(
"vibe", "github_token", "ghp_token"
)
def test_save_token_raises_on_keyring_error(self) -> None:
with patch("vibe.core.auth.github.keyring") as mock_keyring:
import keyring.errors
mock_keyring.errors = keyring.errors
mock_keyring.set_password.side_effect = keyring.errors.KeyringError("error")
provider = GitHubAuthProvider()
with pytest.raises(GitHubAuthError, match="Failed to save token"):
provider._save_token("ghp_token")
class TestGitHubAuthProviderWaitForToken:
@pytest.fixture
def mock_client(self) -> MagicMock:
return MagicMock(spec=httpx.AsyncClient)
@pytest.fixture
def provider(self, mock_client: MagicMock) -> GitHubAuthProvider:
return GitHubAuthProvider(client=mock_client)
@pytest.mark.asyncio
async def test_wait_for_token_polls_and_saves(
self, provider: GitHubAuthProvider, mock_client: MagicMock
) -> None:
mock_response = MagicMock()
mock_response.json.return_value = {"access_token": "ghp_token"}
mock_client.post = AsyncMock(return_value=mock_response)
info = DeviceFlowInfo(user_code="ABC", verification_uri="https://example.com")
handle = DeviceFlowHandle(device_code="dc_123", expires_in=10, info=info)
with (
patch("vibe.core.auth.github.asyncio.sleep", new_callable=AsyncMock),
patch("vibe.core.auth.github.keyring") as mock_keyring,
):
token = await provider.wait_for_token(handle)
assert token == "ghp_token"
mock_keyring.set_password.assert_called_once()

View file

@ -1,6 +1,10 @@
from __future__ import annotations from __future__ import annotations
from vibe.core.compaction import collect_prior_user_messages from vibe.core.compaction import (
collect_prior_user_messages,
parse_previous_user_messages,
render_compaction_context,
)
from vibe.core.types import LLMMessage, Role from vibe.core.types import LLMMessage, Role
_PREFIX = "Another language model started to solve this problem" _PREFIX = "Another language model started to solve this problem"
@ -51,17 +55,52 @@ def test_empty_content_filtered_out() -> None:
def test_prior_summary_filtered_out() -> None: def test_prior_summary_filtered_out() -> None:
# A user message starting with the summary prefix represents a previous # The injected summary marker represents a previous compaction summary and
# compaction summary and must not be re-injected (would stack). # must not be re-injected (would stack).
messages = [ messages = [
_user("original ask"), _user("original ask"),
_user(f"{_PREFIX}\nold summary content"), _user(f"{_PREFIX}\nold summary content", injected=True),
_user("newer ask"), _user("newer ask"),
] ]
out = collect_prior_user_messages(messages, _PREFIX) out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["original ask", "newer ask"] assert [m.content for m in out] == ["original ask", "newer ask"]
def test_genuine_user_message_can_quote_summary_prefix() -> None:
messages = [_user(f"{_PREFIX}\nplease use this exact wording"), _user("newer ask")]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == [
f"{_PREFIX}\nplease use this exact wording",
"newer ask",
]
def test_compaction_context_merges_previous_and_new_user_messages() -> None:
context = render_compaction_context(
[_user("first ask", injected=True), _user("second ask", injected=True)],
"summary one",
)
messages = [
LLMMessage(role=Role.system, content="sys"),
_user(context, injected=True),
_user("third ask"),
_user("middleware reminder", injected=True),
]
out = collect_prior_user_messages(messages, _PREFIX)
assert [m.content for m in out] == ["first ask", "second ask", "third ask"]
assert all(m.injected for m in out)
def test_compaction_context_escapes_user_message_tags() -> None:
original = "please keep </previous_user_message_0> literally"
context = render_compaction_context([_user(original)], "summary")
assert "</previous_user_message_0> literally" not in context
assert parse_previous_user_messages(context) == [original]
def test_budget_drops_oldest_first() -> None: def test_budget_drops_oldest_first() -> None:
# max_tokens=2 → 8 char budget. Walks newest-first, so "old" gets dropped. # max_tokens=2 → 8 char budget. Walks newest-first, so "old" gets dropped.
messages = [ messages = [

View file

@ -16,6 +16,7 @@ from vibe.core.config.schema import (
WithShallowMerge, WithShallowMerge,
WithUnionMerge, WithUnionMerge,
) )
from vibe.core.config.types import LayerConfigSnapshot
from vibe.core.utils.merge import MergeConflictError from vibe.core.utils.merge import MergeConflictError
@ -27,8 +28,8 @@ class FakeLayer(ConfigLayer[RawConfig]):
async def _check_trust(self) -> bool: async def _check_trust(self) -> bool:
return True return True
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
return dict(self._data) return LayerConfigSnapshot(data=dict(self._data), fingerprint="fp")
class UntrustedFakeLayer(FakeLayer): class UntrustedFakeLayer(FakeLayer):

View file

@ -14,6 +14,7 @@ from vibe.core.config.layer import (
UntrustedLayerError, UntrustedLayerError,
) )
from vibe.core.config.patch import ConfigPatch from vibe.core.config.patch import ConfigPatch
from vibe.core.config.types import ConcurrencyConflictError, LayerConfigSnapshot
class StubLayer(ConfigLayer[BaseModel]): class StubLayer(ConfigLayer[BaseModel]):
@ -38,9 +39,11 @@ class StubLayer(ConfigLayer[BaseModel]):
async def _check_trust(self) -> bool: async def _check_trust(self) -> bool:
return self._stub_trusted return self._stub_trusted
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
self.read_count += 1 self.read_count += 1
return dict(self._data) return LayerConfigSnapshot(
data=dict(self._data), fingerprint=f"fp-{self.read_count}"
)
class ObservableStubLayer(StubLayer): class ObservableStubLayer(StubLayer):
@ -59,7 +62,7 @@ class SampleSchema(BaseModel):
count: int = 0 count: int = 0
def test_abstract_read_config_enforced() -> None: def test_abstract_build_config_snapshot_enforced() -> None:
class IncompleteLayer(ConfigLayer[BaseModel]): class IncompleteLayer(ConfigLayer[BaseModel]):
pass pass
@ -72,11 +75,21 @@ def test_repr() -> None:
assert repr(layer) == "StubLayer(name='my-layer')" assert repr(layer) == "StubLayer(name='my-layer')"
def test_layer_config_snapshot_strips_fingerprint() -> None:
snapshot = LayerConfigSnapshot(data={}, fingerprint=" fp ")
assert snapshot.fingerprint == "fp"
def test_layer_config_snapshot_rejects_empty_fingerprint() -> None:
with pytest.raises(ValidationError):
LayerConfigSnapshot(data={}, fingerprint=" ")
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_default_check_trust_returns_false() -> None: async def test_default_check_trust_returns_false() -> None:
class DefaultTrustLayer(ConfigLayer[BaseModel]): class DefaultTrustLayer(ConfigLayer[BaseModel]):
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
return {} return LayerConfigSnapshot(data={}, fingerprint="fp")
layer = DefaultTrustLayer(name="default") layer = DefaultTrustLayer(name="default")
result = await layer.resolve_trust() result = await layer.resolve_trust()
@ -224,6 +237,7 @@ async def test_load_returns_data() -> None:
result = await layer.load() result = await layer.load()
assert isinstance(result, RawConfig) assert isinstance(result, RawConfig)
assert result.model_extra == {"key": "value"} assert result.model_extra == {"key": "value"}
assert layer.fingerprint == "fp-1"
@pytest.mark.asyncio @pytest.mark.asyncio
@ -233,6 +247,10 @@ async def test_load_auto_resolves_trust() -> None:
result = await layer.load() result = await layer.load()
assert layer.is_trusted is True assert layer.is_trusted is True
assert result.model_extra == {"a": 1} assert result.model_extra == {"a": 1}
assert layer.fingerprint == "fp-1"
await layer.resolve_trust()
assert layer.fingerprint == "fp-1"
@pytest.mark.asyncio @pytest.mark.asyncio
@ -251,6 +269,7 @@ async def test_load_force_bypasses_cache() -> None:
assert layer.read_count == 1 assert layer.read_count == 1
await layer.load(force=True) await layer.load(force=True)
assert layer.read_count == 2 assert layer.read_count == 2
assert layer.fingerprint == "fp-2"
@pytest.mark.asyncio @pytest.mark.asyncio
@ -312,6 +331,7 @@ async def test_resolve_trust_clears_data_on_revocation() -> None:
layer._stub_trusted = False layer._stub_trusted = False
await layer.resolve_trust() await layer.resolve_trust()
assert layer.is_trusted is False assert layer.is_trusted is False
assert layer.fingerprint is None
# Re-trust and update backing data while revoked # Re-trust and update backing data while revoked
layer._stub_trusted = True layer._stub_trusted = True
@ -335,17 +355,30 @@ async def test_load_returns_deep_copy() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_read_config_failure_wrapped() -> None: async def test_build_config_snapshot_failure_wrapped() -> None:
class BrokenReadLayer(StubLayer): class BrokenReadLayer(StubLayer):
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
raise OSError("config file missing") raise OSError("config file missing")
layer = BrokenReadLayer() layer = BrokenReadLayer()
with pytest.raises(LayerImplementationError, match="_read_config") as exc_info: with pytest.raises(
LayerImplementationError, match="_build_config_snapshot"
) as exc_info:
await layer.load() await layer.load()
assert isinstance(exc_info.value.__cause__, IOError) assert isinstance(exc_info.value.__cause__, IOError)
@pytest.mark.asyncio
async def test_build_config_snapshot_concurrency_conflict_propagates() -> None:
class ConflictingReadLayer(StubLayer):
async def _build_config_snapshot(self) -> LayerConfigSnapshot:
raise ConcurrencyConflictError(expected_fp="before", actual_fp="after")
layer = ConflictingReadLayer()
with pytest.raises(ConcurrencyConflictError):
await layer.load()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_default_schema_preserves_extras() -> None: async def test_default_schema_preserves_extras() -> None:
layer = StubLayer(data={"anything": "goes"}) layer = StubLayer(data={"anything": "goes"})
@ -364,10 +397,13 @@ async def test_custom_schema_validates() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_invalid_data_raises_validation_error() -> None: async def test_invalid_data_raises_layer_implementation_error() -> None:
layer = StubLayer(output_schema=SampleSchema, data={"count": "bad"}) layer = StubLayer(output_schema=SampleSchema, data={"count": "bad"})
with pytest.raises(ValidationError): with pytest.raises(
LayerImplementationError, match="_build_config_snapshot"
) as exc_info:
await layer.load() await layer.load()
assert isinstance(exc_info.value.__cause__, ValidationError)
@pytest.mark.asyncio @pytest.mark.asyncio
@ -380,10 +416,12 @@ async def test_concurrent_loads_serialize() -> None:
async def _check_trust(self) -> bool: async def _check_trust(self) -> bool:
return True return True
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
self.read_count += 1 self.read_count += 1
await asyncio.sleep(0.05) await asyncio.sleep(0.05)
return {"v": self.read_count} return LayerConfigSnapshot(
data={"v": self.read_count}, fingerprint=f"fp-{self.read_count}"
)
layer = SlowLayer() layer = SlowLayer()
results = await asyncio.gather(layer.load(), layer.load(), layer.load()) results = await asyncio.gather(layer.load(), layer.load(), layer.load())
@ -392,10 +430,9 @@ async def test_concurrent_loads_serialize() -> None:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_fingerprint_not_implemented() -> None: async def test_fingerprint_returns_none_before_load() -> None:
layer = StubLayer() layer = StubLayer()
with pytest.raises(NotImplementedError): assert layer.fingerprint is None
await layer.get_fingerprint()
@pytest.mark.asyncio @pytest.mark.asyncio
@ -459,8 +496,8 @@ class FakeLocalUserLayer(ConfigLayer[UserConfigSchema]):
async def _check_trust(self) -> bool: async def _check_trust(self) -> bool:
return True return True
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
return dict(self._data) return LayerConfigSnapshot(data=dict(self._data), fingerprint="user-fp")
@pytest.mark.asyncio @pytest.mark.asyncio
@ -504,8 +541,8 @@ class FakeLocalProjectLayer(ConfigLayer[BaseModel]):
else: else:
self._trust_store.pop(self._project_path, None) self._trust_store.pop(self._project_path, None)
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
return dict(self._data) return LayerConfigSnapshot(data=dict(self._data), fingerprint="project-fp")
@pytest.mark.asyncio @pytest.mark.asyncio

View file

@ -0,0 +1,213 @@
from __future__ import annotations
import logging
from pydantic import ValidationError
import pytest
from vibe.core.config import MCPHttp, MCPOAuth, MCPStaticAuth, MCPStreamableHttp
from vibe.core.tools.mcp.registry import MCPRegistry
HTTP_TRANSPORTS = [
pytest.param(MCPHttp, "http", id="http"),
pytest.param(MCPStreamableHttp, "streamable-http", id="streamable-http"),
]
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_legacy_top_level_keys_promote_to_static_auth(
cls: type[MCPHttp | MCPStreamableHttp],
transport: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("MCP_TOKEN", "secret-token")
srv = cls.model_validate({
"name": "remote",
"transport": transport,
"url": "https://mcp.example.com",
"api_key_env": "MCP_TOKEN",
})
assert isinstance(srv.auth, MCPStaticAuth)
assert srv.auth.api_key_env == "MCP_TOKEN"
assert srv.http_headers() == {"Authorization": "Bearer secret-token"}
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_legacy_custom_header_and_format(
cls: type[MCPHttp | MCPStreamableHttp],
transport: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setenv("MCP_TOKEN", "k")
srv = cls.model_validate({
"name": "remote",
"transport": transport,
"url": "https://mcp.example.com",
"api_key_env": "MCP_TOKEN",
"api_key_header": "X-API-Key",
"api_key_format": "{token}",
})
assert srv.http_headers() == {"X-API-Key": "k"}
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_explicit_static_auth_round_trips(
cls: type[MCPHttp | MCPStreamableHttp], transport: str
) -> None:
srv = cls.model_validate({
"name": "remote",
"transport": transport,
"url": "https://mcp.example.com",
"auth": {"type": "static", "api_key_env": "X", "api_key_header": "X-API-Key"},
})
dumped = srv.model_dump()
rebuilt = cls.model_validate(dumped)
assert isinstance(rebuilt.auth, MCPStaticAuth)
assert rebuilt.auth.api_key_env == "X"
assert rebuilt.auth.api_key_header == "X-API-Key"
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_oauth_auth_parses(
cls: type[MCPHttp | MCPStreamableHttp], transport: str
) -> None:
srv = cls.model_validate({
"name": "linear",
"transport": transport,
"url": "https://mcp.linear.app/mcp",
"auth": {"type": "oauth", "scopes": ["read", "write"]},
})
assert isinstance(srv.auth, MCPOAuth)
assert srv.auth.scopes == ["read", "write"]
assert srv.auth.redirect_port == 47823
assert srv.http_headers() == {}
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_mixing_legacy_keys_with_auth_block_is_rejected(
cls: type[MCPHttp | MCPStreamableHttp], transport: str
) -> None:
with pytest.raises(ValidationError, match="cannot mix top-level"):
cls.model_validate({
"name": "remote",
"transport": transport,
"url": "https://mcp.example.com",
"api_key_env": "LEGACY",
"auth": {"type": "static", "api_key_env": "NEW"},
})
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
def test_default_auth_is_static(
cls: type[MCPHttp | MCPStreamableHttp], transport: str
) -> None:
srv = cls.model_validate({
"name": "remote",
"transport": transport,
"url": "https://mcp.example.com",
})
assert isinstance(srv.auth, MCPStaticAuth)
assert srv.http_headers() == {}
def test_oauth_client_id_and_metadata_url_mutually_exclusive() -> None:
with pytest.raises(ValidationError, match="mutually exclusive"):
MCPOAuth.model_validate({
"type": "oauth",
"scopes": ["read"],
"client_id": "abc",
"client_metadata_url": "https://example.com/cm.json",
})
def test_oauth_client_metadata_url_must_be_http_url() -> None:
with pytest.raises(ValidationError):
MCPOAuth.model_validate({
"type": "oauth",
"scopes": ["read"],
"client_metadata_url": "not-a-url",
})
def test_oauth_client_id_rejects_empty_string() -> None:
with pytest.raises(ValidationError):
MCPOAuth.model_validate({"type": "oauth", "scopes": ["read"], "client_id": ""})
@pytest.mark.parametrize("port", [80, 1023, 0, 65536, 70000])
def test_oauth_redirect_port_out_of_range(port: int) -> None:
with pytest.raises(ValidationError):
MCPOAuth.model_validate({
"type": "oauth",
"scopes": ["read"],
"redirect_port": port,
})
def test_oauth_redirect_port_inside_range() -> None:
auth = MCPOAuth.model_validate({
"type": "oauth",
"scopes": ["read"],
"redirect_port": 1024,
})
assert auth.redirect_port == 1024
def test_static_auth_forbids_extra_keys() -> None:
with pytest.raises(ValidationError):
MCPStaticAuth.model_validate({"type": "static", "headerz": {}})
def test_oauth_forbids_extra_keys() -> None:
with pytest.raises(ValidationError):
MCPOAuth.model_validate({"type": "oauth", "scopes": ["read"], "scope": "x"})
def test_oauth_scopes_required() -> None:
with pytest.raises(ValidationError):
MCPOAuth.model_validate({"type": "oauth"})
def test_oauth_scopes_empty_list_allowed() -> None:
auth = MCPOAuth.model_validate({"type": "oauth", "scopes": []})
assert auth.scopes == []
@pytest.mark.asyncio
@pytest.mark.parametrize(("cls", "transport"), HTTP_TRANSPORTS)
async def test_registry_skips_oauth_servers_with_gated_warning(
cls: type[MCPHttp | MCPStreamableHttp],
transport: str,
caplog: pytest.LogCaptureFixture,
) -> None:
srv = cls.model_validate({
"name": "linear",
"transport": transport,
"url": "https://mcp.linear.app/mcp",
"auth": {"type": "oauth", "scopes": ["read"]},
})
registry = MCPRegistry()
with caplog.at_level(logging.WARNING, logger="vibe"):
first = await registry.get_tools_async([srv])
assert first == {}
assert (
"OAuth support for MCP servers is not yet enabled; coming in a future release"
in caplog.text
)
caplog.clear()
with caplog.at_level(logging.WARNING, logger="vibe"):
second = await registry.get_tools_async([srv])
assert second == {}
assert "OAuth support" not in caplog.text

View file

@ -9,6 +9,7 @@ from vibe.core.config.layer import ConfigLayer, RawConfig
from vibe.core.config.orchestrator import ConfigOrchestrator from vibe.core.config.orchestrator import ConfigOrchestrator
from vibe.core.config.patch import ConfigPatch from vibe.core.config.patch import ConfigPatch
from vibe.core.config.schema import ConfigSchema, WithReplaceMerge from vibe.core.config.schema import ConfigSchema, WithReplaceMerge
from vibe.core.config.types import LayerConfigSnapshot
class FakeLayer(ConfigLayer[RawConfig]): class FakeLayer(ConfigLayer[RawConfig]):
@ -19,8 +20,8 @@ class FakeLayer(ConfigLayer[RawConfig]):
async def _check_trust(self) -> bool: async def _check_trust(self) -> bool:
return True return True
async def _read_config(self) -> dict[str, Any]: async def _build_config_snapshot(self) -> LayerConfigSnapshot:
return dict(self._data) return LayerConfigSnapshot(data=dict(self._data), fingerprint="fp")
class SimpleSchema(ConfigSchema): class SimpleSchema(ConfigSchema):

View file

@ -342,7 +342,10 @@ class TestMigrateLeavesFindInBashAllowlist:
) -> None: ) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path)) monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml" config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "ls"]}}} data = {
"applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["echo", "ls"]}},
}
with config_file.open("wb") as f: with config_file.open("wb") as f:
tomli_w.dump(data, f) tomli_w.dump(data, f)
@ -359,7 +362,10 @@ class TestMigrateLeavesFindInBashAllowlist:
) -> None: ) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path)) monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml" config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}}} data = {
"applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}},
}
with config_file.open("wb") as f: with config_file.open("wb") as f:
tomli_w.dump(data, f) tomli_w.dump(data, f)
@ -396,7 +402,8 @@ class TestMigrateStripsBashAllowlistWildcardSuffix:
monkeypatch.setenv("VIBE_HOME", str(tmp_path)) monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml" config_file = tmp_path / "config.toml"
data = { data = {
"tools": {"bash": {"allowlist": ["git commit *", "npm install *", "echo"]}} "applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["git commit *", "npm install *", "echo"]}},
} }
with config_file.open("wb") as f: with config_file.open("wb") as f:
tomli_w.dump(data, f) tomli_w.dump(data, f)
@ -420,7 +427,8 @@ class TestMigrateStripsBashAllowlistWildcardSuffix:
monkeypatch.setenv("VIBE_HOME", str(tmp_path)) monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml" config_file = tmp_path / "config.toml"
data = { data = {
"tools": {"bash": {"allowlist": ["git commit *", "git commit", "find"]}} "applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["git commit *", "git commit", "find"]}},
} }
with config_file.open("wb") as f: with config_file.open("wb") as f:
tomli_w.dump(data, f) tomli_w.dump(data, f)
@ -438,7 +446,10 @@ class TestMigrateStripsBashAllowlistWildcardSuffix:
) -> None: ) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path)) monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml" config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}}} data = {
"applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["echo", "find", "ls"]}},
}
with config_file.open("wb") as f: with config_file.open("wb") as f:
tomli_w.dump(data, f) tomli_w.dump(data, f)
@ -451,6 +462,68 @@ class TestMigrateStripsBashAllowlistWildcardSuffix:
assert result["tools"]["bash"]["allowlist"] == ["echo", "find", "ls"] assert result["tools"]["bash"]["allowlist"] == ["echo", "find", "ls"]
class TestMigrateBashReadOnlyDefaults:
def test_merges_read_only_commands_into_existing_allowlist(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from vibe.core.tools.builtins.bash import default_read_only_commands
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"tools": {"bash": {"allowlist": ["echo", "git commit"]}}}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
allowlist = result["tools"]["bash"]["allowlist"]
assert "git commit" in allowlist
for cmd in default_read_only_commands():
assert cmd in allowlist
assert VibeConfig._BASH_READ_ONLY_MIGRATION in result["applied_migrations"]
def test_does_not_readd_removed_command_after_migration(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {
"applied_migrations": [VibeConfig._BASH_READ_ONLY_MIGRATION],
"tools": {"bash": {"allowlist": ["echo", "find"]}},
}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result["tools"]["bash"]["allowlist"] == ["echo", "find"]
def test_noop_when_no_bash_allowlist(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VIBE_HOME", str(tmp_path))
config_file = tmp_path / "config.toml"
data = {"active_model": "test"}
with config_file.open("wb") as f:
tomli_w.dump(data, f)
reset_harness_files_manager()
init_harness_files_manager("user")
VibeConfig._migrate()
with config_file.open("rb") as f:
result = tomllib.load(f)
assert result == {"active_model": "test"}
class TestMigrateMistralVibeCliLatestDefaults: class TestMigrateMistralVibeCliLatestDefaults:
def test_updates_alias_temperature_and_thinking_for_default_model( def test_updates_alias_temperature_and_thinking_for_default_model(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch

View file

@ -43,6 +43,26 @@ async def test_no_vars_set_returns_empty() -> None:
assert data.model_dump() == {} assert data.model_dump() == {}
@pytest.mark.asyncio
async def test_fingerprint_changes_when_env_changes() -> None:
with patch.dict(os.environ, {"VIBE_ACTIVE_MODEL": "first-model"}, clear=True):
layer = EnvironmentLayer(schema=VibeConfigSchema)
data1 = await layer.load()
fp1 = layer.fingerprint
os.environ["VIBE_ACTIVE_MODEL"] = "second-model"
data2 = await layer.load(force=True)
fp2 = layer.fingerprint
assert data1.model_dump() == {"active_model": "first-model"}
assert data2.model_dump() == {"active_model": "second-model"}
assert isinstance(fp1, str)
assert fp1
assert isinstance(fp2, str)
assert fp2
assert fp1 != fp2
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_always_trusted() -> None: async def test_always_trusted() -> None:
assert await EnvironmentLayer(schema=VibeConfigSchema).resolve_trust() is True assert await EnvironmentLayer(schema=VibeConfigSchema).resolve_trust() is True

View file

@ -0,0 +1,118 @@
from __future__ import annotations
from pathlib import Path
import pytest
from vibe.core.config.fingerprint import capture_stable_file, create_dict_fingerprint
from vibe.core.config.types import ConcurrencyConflictError
class TestCaptureStableFile:
def test_captures_unchanged_file(self, tmp_working_directory: Path) -> None:
path = tmp_working_directory / "config.toml"
path.write_text("key = 1")
with capture_stable_file(path) as (file, first_fingerprint):
assert file.read() == b"key = 1"
with capture_stable_file(path) as (file, second_fingerprint):
assert file.read() == b"key = 1"
assert isinstance(first_fingerprint, str)
assert first_fingerprint
assert first_fingerprint == second_fingerprint
def test_raises_when_file_changes(self, tmp_working_directory: Path) -> None:
path = tmp_working_directory / "config.toml"
path.write_text("key = 1")
with pytest.raises(ConcurrencyConflictError) as exc_info:
with capture_stable_file(path):
path.write_text("key = 123")
assert exc_info.value.actual_fp != exc_info.value.expected_fp
def test_raises_when_path_is_replaced_after_open(
self, tmp_working_directory: Path
) -> None:
path = tmp_working_directory / "config.toml"
replacement = tmp_working_directory / "replacement.toml"
path.write_text("key = 1")
replacement.write_text("key = 2")
with pytest.raises(ConcurrencyConflictError) as exc_info:
with capture_stable_file(path) as (file, _):
replacement.replace(path)
assert file.read() == b"key = 1"
assert exc_info.value.actual_fp != exc_info.value.expected_fp
def test_raises_when_file_disappears_after_read(
self, tmp_working_directory: Path
) -> None:
path = tmp_working_directory / "config.toml"
path.write_text("key = 1")
with pytest.raises(FileNotFoundError):
with capture_stable_file(path) as (file, _):
assert file.read() == b"key = 1"
path.unlink()
def test_raises_when_file_is_missing(self, tmp_working_directory: Path) -> None:
path = tmp_working_directory / "missing.toml"
with pytest.raises(FileNotFoundError):
with capture_stable_file(path):
pass
class TestCreateDictFingerprint:
def test_empty_dict_returns_stable_non_empty_token(self) -> None:
first_fingerprint = create_dict_fingerprint({})
second_fingerprint = create_dict_fingerprint({})
assert isinstance(first_fingerprint, str)
assert first_fingerprint
assert first_fingerprint == second_fingerprint
def test_stable_for_same_dict(self) -> None:
data = {
"VIBE_MODEL": "mistral-large",
"VIBE_THEME": "dark",
"VIBE_TOOLS": ["read", "write"],
}
fp1 = create_dict_fingerprint(data)
fp2 = create_dict_fingerprint(data)
assert fp1 == fp2
def test_order_independent(self) -> None:
fp1 = create_dict_fingerprint({"a": "1", "b": "2"})
fp2 = create_dict_fingerprint({"b": "2", "a": "1"})
assert fp1 == fp2
def test_serializes_path_values(self) -> None:
fp1 = create_dict_fingerprint({
"tool_paths": [Path("/tmp/custom-tools")],
"agent_paths": [Path("agents")],
})
fp2 = create_dict_fingerprint({
"tool_paths": ["/tmp/custom-tools"],
"agent_paths": ["agents"],
})
assert fp1 == fp2
def test_changes_when_list_order_changes(self) -> None:
fp1 = create_dict_fingerprint({"tools": ["read", "write"]})
fp2 = create_dict_fingerprint({"tools": ["write", "read"]})
assert fp1 != fp2
def test_changes_when_value_changes(self) -> None:
fp1 = create_dict_fingerprint({"VIBE_MODEL": "mistral-large"})
fp2 = create_dict_fingerprint({"VIBE_MODEL": "devstral-2"})
assert fp1 != fp2
def test_changes_when_key_added(self) -> None:
fp1 = create_dict_fingerprint({"a": "1"})
fp2 = create_dict_fingerprint({"a": "1", "b": "2"})
assert fp1 != fp2

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
import pytest
from vibe.core.paths._local_config_files import LocalConfigDirs, find_local_config_dirs from vibe.core.paths._local_config_files import LocalConfigDirs, find_local_config_dirs
@ -87,6 +89,21 @@ class TestConfigDirs:
assert resolved / ".vibe" in result.config_dirs assert resolved / ".vibe" in result.config_dirs
assert resolved / ".agents" in result.config_dirs assert resolved / ".agents" in result.config_dirs
def test_unreadable_config_dirs_do_not_crash(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
def fake_is_dir(self: Path) -> bool:
raise PermissionError(13, "Permission denied")
def fake_is_file(self: Path) -> bool:
raise PermissionError(13, "Permission denied")
monkeypatch.setattr(Path, "is_dir", fake_is_dir)
monkeypatch.setattr(Path, "is_file", fake_is_file)
result = find_local_config_dirs(tmp_path)
assert result == LocalConfigDirs()
class TestLocalConfigDirsOr: class TestLocalConfigDirsOr:
def test_or_concatenates_each_field(self) -> None: def test_or_concatenates_each_field(self) -> None:

View file

@ -0,0 +1,496 @@
from __future__ import annotations
import asyncio
from collections.abc import Callable, Iterator
from contextlib import suppress
import socket
import urllib.parse
import httpx
import keyring
from keyring.backend import KeyringBackend
import keyring.backends.fail
import keyring.errors
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
import pytest
import respx
from vibe.core.auth.mcp_oauth import (
Fingerprint,
KeyringTokenStorage,
LoopbackCallbackHandler,
MCPOAuthError,
MCPOAuthHeadlessError,
MCPOAuthPortInUse,
build_oauth_provider,
perform_oauth_login,
)
from vibe.core.config import MCPOAuth, MCPStreamableHttp
class _MemoryKeyring(KeyringBackend):
priority = 100 # type: ignore[assignment]
def __init__(self) -> None:
self.store: dict[tuple[str, str], str] = {}
def get_password(self, service: str, username: str) -> str | None:
return self.store.get((service, username))
def set_password(self, service: str, username: str, password: str) -> None:
self.store[(service, username)] = password
def delete_password(self, service: str, username: str) -> None:
if (service, username) not in self.store:
raise keyring.errors.PasswordDeleteError(username)
del self.store[(service, username)]
@pytest.fixture
def memory_keyring() -> Iterator[_MemoryKeyring]:
original = keyring.get_keyring()
fake = _MemoryKeyring()
keyring.set_keyring(fake)
try:
yield fake
finally:
keyring.set_keyring(original)
@pytest.fixture
def headless_keyring() -> Iterator[None]:
original = keyring.get_keyring()
keyring.set_keyring(keyring.backends.fail.Keyring())
try:
yield
finally:
keyring.set_keyring(original)
def _oauth_server(
*,
name: str = "demo",
url: str = "https://mcp.example.com/mcp",
scopes: list[str] | None = None,
client_id: str | None = None,
client_metadata_url: str | None = None,
redirect_port: int = 47823,
) -> MCPStreamableHttp:
auth = MCPOAuth(
type="oauth",
scopes=scopes if scopes is not None else ["read", "write"],
client_id=client_id,
client_metadata_url=client_metadata_url, # type: ignore[arg-type]
redirect_port=redirect_port,
)
return MCPStreamableHttp(transport="streamable-http", name=name, url=url, auth=auth)
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
return int(s.getsockname()[1])
async def _send_callback(port: int, query: str, *, timeout: float = 5.0) -> bytes:
deadline = asyncio.get_event_loop().time() + timeout
last_err: BaseException | None = None
while asyncio.get_event_loop().time() < deadline:
try:
reader, writer = await asyncio.open_connection("127.0.0.1", port)
except (ConnectionRefusedError, OSError) as exc:
last_err = exc
await asyncio.sleep(0.02)
continue
try:
request = (
f"GET /callback?{query} HTTP/1.0\r\n"
"Host: 127.0.0.1\r\n"
"Connection: close\r\n"
"\r\n"
)
writer.write(request.encode("ascii"))
await writer.drain()
return await reader.read()
finally:
writer.close()
with suppress(Exception):
await writer.wait_closed()
raise RuntimeError(f"loopback never bound on port {port}: {last_err}")
class TestKeyringTokenStorage:
@pytest.mark.asyncio
async def test_round_trip_tokens(self, memory_keyring: _MemoryKeyring) -> None:
storage = KeyringTokenStorage(alias="linear")
assert await storage.get_tokens() is None
tokens = OAuthToken(
access_token="at",
token_type="Bearer",
expires_in=3600,
refresh_token="rt",
scope="read write",
)
await storage.set_tokens(tokens)
loaded = await storage.get_tokens()
assert loaded is not None
assert loaded.access_token == "at"
assert loaded.refresh_token == "rt"
assert loaded.scope == "read write"
assert ("vibe", "mcp-oauth:linear:tokens") in memory_keyring.store
@pytest.mark.asyncio
async def test_round_trip_client_info(self, memory_keyring: _MemoryKeyring) -> None:
storage = KeyringTokenStorage(alias="linear")
assert await storage.get_client_info() is None
info = OAuthClientInformationFull(
client_id="abc123",
redirect_uris=["http://127.0.0.1:47823/callback"], # type: ignore[list-item]
token_endpoint_auth_method="none",
)
await storage.set_client_info(info)
loaded = await storage.get_client_info()
assert loaded is not None
assert loaded.client_id == "abc123"
assert ("vibe", "mcp-oauth:linear:client_info") in memory_keyring.store
@pytest.mark.asyncio
async def test_per_alias_isolation(self, memory_keyring: _MemoryKeyring) -> None:
a = KeyringTokenStorage(alias="linear")
b = KeyringTokenStorage(alias="notion")
await a.set_tokens(
OAuthToken(access_token="A", token_type="Bearer", expires_in=60)
)
await b.set_tokens(
OAuthToken(access_token="B", token_type="Bearer", expires_in=60)
)
loaded_a = await a.get_tokens()
loaded_b = await b.get_tokens()
assert loaded_a is not None and loaded_a.access_token == "A"
assert loaded_b is not None and loaded_b.access_token == "B"
def test_headless_init_raises(self, headless_keyring: None) -> None:
with pytest.raises(MCPOAuthHeadlessError) as exc_info:
KeyringTokenStorage(alias="linear")
msg = str(exc_info.value)
assert "linear" in msg
assert "api_key_env" in msg
assert exc_info.value.server_alias == "linear"
class TestFingerprint:
def test_compute_stable_across_scope_order(
self, memory_keyring: _MemoryKeyring
) -> None:
a = _oauth_server(scopes=["read", "write", "admin"])
b = _oauth_server(scopes=["admin", "write", "read"])
assert Fingerprint.compute(a) == Fingerprint.compute(b)
def test_compute_strips_whitespace_and_dedupes(
self, memory_keyring: _MemoryKeyring
) -> None:
a = _oauth_server(scopes=["read", "write"])
b = _oauth_server(scopes=[" read ", "write", "write", ""])
assert Fingerprint.compute(a) == Fingerprint.compute(b)
def test_compute_marker_for_client_id(self, memory_keyring: _MemoryKeyring) -> None:
srv = _oauth_server(client_id="pre-registered-id")
assert Fingerprint.compute(srv).client_marker == "pre-registered-id"
def test_compute_marker_for_client_metadata_url(
self, memory_keyring: _MemoryKeyring
) -> None:
srv = _oauth_server(client_metadata_url="https://vibe.example/cm.json")
fp = Fingerprint.compute(srv)
assert fp.client_marker.startswith("https://vibe.example/cm.json")
def test_compute_marker_for_dcr(self, memory_keyring: _MemoryKeyring) -> None:
assert Fingerprint.compute(_oauth_server()).client_marker == "<dcr>"
def test_compute_rejects_static_auth(self, memory_keyring: _MemoryKeyring) -> None:
from vibe.core.config import MCPStaticAuth
srv = MCPStreamableHttp(
transport="streamable-http",
name="x",
url="https://x/mcp",
auth=MCPStaticAuth(),
)
with pytest.raises(TypeError, match="OAuth"):
Fingerprint.compute(srv)
def test_matches_detects_url_change(self, memory_keyring: _MemoryKeyring) -> None:
a = Fingerprint.compute(_oauth_server(url="https://a/mcp"))
b = Fingerprint.compute(_oauth_server(url="https://b/mcp"))
assert a != b
def test_matches_detects_scope_change(self, memory_keyring: _MemoryKeyring) -> None:
a = Fingerprint.compute(_oauth_server(scopes=["read"]))
b = Fingerprint.compute(_oauth_server(scopes=["read", "write"]))
assert a != b
def test_matches_detects_marker_change(
self, memory_keyring: _MemoryKeyring
) -> None:
a = Fingerprint.compute(_oauth_server(client_id="x"))
b = Fingerprint.compute(_oauth_server(client_id="y"))
assert a != b
@pytest.mark.asyncio
async def test_load_returns_none_when_missing(
self, memory_keyring: _MemoryKeyring
) -> None:
assert await Fingerprint.load("nope") is None
@pytest.mark.asyncio
async def test_save_and_load_round_trip(
self, memory_keyring: _MemoryKeyring
) -> None:
fp = Fingerprint.compute(_oauth_server(name="linear"))
await fp.save("linear")
loaded = await Fingerprint.load("linear")
assert loaded == fp
class TestLoopbackCallbackHandler:
@pytest.mark.asyncio
async def test_happy_path(self) -> None:
port = _free_port()
handler = LoopbackCallbackHandler(port=port, server_alias="demo")
async def driver() -> bytes:
return await _send_callback(port, "code=AUTH_CODE_123&state=STATE_XYZ")
serve_task = asyncio.create_task(handler.serve_once())
driver_task = asyncio.create_task(driver())
code, state = await serve_task
response = await driver_task
assert code == "AUTH_CODE_123"
assert state == "STATE_XYZ"
assert b"200 OK" in response
assert b"Login complete" in response
@pytest.mark.asyncio
async def test_port_in_use_raises(self) -> None:
port = _free_port()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
sock.bind(("127.0.0.1", port))
sock.listen(1)
try:
handler = LoopbackCallbackHandler(port=port, server_alias="demo")
with pytest.raises(MCPOAuthPortInUse) as exc_info:
await handler.serve_once()
assert exc_info.value.port == port
assert exc_info.value.server_alias == "demo"
assert "redirect_port" in str(exc_info.value)
finally:
sock.close()
@pytest.mark.asyncio
async def test_missing_code_raises(self) -> None:
port = _free_port()
handler = LoopbackCallbackHandler(port=port, server_alias="demo")
async def driver() -> bytes:
return await _send_callback(port, "error=access_denied&state=S")
serve_task = asyncio.create_task(handler.serve_once())
driver_task = asyncio.create_task(driver())
with pytest.raises(MCPOAuthError, match="missing 'code'"):
await serve_task
response = await driver_task
assert b"400 Bad Request" in response
class TestBuildOAuthProvider:
@pytest.mark.asyncio
async def test_metadata_matches_config(
self, memory_keyring: _MemoryKeyring
) -> None:
srv = _oauth_server(scopes=["read", "write"], redirect_port=51234)
async def on_url(_url: str) -> None:
return None
async def cb() -> tuple[str, str | None]:
return "code", None
provider = build_oauth_provider(
srv, redirect_handler=on_url, callback_handler=cb
)
md = provider.context.client_metadata
assert md.scope == "read write"
assert md.client_name == "Mistral Vibe"
assert md.token_endpoint_auth_method == "none"
assert md.grant_types == ["authorization_code", "refresh_token"]
assert md.redirect_uris is not None
assert str(md.redirect_uris[0]) == "http://127.0.0.1:51234/callback"
@pytest.mark.asyncio
async def test_empty_scopes_becomes_none(
self, memory_keyring: _MemoryKeyring
) -> None:
srv = _oauth_server(scopes=[])
async def on_url(_url: str) -> None:
return None
async def cb() -> tuple[str, str | None]:
return "code", None
provider = build_oauth_provider(
srv, redirect_handler=on_url, callback_handler=cb
)
assert provider.context.client_metadata.scope is None
@pytest.mark.asyncio
async def test_client_metadata_url_forwarded(
self, memory_keyring: _MemoryKeyring
) -> None:
srv = _oauth_server(client_metadata_url="https://vibe.example/cm.json")
async def on_url(_url: str) -> None:
return None
async def cb() -> tuple[str, str | None]:
return "code", None
provider = build_oauth_provider(
srv, redirect_handler=on_url, callback_handler=cb
)
assert provider.context.client_metadata_url == "https://vibe.example/cm.json"
@pytest.mark.asyncio
async def test_rejects_static_auth(self, memory_keyring: _MemoryKeyring) -> None:
from vibe.core.config import MCPStaticAuth
srv = MCPStreamableHttp(
transport="streamable-http",
name="x",
url="https://x/mcp",
auth=MCPStaticAuth(),
)
async def on_url(_url: str) -> None:
return None
async def cb() -> tuple[str, str | None]:
return "code", None
with pytest.raises(TypeError, match="OAuth"):
build_oauth_provider(srv, redirect_handler=on_url, callback_handler=cb)
class TestPerformOAuthLogin:
@pytest.mark.asyncio
async def test_full_flow_persists_tokens_and_fingerprint(
self, memory_keyring: _MemoryKeyring
) -> None:
port = _free_port()
server_url = "https://mcp.example.com/mcp"
as_url = "https://as.example.com"
srv = _oauth_server(
name="demo", url=server_url, scopes=["read"], redirect_port=port
)
async def on_url(url: str) -> None:
qs = urllib.parse.urlparse(url).query
state = urllib.parse.parse_qs(qs)["state"][0]
async def fire() -> None:
await _send_callback(port, f"code=THE_CODE&state={state}")
asyncio.get_event_loop().create_task(fire())
async with respx.mock(assert_all_called=False) as router:
router.get(server_url).mock(side_effect=_mcp_responses())
router.get(
"https://mcp.example.com/.well-known/oauth-protected-resource"
).mock(
return_value=httpx.Response(
200,
json={"resource": server_url, "authorization_servers": [as_url]},
)
)
router.get(
"https://as.example.com/.well-known/oauth-authorization-server"
).mock(
return_value=httpx.Response(
200,
json={
"issuer": as_url,
"authorization_endpoint": f"{as_url}/authorize",
"token_endpoint": f"{as_url}/token",
"registration_endpoint": f"{as_url}/register",
"response_types_supported": ["code"],
"code_challenge_methods_supported": ["S256"],
"grant_types_supported": [
"authorization_code",
"refresh_token",
],
},
)
)
router.post(f"{as_url}/register").mock(
return_value=httpx.Response(
201,
json={
"client_id": "dcr-client-id",
"redirect_uris": [f"http://127.0.0.1:{port}/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
},
)
)
router.post(f"{as_url}/token").mock(
return_value=httpx.Response(
200,
json={
"access_token": "ACCESS_TOKEN",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "REFRESH_TOKEN",
"scope": "read",
},
)
)
router.route(host="127.0.0.1").pass_through()
await perform_oauth_login(srv, on_url=on_url)
storage = KeyringTokenStorage(alias="demo")
tokens = await storage.get_tokens()
assert tokens is not None
assert tokens.access_token == "ACCESS_TOKEN"
assert tokens.refresh_token == "REFRESH_TOKEN"
fp = await Fingerprint.load("demo")
assert fp is not None
assert fp == Fingerprint.compute(srv)
def _mcp_responses() -> Callable[[httpx.Request], httpx.Response]:
state = {"calls": 0}
def _factory(_request: httpx.Request) -> httpx.Response:
state["calls"] += 1
if state["calls"] == 1:
return httpx.Response(
401,
headers={
"WWW-Authenticate": (
"Bearer resource_metadata="
'"https://mcp.example.com/.well-known/oauth-protected-resource"'
)
},
)
return httpx.Response(200, json={"ok": True})
return _factory

View file

@ -80,6 +80,10 @@ async def test_live_reference_picks_up_caller_mutation() -> None:
data: dict[str, object] = {"key": "original"} data: dict[str, object] = {"key": "original"}
layer = OverridesLayer(data=data) layer = OverridesLayer(data=data)
await layer.load() await layer.load()
fp1 = layer.fingerprint
data["key"] = "updated" data["key"] = "updated"
result = await layer.load(force=True) result = await layer.load(force=True)
fp2 = layer.fingerprint
assert result.model_extra == {"key": "updated"} assert result.model_extra == {"key": "updated"}
assert fp1 != fp2

View file

@ -7,6 +7,7 @@ import pytest
from vibe.core.config.layer import UntrustedLayerError from vibe.core.config.layer import UntrustedLayerError
from vibe.core.config.layers.project import ProjectConfigLayer from vibe.core.config.layers.project import ProjectConfigLayer
from vibe.core.config.patch import ConfigPatch from vibe.core.config.patch import ConfigPatch
from vibe.core.config.types import MISSING_CONFIG_FILE_FINGERPRINT
from vibe.core.paths._vibe_home import GlobalPath from vibe.core.paths._vibe_home import GlobalPath
from vibe.core.trusted_folders import trusted_folders_manager from vibe.core.trusted_folders import trusted_folders_manager
@ -21,6 +22,14 @@ async def test_reads_toml_when_trusted(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory) layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load() data = await layer.load()
assert data.model_extra == {"active_model": "project-model"} assert data.model_extra == {"active_model": "project-model"}
fp1 = layer.fingerprint
assert isinstance(fp1, str)
assert fp1
config_path.unlink()
data = await layer.load(force=True)
assert data.model_extra == {}
assert layer.fingerprint == MISSING_CONFIG_FILE_FINGERPRINT
@pytest.mark.asyncio @pytest.mark.asyncio
@ -55,6 +64,7 @@ async def test_missing_file_returns_empty(tmp_working_directory: Path) -> None:
layer = ProjectConfigLayer(path=tmp_working_directory) layer = ProjectConfigLayer(path=tmp_working_directory)
data = await layer.load() data = await layer.load()
assert data.model_extra == {} assert data.model_extra == {}
assert layer.fingerprint == MISSING_CONFIG_FILE_FINGERPRINT
@pytest.mark.asyncio @pytest.mark.asyncio

View file

@ -1,9 +1,11 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from pathlib import Path
from typing import Any from typing import Any
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock, MagicMock
from pydantic import BaseModel
import pytest import pytest
from tests.conftest import build_test_vibe_config from tests.conftest import build_test_vibe_config
@ -14,7 +16,7 @@ from vibe.core.telemetry.build_metadata import (
build_base_metadata, build_base_metadata,
build_request_metadata, build_request_metadata,
) )
from vibe.core.telemetry.send import TelemetryClient from vibe.core.telemetry.send import TelemetryClient, _extract_file_extension
from vibe.core.telemetry.types import ( from vibe.core.telemetry.types import (
AttachmentKind, AttachmentKind,
EntrypointMetadata, EntrypointMetadata,
@ -25,16 +27,33 @@ from vibe.core.types import Backend
from vibe.core.utils import get_user_agent from vibe.core.utils import get_user_agent
_original_send_telemetry_event = TelemetryClient.send_telemetry_event _original_send_telemetry_event = TelemetryClient.send_telemetry_event
from vibe.core.tools.builtins.edit import Edit, EditArgs
from vibe.core.tools.builtins.read import Read, ReadArgs
from vibe.core.tools.builtins.write_file import WriteFile, WriteFileArgs from vibe.core.tools.builtins.write_file import WriteFile, WriteFileArgs
def _make_resolved_tool_call( def _make_resolved_tool_call(
tool_name: str, args_dict: dict[str, Any] tool_name: str, args_dict: dict[str, Any]
) -> ResolvedToolCall: ) -> ResolvedToolCall:
if tool_name == "write_file": validated: BaseModel
validated = WriteFileArgs(path="foo.txt", content="x") cls: type[BaseTool]
cls: type[BaseTool] = WriteFile match tool_name:
else: case "write_file":
validated = WriteFileArgs(
path=args_dict.get("path", "foo.txt"), content="x"
)
cls = WriteFile
case "edit":
validated = EditArgs(
file_path=args_dict.get("file_path", "foo.txt"),
old_string="a",
new_string="b",
)
cls = Edit
case "read":
validated = ReadArgs(file_path=args_dict.get("file_path", "foo.txt"))
cls = Read
case _:
validated = FakeToolArgs() validated = FakeToolArgs()
cls = FakeTool cls = FakeTool
return ResolvedToolCall( return ResolvedToolCall(
@ -50,6 +69,37 @@ def _run_telemetry_tasks() -> None:
loop.close() loop.close()
class TestExtractFileExtension:
@pytest.mark.parametrize(
("path", "expected"),
[
("/tmp/foo.py", ".py"),
("foo.TSX", ".tsx"),
("/tmp/Makefile", None),
("/tmp/.bashrc", None),
("archive.tar.gz", ".gz"),
("", None),
],
)
def test_string_inputs(self, path: str, expected: str | None) -> None:
assert _extract_file_extension(path) == expected
@pytest.mark.parametrize(
("path", "expected"),
[
(Path("/tmp/foo.py"), ".py"),
(Path("foo.TSX"), ".tsx"),
(Path("/tmp/Makefile"), None),
],
)
def test_path_inputs(self, path: Path, expected: str | None) -> None:
assert _extract_file_extension(path) == expected
@pytest.mark.parametrize("path", [None, 42, ["foo.py"], {"path": "foo.py"}])
def test_non_path_like_inputs_return_none(self, path: object) -> None:
assert _extract_file_extension(path) is None
class TestTelemetryClient: class TestTelemetryClient:
def test_send_telemetry_event_swallows_config_getter_value_error(self) -> None: def test_send_telemetry_event_swallows_config_getter_value_error(self) -> None:
def _raise_config_error() -> Any: def _raise_config_error() -> Any:
@ -151,6 +201,7 @@ class TestTelemetryClient:
assert properties["model"] == "mistral-large" assert properties["model"] == "mistral-large"
assert properties["nb_files_created"] == 0 assert properties["nb_files_created"] == 0
assert properties["nb_files_modified"] == 0 assert properties["nb_files_modified"] == 0
assert properties["file_extension"] is None
assert properties["message_id"] is None assert properties["message_id"] is None
def test_send_tool_call_finished_with_message_id( def test_send_tool_call_finished_with_message_id(
@ -176,7 +227,7 @@ class TestTelemetryClient:
) -> None: ) -> None:
config = build_test_vibe_config(enable_telemetry=True) config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config) client = TelemetryClient(config_getter=lambda: config)
tool_call = _make_resolved_tool_call("write_file", {}) tool_call = _make_resolved_tool_call("write_file", {"path": "/tmp/foo.PY"})
client.send_tool_call_finished( client.send_tool_call_finished(
tool_call=tool_call, tool_call=tool_call,
@ -189,6 +240,86 @@ class TestTelemetryClient:
assert telemetry_events[0]["properties"]["nb_files_created"] == 1 assert telemetry_events[0]["properties"]["nb_files_created"] == 1
assert telemetry_events[0]["properties"]["nb_files_modified"] == 0 assert telemetry_events[0]["properties"]["nb_files_modified"] == 0
assert telemetry_events[0]["properties"]["file_extension"] == ".py"
def test_send_tool_call_finished_file_extension_edit(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
tool_call = _make_resolved_tool_call(
"edit", {"file_path": "/tmp/component.tsx"}
)
client.send_tool_call_finished(
tool_call=tool_call,
status="success",
decision=None,
agent_profile_name="default",
model="mistral-large",
result={},
)
assert telemetry_events[0]["properties"]["nb_files_modified"] == 1
assert telemetry_events[0]["properties"]["nb_files_created"] == 0
assert telemetry_events[0]["properties"]["file_extension"] == ".tsx"
def test_send_tool_call_finished_file_extension_read(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
tool_call = _make_resolved_tool_call("read", {"file_path": "/tmp/lib.rs"})
client.send_tool_call_finished(
tool_call=tool_call,
status="success",
decision=None,
agent_profile_name="default",
model="mistral-large",
result={},
)
assert telemetry_events[0]["properties"]["nb_files_created"] == 0
assert telemetry_events[0]["properties"]["nb_files_modified"] == 0
assert telemetry_events[0]["properties"]["file_extension"] == ".rs"
def test_send_tool_call_finished_file_extension_none_when_no_suffix(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
tool_call = _make_resolved_tool_call("write_file", {"path": "/tmp/Makefile"})
client.send_tool_call_finished(
tool_call=tool_call,
status="success",
decision=None,
agent_profile_name="default",
model="mistral-large",
result={},
)
assert telemetry_events[0]["properties"]["file_extension"] is None
def test_send_tool_call_finished_file_extension_none_on_failure(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
tool_call = _make_resolved_tool_call("write_file", {"path": "/tmp/foo.py"})
client.send_tool_call_finished(
tool_call=tool_call,
status="failure",
decision=None,
agent_profile_name="default",
model="mistral-large",
result={},
)
assert telemetry_events[0]["properties"]["nb_files_created"] == 0
assert telemetry_events[0]["properties"]["file_extension"] is None
def test_send_tool_call_finished_decision_none( def test_send_tool_call_finished_decision_none(
self, telemetry_events: list[dict[str, Any]] self, telemetry_events: list[dict[str, Any]]
@ -378,6 +509,40 @@ class TestTelemetryClient:
"nb_session_messages": 4, "nb_session_messages": 4,
} }
def test_send_remote_resume_requested_payload(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
client.send_remote_resume_requested(session_id="remote-123")
assert len(telemetry_events) == 1
assert telemetry_events[0]["event_name"] == "vibe.remote_resume_requested"
assert telemetry_events[0]["properties"] == {"session_id": "remote-123"}
def test_send_teleport_failed_payload_includes_error_details(
self, telemetry_events: list[dict[str, Any]]
) -> None:
config = build_test_vibe_config(enable_telemetry=True)
client = TelemetryClient(config_getter=lambda: config)
client.send_teleport_failed(
stage="workflow_start",
error_class="ServiceTeleportError",
push_required=False,
nb_session_messages=4,
error_details={"failure_kind": "http_error", "http_status_code": 502},
)
assert telemetry_events[0]["event_name"] == "vibe.teleport_failed"
assert telemetry_events[0]["properties"] == {
"stage": "workflow_start",
"error_class": "ServiceTeleportError",
"push_required": False,
"nb_session_messages": 4,
"failure_kind": "http_error",
"http_status_code": 502,
}
def test_send_new_session_payload( def test_send_new_session_payload(
self, telemetry_events: list[dict[str, Any]] self, telemetry_events: list[dict[str, Any]]
) -> None: ) -> None:

View file

@ -174,9 +174,14 @@ async def test_start_raises_for_unsuccessful_response() -> None:
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
nuage = NuageClient("https://chat.example.com", "api-key", client=client) nuage = NuageClient("https://chat.example.com", "api-key", client=client)
with pytest.raises(ServiceTeleportError, match="Nuage start"): with pytest.raises(ServiceTeleportError, match="status 401") as exc_info:
await nuage.start(_request()) await nuage.start(_request())
assert exc_info.value.telemetry_details == {
"failure_kind": "http_error",
"http_status_code": 401,
}
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_start_raises_for_invalid_response() -> None: async def test_start_raises_for_invalid_response() -> None:
@ -185,5 +190,30 @@ async def test_start_raises_for_invalid_response() -> None:
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
nuage = NuageClient("https://chat.example.com", "api-key", client=client) nuage = NuageClient("https://chat.example.com", "api-key", client=client)
with pytest.raises(ServiceTeleportError, match="response was invalid"): with pytest.raises(
ServiceTeleportError, match="response was invalid"
) as exc_info:
await nuage.start(_request()) await nuage.start(_request())
assert exc_info.value.telemetry_details == {
"failure_kind": "invalid_schema",
"http_status_code": 200,
}
@pytest.mark.asyncio
async def test_start_raises_for_invalid_json_response() -> None:
async def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
200, text="not-json", headers={"content-type": "text/plain"}
)
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
nuage = NuageClient("https://chat.example.com", "api-key", client=client)
with pytest.raises(ServiceTeleportError, match="not valid JSON") as exc_info:
await nuage.start(_request())
assert exc_info.value.telemetry_details == {
"failure_kind": "invalid_json",
"http_status_code": 200,
}

View file

@ -87,7 +87,10 @@ class TestTeleportAgentLoopTelemetry:
) -> AsyncGenerator[object, object]: ) -> AsyncGenerator[object, object]:
yield TeleportCheckingGitEvent() yield TeleportCheckingGitEvent()
yield TeleportStartingWorkflowEvent() yield TeleportStartingWorkflowEvent()
raise ServiceTeleportError("Workflow api-key-123 could not be started.") raise ServiceTeleportError(
"Workflow api-key-123 could not be started.",
telemetry_details={"http_status_code": 502},
)
agent_loop.messages.append(LLMMessage(role=Role.user, content="hello")) agent_loop.messages.append(LLMMessage(role=Role.user, content="hello"))
_set_teleport_service(agent_loop, FakeTeleportService()) _set_teleport_service(agent_loop, FakeTeleportService())
@ -103,6 +106,7 @@ class TestTeleportAgentLoopTelemetry:
"error_class": "ServiceTeleportError", "error_class": "ServiceTeleportError",
"push_required": False, "push_required": False,
"nb_session_messages": 1, "nb_session_messages": 1,
"http_status_code": 502,
"session_id": agent_loop.session_id, "session_id": agent_loop.session_id,
} }
assert "api-key-123" not in str(telemetry_events[-1]["properties"]) assert "api-key-123" not in str(telemetry_events[-1]["properties"])

View file

@ -351,6 +351,19 @@ class TestHasAgentsMdFile:
def test_agents_md_filename_constant(self) -> None: def test_agents_md_filename_constant(self) -> None:
assert AGENTS_MD_FILENAME == "AGENTS.md" assert AGENTS_MD_FILENAME == "AGENTS.md"
def test_unreadable_agents_md_does_not_crash(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
denied = (tmp_path / "AGENTS.md").resolve()
def fake_is_file(self: Path) -> bool:
if self.resolve() == denied:
raise PermissionError(13, "Permission denied")
return False
monkeypatch.setattr(Path, "is_file", fake_is_file)
assert has_agents_md_file(tmp_path) is False
class TestFindTrustableFiles: class TestFindTrustableFiles:
def test_returns_empty_for_clean_directory(self, tmp_path: Path) -> None: def test_returns_empty_for_clean_directory(self, tmp_path: Path) -> None:
@ -513,3 +526,24 @@ class TestFindGitRepoAncestor:
) -> None: ) -> None:
monkeypatch.setattr(Path, "home", classmethod(lambda _cls: tmp_path / "nope")) monkeypatch.setattr(Path, "home", classmethod(lambda _cls: tmp_path / "nope"))
assert find_git_repo_ancestor(tmp_path) is None assert find_git_repo_ancestor(tmp_path) is None
def test_unreadable_ancestor_git_head_does_not_crash(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
ancestor = tmp_path / "mnt" / "vast"
(ancestor / ".git").mkdir(parents=True)
cwd = ancestor / "project"
cwd.mkdir()
real_is_file = Path.is_file
denied = (ancestor / ".git" / "HEAD").resolve()
def fake_is_file(self: Path) -> bool:
if self.resolve() == denied:
raise PermissionError(13, "Permission denied")
return real_is_file(self)
monkeypatch.setattr(Path, "is_file", fake_is_file)
monkeypatch.setattr(Path, "home", classmethod(lambda _cls: tmp_path / "home"))
assert find_git_repo_ancestor(cwd) is None

View file

@ -7,6 +7,7 @@ import pytest
from vibe.core.config.layer import LayerImplementationError from vibe.core.config.layer import LayerImplementationError
from vibe.core.config.layers.user import UserConfigLayer from vibe.core.config.layers.user import UserConfigLayer
from vibe.core.config.patch import ConfigPatch from vibe.core.config.patch import ConfigPatch
from vibe.core.config.types import MISSING_CONFIG_FILE_FINGERPRINT
@pytest.mark.asyncio @pytest.mark.asyncio
@ -17,6 +18,9 @@ async def test_reads_toml_file(tmp_working_directory: Path) -> None:
layer = UserConfigLayer(path=path, name="user-toml") layer = UserConfigLayer(path=path, name="user-toml")
data = await layer.load() data = await layer.load()
assert data.model_extra == {"active_model": "mistral-large", "count": 42} assert data.model_extra == {"active_model": "mistral-large", "count": 42}
fingerprint = layer.fingerprint
assert isinstance(fingerprint, str)
assert fingerprint
@pytest.mark.asyncio @pytest.mark.asyncio
@ -37,6 +41,7 @@ async def test_missing_file_returns_empty(tmp_working_directory: Path) -> None:
layer = UserConfigLayer(path=path, name="user-toml") layer = UserConfigLayer(path=path, name="user-toml")
data = await layer.load() data = await layer.load()
assert data.model_extra == {} assert data.model_extra == {}
assert layer.fingerprint == MISSING_CONFIG_FILE_FINGERPRINT
@pytest.mark.asyncio @pytest.mark.asyncio
@ -70,7 +75,7 @@ async def test_invalid_toml_raises(tmp_working_directory: Path) -> None:
path = tmp_working_directory / "bad.toml" path = tmp_working_directory / "bad.toml"
path.write_text("this is not valid = = = toml [[[") path.write_text("this is not valid = = = toml [[[")
layer = UserConfigLayer(path=path, name="user-toml") layer = UserConfigLayer(path=path, name="user-toml")
with pytest.raises(LayerImplementationError, match="_read_config"): with pytest.raises(LayerImplementationError, match="_build_config_snapshot"):
await layer.load() await layer.load()
@ -81,11 +86,23 @@ async def test_force_reload_reads_fresh_data(tmp_working_directory: Path) -> Non
layer = UserConfigLayer(path=path, name="user-toml") layer = UserConfigLayer(path=path, name="user-toml")
data1 = await layer.load() data1 = await layer.load()
fp1 = layer.fingerprint
assert data1.model_extra == {"value": "first"} assert data1.model_extra == {"value": "first"}
assert isinstance(fp1, str)
assert fp1
path.write_text('value = "second"\n') path.write_text('value = "second"\n')
data2 = await layer.load(force=True) data2 = await layer.load(force=True)
fp2 = layer.fingerprint
assert data2.model_extra == {"value": "second"} assert data2.model_extra == {"value": "second"}
assert isinstance(fp2, str)
assert fp2
assert fp1 != fp2
path.unlink()
data3 = await layer.load(force=True)
assert data3.model_extra == {}
assert layer.fingerprint == MISSING_CONFIG_FILE_FINGERPRINT
@pytest.mark.asyncio @pytest.mark.asyncio

View file

@ -106,6 +106,28 @@ class TestReadSafe:
with pytest.raises(FileNotFoundError): with pytest.raises(FileNotFoundError):
read_safe(tmp_path / "nope.txt") read_safe(tmp_path / "nope.txt")
def test_from_subprocess_prefers_oem_over_locale_ansi(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
# \x82 is invalid UTF-8 and decodes differently across single-byte
# encodings: cp1252 (Windows ANSI) -> "" (low-9 quote);
# cp850 (Windows OEM) -> "é". Subprocess output prefers OEM over the
# ANSI locale; file reads (from_subprocess=False) must not.
raw = "café\n".encode("cp850")
monkeypatch.setattr(
io_utils.locale, "getpreferredencoding", lambda _do_setlocale: "cp1252"
)
monkeypatch.setattr(io_utils, "_encoding_from_best_match", lambda _raw: None)
monkeypatch.setattr(io_utils, "_windows_oem_encoding", lambda: "cp850")
from_file = decode_safe(raw)
assert from_file.encoding == "cp1252"
assert from_file.text == raw.decode("cp1252")
from_subprocess = decode_safe(raw, from_subprocess=True)
assert from_subprocess.encoding == "cp850"
assert from_subprocess.text == "café\n"
class TestReadSafeNewlines: class TestReadSafeNewlines:
def test_lf(self, tmp_path: Path) -> None: def test_lf(self, tmp_path: Path) -> None:

View file

@ -244,7 +244,6 @@ def test_get_result_display() -> None:
assert isinstance(display, ToolResultDisplay) assert isinstance(display, ToolResultDisplay)
assert display.success is True assert display.success is True
assert "10 lines" in display.message
assert "foo.py" in display.message assert "foo.py" in display.message
@ -261,7 +260,7 @@ def test_get_result_display_truncated() -> None:
) )
display = Read.get_result_display(event) display = Read.get_result_display(event)
assert "truncated" in display.message assert "truncated" in display.suffix
def test_get_result_display_truncated_via_flag() -> None: def test_get_result_display_truncated_via_flag() -> None:
@ -278,7 +277,7 @@ def test_get_result_display_truncated_via_flag() -> None:
) )
display = Read.get_result_display(event) display = Read.get_result_display(event)
assert "truncated" in display.message assert "truncated" in display.suffix
@pytest.fixture() @pytest.fixture()

View file

@ -85,3 +85,7 @@ def test_spawn_cli_runs_configured_hook_after_turn(
assert invocation["hook_event_name"] == "post_agent_turn" assert invocation["hook_event_name"] == "post_agent_turn"
assert isinstance(invocation["cwd"], str) and invocation["cwd"] assert isinstance(invocation["cwd"], str) and invocation["cwd"]
assert isinstance(invocation["session_id"], str) and invocation["session_id"] assert isinstance(invocation["session_id"], str) and invocation["session_id"]
# New in the discriminated-union payload: top-level sessions have no
# parent. Tool hook invocations in this test never fire (no tool calls
# in the mock response), so we only assert the post_agent_turn shape.
assert invocation.get("parent_session_id") is None

View file

@ -2,6 +2,8 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
import io import io
import json
import os
from pathlib import Path from pathlib import Path
import re import re
import time import time
@ -12,12 +14,14 @@ import pytest
from tests.e2e.common import ( from tests.e2e.common import (
SpawnedVibeProcessFixture, SpawnedVibeProcessFixture,
ansi_tolerant_pattern, ansi_tolerant_pattern,
poll_until,
send_ctrl_c_until_quit_confirmation, send_ctrl_c_until_quit_confirmation,
strip_ansi, strip_ansi,
wait_for_main_screen, wait_for_main_screen,
wait_for_request_count, wait_for_request_count,
) )
from tests.e2e.mock_server import StreamingMockServer from tests.e2e.mock_server import StreamingMockServer
from vibe.core.utils.io import read_safe
def _usage_by_run_factory( def _usage_by_run_factory(
@ -42,6 +46,47 @@ def _usage_by_run_factory(
] ]
def _saved_session_has_usage(
vibe_home: Path, expected_prompt_tokens: int, expected_completion_tokens: int
) -> bool:
session_log_dir = vibe_home / "logs" / "session"
if not session_log_dir.exists():
return False
for metadata_path in session_log_dir.glob("session_*/meta.json"):
try:
metadata = json.loads(read_safe(metadata_path).text)
except (OSError, json.JSONDecodeError):
continue
stats = metadata.get("stats", {})
if not isinstance(stats, dict):
continue
if (
stats.get("session_prompt_tokens") == expected_prompt_tokens
and stats.get("session_completion_tokens") == expected_completion_tokens
):
return True
return False
def _wait_for_saved_session_usage(
expected_prompt_tokens: int, expected_completion_tokens: int
) -> None:
vibe_home = Path(os.environ["VIBE_HOME"])
poll_until(
lambda: _saved_session_has_usage(
vibe_home, expected_prompt_tokens, expected_completion_tokens
),
timeout=10,
message=(
"Timed out waiting for saved session usage "
f"input={expected_prompt_tokens} output={expected_completion_tokens}."
),
)
def _finish_turn( def _finish_turn(
child: pexpect.spawn, child: pexpect.spawn,
captured: io.StringIO, captured: io.StringIO,
@ -103,6 +148,9 @@ def test_resumed_session_prints_only_fresh_token_usage_on_exit(
expected_request_count=1, expected_request_count=1,
request_count_getter=lambda: len(streaming_mock_server.requests), request_count_getter=lambda: len(streaming_mock_server.requests),
) )
_wait_for_saved_session_usage(
expected_prompt_tokens=11, expected_completion_tokens=7
)
send_ctrl_c_until_quit_confirmation(child, captured, timeout=5) send_ctrl_c_until_quit_confirmation(child, captured, timeout=5)
child.expect(pexpect.EOF, timeout=10) child.expect(pexpect.EOF, timeout=10)
@ -130,6 +178,9 @@ def test_resumed_session_prints_only_fresh_token_usage_on_exit(
expected_request_count=2, expected_request_count=2,
request_count_getter=lambda: len(streaming_mock_server.requests), request_count_getter=lambda: len(streaming_mock_server.requests),
) )
_wait_for_saved_session_usage(
expected_prompt_tokens=2, expected_completion_tokens=1
)
send_ctrl_c_until_quit_confirmation(resumed_child, resumed_captured, timeout=5) send_ctrl_c_until_quit_confirmation(resumed_child, resumed_captured, timeout=5)
resumed_child.expect(pexpect.EOF, timeout=10) resumed_child.expect(pexpect.EOF, timeout=10)

View file

@ -5,6 +5,8 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest import pytest
from vibe.core.session.resume_sessions import ( from vibe.core.session.resume_sessions import (
ResumeSessionInfo,
can_delete_resume_session_source,
list_remote_resume_sessions, list_remote_resume_sessions,
short_session_id, short_session_id,
) )
@ -45,6 +47,25 @@ class TestShortSessionId:
assert short_session_id("") == "" assert short_session_id("") == ""
class TestCanDeleteResumeSession:
def test_local_source_can_delete(self) -> None:
assert can_delete_resume_session_source("local") is True
def test_remote_source_cannot_delete(self) -> None:
assert can_delete_resume_session_source("remote") is False
def test_session_info_can_delete_matches_source(self) -> None:
session = ResumeSessionInfo(
session_id="session-a",
source="local",
cwd="/test",
title=None,
end_time=None,
)
assert session.can_delete is True
class TestListRemoteResumeSessions: class TestListRemoteResumeSessions:
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_returns_empty_when_vibe_code_disabled(self) -> None: async def test_returns_empty_when_vibe_code_disabled(self) -> None:

View file

@ -20,6 +20,22 @@ class TestBuiltinSkills:
def test_vibe_skill_has_inline_prompt(self) -> None: def test_vibe_skill_has_inline_prompt(self) -> None:
assert BUILTIN_SKILLS["vibe"].prompt assert BUILTIN_SKILLS["vibe"].prompt
def test_vibe_skill_pins_readme_url_to_running_version(self) -> None:
from vibe import __version__
prompt = BUILTIN_SKILLS["vibe"].prompt
assert "__VIBE_VERSION__" not in prompt
assert (
f"https://github.com/mistralai/mistral-vibe/blob/v{__version__}/README.md"
in prompt
)
def test_vibe_skill_references_user_docs_url(self) -> None:
assert (
"https://docs.mistral.ai/vibe/code/overview"
in BUILTIN_SKILLS["vibe"].prompt
)
def test_discovers_builtin_skills(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_discovers_builtin_skills(self, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr("vibe.core.skills.manager.BUILTIN_SKILLS", BUILTIN_SKILLS) monkeypatch.setattr("vibe.core.skills.manager.BUILTIN_SKILLS", BUILTIN_SKILLS)
config = build_test_vibe_config( config = build_test_vibe_config(

View file

@ -114,15 +114,15 @@
<text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> <text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> </text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> </text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> </text><text class="terminal-r1" x="0" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="0" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="0" y="142" textLength="134.2" clip-path="url(#terminal-line-5)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r2" x="0" y="190.8" textLength="146.4" clip-path="url(#terminal-line-7)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="190.8" textLength="122" clip-path="url(#terminal-line-7)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="190.8" textLength="244" clip-path="url(#terminal-line-7)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="190.8" textLength="256.2" clip-path="url(#terminal-line-7)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="0" y="215.2" textLength="134.2" clip-path="url(#terminal-line-8)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="215.2" textLength="146.4" clip-path="url(#terminal-line-8)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="215.2" textLength="122" clip-path="url(#terminal-line-8)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="215.2" textLength="244" clip-path="url(#terminal-line-8)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="215.2" textLength="256.2" clip-path="url(#terminal-line-8)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="414.8" clip-path="url(#terminal-line-8)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="0" y="239.6" textLength="134.2" clip-path="url(#terminal-line-9)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="239.6" textLength="414.8" clip-path="url(#terminal-line-9)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="0" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">Type&#160;</text><text class="terminal-r3" x="61" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">/help</text><text class="terminal-r1" x="122" y="239.6" textLength="256.2" clip-path="url(#terminal-line-9)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-line-10)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="264" textLength="61" clip-path="url(#terminal-line-10)">Type&#160;</text><text class="terminal-r3" x="231.8" y="264" textLength="61" clip-path="url(#terminal-line-10)">/help</text><text class="terminal-r1" x="292.8" y="264" textLength="256.2" clip-path="url(#terminal-line-10)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r4" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r4" x="48.8" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r4" x="73.2" y="288.4" textLength="85.4" clip-path="url(#terminal-line-11)">6&#160;lines</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="1464" clip-path="url(#terminal-line-14)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────&#160;default&#160;</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="1464" clip-path="url(#terminal-line-14)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────&#160;default&#160;</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Before After
Before After

View file

@ -148,21 +148,21 @@
</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="0" y="190.8" textLength="134.2" clip-path="url(#terminal-line-7)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="134.2" clip-path="url(#terminal-line-8)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="0" y="239.6" textLength="134.2" clip-path="url(#terminal-line-9)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r2" x="0" y="288.4" textLength="146.4" clip-path="url(#terminal-line-11)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="288.4" textLength="122" clip-path="url(#terminal-line-11)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="288.4" textLength="244" clip-path="url(#terminal-line-11)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="288.4" textLength="256.2" clip-path="url(#terminal-line-11)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="312.8" textLength="146.4" clip-path="url(#terminal-line-12)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="312.8" textLength="122" clip-path="url(#terminal-line-12)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="312.8" textLength="244" clip-path="url(#terminal-line-12)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="312.8" textLength="256.2" clip-path="url(#terminal-line-12)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="414.8" clip-path="url(#terminal-line-12)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="337.2" textLength="414.8" clip-path="url(#terminal-line-13)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="61" clip-path="url(#terminal-line-13)">Type&#160;</text><text class="terminal-r3" x="61" y="337.2" textLength="61" clip-path="url(#terminal-line-13)">/help</text><text class="terminal-r1" x="122" y="337.2" textLength="256.2" clip-path="url(#terminal-line-13)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">Type&#160;</text><text class="terminal-r3" x="231.8" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">/help</text><text class="terminal-r1" x="292.8" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r4" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"></text><text class="terminal-r4" x="48.8" y="386" textLength="695.4" clip-path="url(#terminal-line-15)">What&#160;programming&#160;language&#160;are&#160;you&#160;currently&#160;working&#160;with?</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r4" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r4" x="48.8" y="410.4" textLength="695.4" clip-path="url(#terminal-line-16)">What&#160;programming&#160;language&#160;are&#160;you&#160;currently&#160;working&#160;with?</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r4" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r1" x="48.8" y="410.4" textLength="48.8" clip-path="url(#terminal-line-16)">Rust</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r4" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="48.8" y="434.8" textLength="48.8" clip-path="url(#terminal-line-17)">Rust</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r4" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r4" x="48.8" y="434.8" textLength="463.6" clip-path="url(#terminal-line-17)">What&#160;type&#160;of&#160;project&#160;are&#160;you&#160;building?</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r4" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r4" x="48.8" y="459.2" textLength="463.6" clip-path="url(#terminal-line-18)">What&#160;type&#160;of&#160;project&#160;are&#160;you&#160;building?</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r4" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r1" x="48.8" y="459.2" textLength="183" clip-path="url(#terminal-line-18)">Web&#160;Application</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r4" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="48.8" y="483.6" textLength="183" clip-path="url(#terminal-line-19)">Web&#160;Application</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r4" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r4" x="48.8" y="483.6" textLength="402.6" clip-path="url(#terminal-line-19)">What&#160;editor&#160;or&#160;IDE&#160;do&#160;you&#160;prefer?</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r4" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r4" x="48.8" y="508" textLength="402.6" clip-path="url(#terminal-line-20)">What&#160;editor&#160;or&#160;IDE&#160;do&#160;you&#160;prefer?</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r4" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r1" x="48.8" y="508" textLength="183" clip-path="url(#terminal-line-20)">(Other)&#160;VS&#160;Code</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r4" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"></text><text class="terminal-r1" x="48.8" y="532.4" textLength="183" clip-path="url(#terminal-line-21)">(Other)&#160;VS&#160;Code</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r4" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"></text><text class="terminal-r4" x="48.8" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"></text><text class="terminal-r4" x="73.2" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">show&#160;less</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="1464" clip-path="url(#terminal-line-24)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────&#160;default&#160;</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="0" y="605.6" textLength="1464" clip-path="url(#terminal-line-24)">──────────────────────────────────────────────────────────────────────────────────────────────────────────────&#160;default&#160;</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -174,13 +174,13 @@
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -178,13 +178,13 @@
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r2" x="0" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="231.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="292.8" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="61" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="122" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="24.4" y="532.4" textLength="451.4" clip-path="url(#terminal-line-21)">Here&#x27;s&#160;a&#160;very&#160;long&#160;print&#160;instruction:</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="24.4" y="532.4" textLength="451.4" clip-path="url(#terminal-line-21)">Here&#x27;s&#160;a&#160;very&#160;long&#160;print&#160;instruction:</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -177,13 +177,13 @@
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="556.8" textLength="146.4" clip-path="url(#terminal-line-22)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="556.8" textLength="122" clip-path="url(#terminal-line-22)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="556.8" textLength="244" clip-path="url(#terminal-line-22)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="556.8" textLength="256.2" clip-path="url(#terminal-line-22)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r2" x="0" y="556.8" textLength="146.4" clip-path="url(#terminal-line-22)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="556.8" textLength="122" clip-path="url(#terminal-line-22)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="556.8" textLength="244" clip-path="url(#terminal-line-22)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="556.8" textLength="256.2" clip-path="url(#terminal-line-22)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="0" y="581.2" textLength="134.2" clip-path="url(#terminal-line-23)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="581.2" textLength="414.8" clip-path="url(#terminal-line-23)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="0" y="581.2" textLength="414.8" clip-path="url(#terminal-line-23)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">Type&#160;</text><text class="terminal-r3" x="231.8" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">/help</text><text class="terminal-r1" x="292.8" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="0" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">Type&#160;</text><text class="terminal-r3" x="61" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">/help</text><text class="terminal-r1" x="122" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="1220" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="1220" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="48.8" y="654.4" textLength="280.6" clip-path="url(#terminal-line-26)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="48.8" y="654.4" textLength="280.6" clip-path="url(#terminal-line-26)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="48.8" y="678.8" textLength="488" clip-path="url(#terminal-line-27)">Configuration&#160;closed&#160;(no&#160;changes&#160;saved).</text><text class="terminal-r1" x="1220" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="48.8" y="678.8" textLength="488" clip-path="url(#terminal-line-27)">Configuration&#160;closed&#160;(no&#160;changes&#160;saved).</text><text class="terminal-r1" x="1220" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -177,13 +177,13 @@
</text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -177,13 +177,13 @@
</text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -177,13 +177,13 @@
</text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="48.8" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">Configuration&#160;opened...</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -180,13 +180,13 @@
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r2" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r4" x="719.8" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r4" x="719.8" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r4" x="719.8" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="744.2" y="678.8" textLength="414.8" clip-path="url(#terminal-line-27)">/test/skills/broken-skill/SKILL.md</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r4" x="719.8" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="744.2" y="678.8" textLength="414.8" clip-path="url(#terminal-line-27)">/test/skills/broken-skill/SKILL.md</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r4" x="719.8" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="744.2" y="703.2" textLength="634.4" clip-path="url(#terminal-line-28)">Failed&#160;to&#160;load:&#160;missing&#160;required&#160;field&#160;&#x27;description&#x27;</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r4" x="719.8" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="744.2" y="703.2" textLength="634.4" clip-path="url(#terminal-line-28)">Failed&#160;to&#160;load:&#160;missing&#160;required&#160;field&#160;&#x27;description&#x27;</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r4" x="719.8" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r4" x="719.8" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -180,13 +180,13 @@
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r2" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r4" x="719.8" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r4" x="719.8" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r4" x="719.8" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="744.2" y="678.8" textLength="341.6" clip-path="url(#terminal-line-27)">/test/hooks/broken-hook.toml</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r4" x="719.8" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="744.2" y="678.8" textLength="341.6" clip-path="url(#terminal-line-27)">/test/hooks/broken-hook.toml</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r4" x="719.8" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="744.2" y="703.2" textLength="439.2" clip-path="url(#terminal-line-28)">Failed&#160;to&#160;parse:&#160;invalid&#160;TOML&#160;syntax</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r4" x="719.8" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="744.2" y="703.2" textLength="439.2" clip-path="url(#terminal-line-28)">Failed&#160;to&#160;parse:&#160;invalid&#160;TOML&#160;syntax</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r4" x="719.8" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r4" x="719.8" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -175,13 +175,13 @@
</text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r4" x="48.8" y="556.8" textLength="414.8" clip-path="url(#terminal-line-22)">Your&#160;Data&#160;Helps&#160;Improve&#160;Mistral&#160;AI</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r4" x="48.8" y="556.8" textLength="414.8" clip-path="url(#terminal-line-22)">Your&#160;Data&#160;Helps&#160;Improve&#160;Mistral&#160;AI</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"></text><text class="terminal-r1" x="48.8" y="581.2" textLength="1159" clip-path="url(#terminal-line-23)">At&#160;Mistral&#160;AI,&#160;we&#x27;re&#160;committed&#160;to&#160;delivering&#160;the&#160;best&#160;possible&#160;experience.&#160;When&#160;you&#160;use&#160;Mistral</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"></text><text class="terminal-r1" x="48.8" y="581.2" textLength="1159" clip-path="url(#terminal-line-23)">At&#160;Mistral&#160;AI,&#160;we&#x27;re&#160;committed&#160;to&#160;delivering&#160;the&#160;best&#160;possible&#160;experience.&#160;When&#160;you&#160;use&#160;Mistral</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -179,13 +179,13 @@
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r2" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -182,13 +182,13 @@
</text><text class="terminal-r1" x="878.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="902.8" y="434.8" textLength="268.4" clip-path="url(#terminal-line-17)">connection&#160;attempt&#160;1/3</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="878.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="902.8" y="434.8" textLength="268.4" clip-path="url(#terminal-line-17)">connection&#160;attempt&#160;1/3</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="878.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r2" x="902.8" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">2026-02-21&#160;10:28:51</text><text class="terminal-r4" x="1146.8" y="459.2" textLength="97.6" clip-path="url(#terminal-line-18)">WARNING&#160;</text><text class="terminal-r1" x="1244.4" y="459.2" textLength="207.4" clip-path="url(#terminal-line-18)">&#160;Rate&#160;limit&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="878.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r2" x="902.8" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">2026-02-21&#160;10:28:51</text><text class="terminal-r4" x="1146.8" y="459.2" textLength="97.6" clip-path="url(#terminal-line-18)">WARNING&#160;</text><text class="terminal-r1" x="1244.4" y="459.2" textLength="207.4" clip-path="url(#terminal-line-18)">&#160;Rate&#160;limit&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="878.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="902.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">approaching&#160;for&#160;client&#160;api-key-abc</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="878.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="902.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">approaching&#160;for&#160;client&#160;api-key-abc</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="878.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r2" x="902.8" y="508" textLength="231.8" clip-path="url(#terminal-line-20)">2026-02-21&#160;10:28:52</text><text class="terminal-r3" x="1146.8" y="508" textLength="97.6" clip-path="url(#terminal-line-20)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1244.4" y="508" textLength="207.4" clip-path="url(#terminal-line-20)">&#160;Health&#160;check&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="780.8" y="508" textLength="109.8" clip-path="url(#terminal-line-20)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r2" x="902.8" y="508" textLength="231.8" clip-path="url(#terminal-line-20)">2026-02-21&#160;10:28:52</text><text class="terminal-r3" x="1146.8" y="508" textLength="97.6" clip-path="url(#terminal-line-20)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1244.4" y="508" textLength="207.4" clip-path="url(#terminal-line-20)">&#160;Health&#160;check&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="878.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"></text><text class="terminal-r1" x="902.8" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">passed</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="780.8" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="902.8" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">passed</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="878.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r2" x="902.8" y="556.8" textLength="231.8" clip-path="url(#terminal-line-22)">2026-02-21&#160;10:28:53</text><text class="terminal-r6" x="1146.8" y="556.8" textLength="97.6" clip-path="url(#terminal-line-22)">CRITICAL</text><text class="terminal-r1" x="1244.4" y="556.8" textLength="207.4" clip-path="url(#terminal-line-22)">&#160;Out&#160;of&#160;memory&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="780.8" y="556.8" textLength="109.8" clip-path="url(#terminal-line-22)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r2" x="902.8" y="556.8" textLength="231.8" clip-path="url(#terminal-line-22)">2026-02-21&#160;10:28:53</text><text class="terminal-r6" x="1146.8" y="556.8" textLength="97.6" clip-path="url(#terminal-line-22)">CRITICAL</text><text class="terminal-r1" x="1244.4" y="556.8" textLength="207.4" clip-path="url(#terminal-line-22)">&#160;Out&#160;of&#160;memory&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="878.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"></text><text class="terminal-r1" x="902.8" y="581.2" textLength="170.8" clip-path="url(#terminal-line-23)">error&#160;detected</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="780.8" y="581.2" textLength="109.8" clip-path="url(#terminal-line-23)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="902.8" y="581.2" textLength="170.8" clip-path="url(#terminal-line-23)">error&#160;detected</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r7" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="195.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription</text><text class="terminal-r1" x="878.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"></text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r7" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="780.8" y="605.6" textLength="109.8" clip-path="url(#terminal-line-24)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="878.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="780.8" y="630" textLength="109.8" clip-path="url(#terminal-line-25)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="878.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="780.8" y="654.4" textLength="109.8" clip-path="url(#terminal-line-26)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="878.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="878.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="878.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="878.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r1" x="878.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r1" x="878.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

@ -181,13 +181,13 @@
</text><text class="terminal-r1" x="878.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="902.8" y="434.8" textLength="268.4" clip-path="url(#terminal-line-17)">connection&#160;attempt&#160;1/3</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="878.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="902.8" y="434.8" textLength="268.4" clip-path="url(#terminal-line-17)">connection&#160;attempt&#160;1/3</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="878.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r2" x="902.8" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">2026-02-21&#160;10:28:51</text><text class="terminal-r4" x="1146.8" y="459.2" textLength="97.6" clip-path="url(#terminal-line-18)">WARNING&#160;</text><text class="terminal-r1" x="1244.4" y="459.2" textLength="207.4" clip-path="url(#terminal-line-18)">&#160;Rate&#160;limit&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="878.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r2" x="902.8" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">2026-02-21&#160;10:28:51</text><text class="terminal-r4" x="1146.8" y="459.2" textLength="97.6" clip-path="url(#terminal-line-18)">WARNING&#160;</text><text class="terminal-r1" x="1244.4" y="459.2" textLength="207.4" clip-path="url(#terminal-line-18)">&#160;Rate&#160;limit&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="878.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="902.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">approaching&#160;for&#160;client&#160;api-key-abc</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="878.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="902.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">approaching&#160;for&#160;client&#160;api-key-abc</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="878.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r2" x="902.8" y="508" textLength="231.8" clip-path="url(#terminal-line-20)">2026-02-21&#160;10:28:52</text><text class="terminal-r3" x="1146.8" y="508" textLength="97.6" clip-path="url(#terminal-line-20)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1244.4" y="508" textLength="207.4" clip-path="url(#terminal-line-20)">&#160;Health&#160;check&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="780.8" y="508" textLength="109.8" clip-path="url(#terminal-line-20)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r2" x="902.8" y="508" textLength="231.8" clip-path="url(#terminal-line-20)">2026-02-21&#160;10:28:52</text><text class="terminal-r3" x="1146.8" y="508" textLength="97.6" clip-path="url(#terminal-line-20)">INFO&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1244.4" y="508" textLength="207.4" clip-path="url(#terminal-line-20)">&#160;Health&#160;check&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="878.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"></text><text class="terminal-r1" x="902.8" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">passed</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="780.8" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="902.8" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">passed</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="878.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"></text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="780.8" y="556.8" textLength="109.8" clip-path="url(#terminal-line-22)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="878.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"></text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="780.8" y="581.2" textLength="109.8" clip-path="url(#terminal-line-23)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r6" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="195.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription</text><text class="terminal-r1" x="878.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"></text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r6" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="780.8" y="605.6" textLength="109.8" clip-path="url(#terminal-line-24)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="878.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="780.8" y="630" textLength="109.8" clip-path="url(#terminal-line-25)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="878.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"></text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="780.8" y="654.4" textLength="109.8" clip-path="url(#terminal-line-26)">&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="878.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="878.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"></text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="878.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="878.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"></text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r1" x="878.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r1" x="878.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"></text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

View file

@ -178,13 +178,13 @@
<rect fill="#4b4e55" x="1427.4" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1427.4" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1427.4" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/> <rect fill="#4b4e55" x="1427.4" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1427.4" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1427.4" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1427.4" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/>
<g class="terminal-matrix"> <g class="terminal-matrix">
<text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> <text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> </text><text class="terminal-r1" x="0" y="44.4" textLength="134.2" clip-path="url(#terminal-line-1)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> </text><text class="terminal-r1" x="0" y="68.8" textLength="134.2" clip-path="url(#terminal-line-2)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> </text><text class="terminal-r1" x="0" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="0" y="142" textLength="134.2" clip-path="url(#terminal-line-5)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="142" textLength="146.4" clip-path="url(#terminal-line-5)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="142" textLength="122" clip-path="url(#terminal-line-5)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="142" textLength="244" clip-path="url(#terminal-line-5)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="142" textLength="256.2" clip-path="url(#terminal-line-5)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r2" x="0" y="142" textLength="146.4" clip-path="url(#terminal-line-5)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="142" textLength="122" clip-path="url(#terminal-line-5)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="142" textLength="244" clip-path="url(#terminal-line-5)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="142" textLength="256.2" clip-path="url(#terminal-line-5)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="0" y="166.4" textLength="134.2" clip-path="url(#terminal-line-6)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="166.4" textLength="414.8" clip-path="url(#terminal-line-6)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="0" y="166.4" textLength="414.8" clip-path="url(#terminal-line-6)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="0" y="190.8" textLength="134.2" clip-path="url(#terminal-line-7)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="190.8" textLength="61" clip-path="url(#terminal-line-7)">Type&#160;</text><text class="terminal-r3" x="231.8" y="190.8" textLength="61" clip-path="url(#terminal-line-7)">/help</text><text class="terminal-r1" x="292.8" y="190.8" textLength="256.2" clip-path="url(#terminal-line-7)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="0" y="190.8" textLength="61" clip-path="url(#terminal-line-7)">Type&#160;</text><text class="terminal-r3" x="61" y="190.8" textLength="61" clip-path="url(#terminal-line-7)">/help</text><text class="terminal-r1" x="122" y="190.8" textLength="256.2" clip-path="url(#terminal-line-7)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Before After
Before After

View file

@ -35,9 +35,9 @@
.terminal-r1 { fill: #c5c8c6 } .terminal-r1 { fill: #c5c8c6 }
.terminal-r2 { fill: #ff8205;font-weight: bold } .terminal-r2 { fill: #ff8205;font-weight: bold }
.terminal-r3 { fill: #68a0b3 } .terminal-r3 { fill: #68a0b3 }
.terminal-r4 { fill: #d0b344;font-weight: bold } .terminal-r4 { fill: #292929 }
.terminal-r5 { fill: #d0b344 } .terminal-r5 { fill: #d0b344;font-weight: bold }
.terminal-r6 { fill: #292929 } .terminal-r6 { fill: #d0b344 }
.terminal-r7 { fill: #98a84b;font-weight: bold } .terminal-r7 { fill: #98a84b;font-weight: bold }
.terminal-r8 { fill: #cc555a } .terminal-r8 { fill: #cc555a }
.terminal-r9 { fill: #868887 } .terminal-r9 { fill: #868887 }
@ -144,26 +144,26 @@
</g> </g>
<g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)"> <g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)">
<rect fill="#4b4e55" x="1183.4" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1183.4" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/> <rect fill="#4b4e55" x="1207.8" y="1.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1207.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1207.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1207.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1207.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1207.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1183.4" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#608ab1" x="1183.4" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/>
<g class="terminal-matrix"> <g class="terminal-matrix">
<text class="terminal-r1" x="1220" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> <text class="terminal-r1" x="0" y="20" textLength="134.2" clip-path="url(#terminal-line-0)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
</text><text class="terminal-r1" x="1220" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> </text><text class="terminal-r1" x="1220" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
</text><text class="terminal-r1" x="0" y="68.8" textLength="134.2" clip-path="url(#terminal-line-2)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="68.8" textLength="146.4" clip-path="url(#terminal-line-2)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="68.8" textLength="122" clip-path="url(#terminal-line-2)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="68.8" textLength="244" clip-path="url(#terminal-line-2)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="68.8" textLength="256.2" clip-path="url(#terminal-line-2)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> </text><text class="terminal-r2" x="0" y="68.8" textLength="146.4" clip-path="url(#terminal-line-2)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="68.8" textLength="122" clip-path="url(#terminal-line-2)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="68.8" textLength="244" clip-path="url(#terminal-line-2)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="68.8" textLength="256.2" clip-path="url(#terminal-line-2)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
</text><text class="terminal-r1" x="0" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="93.2" textLength="414.8" clip-path="url(#terminal-line-3)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> </text><text class="terminal-r1" x="0" y="93.2" textLength="414.8" clip-path="url(#terminal-line-3)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
</text><text class="terminal-r1" x="0" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="117.6" textLength="61" clip-path="url(#terminal-line-4)">Type&#160;</text><text class="terminal-r3" x="231.8" y="117.6" textLength="61" clip-path="url(#terminal-line-4)">/help</text><text class="terminal-r1" x="292.8" y="117.6" textLength="256.2" clip-path="url(#terminal-line-4)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="0" y="117.6" textLength="61" clip-path="url(#terminal-line-4)">Type&#160;</text><text class="terminal-r3" x="61" y="117.6" textLength="61" clip-path="url(#terminal-line-4)">/help</text><text class="terminal-r1" x="122" y="117.6" textLength="256.2" clip-path="url(#terminal-line-4)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="1220" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="1220" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1220" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="1220" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="1220" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="1220" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="0" y="215.2" textLength="1220" clip-path="url(#terminal-line-8)">┌──────────────────────────────────────────────────────────────────────────────────────────────────┐</text><text class="terminal-r1" x="1220" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="1220" clip-path="url(#terminal-line-8)">┌──────────────────────────────────────────────────────────────────────────────────────────────────┐</text><text class="terminal-r1" x="1220" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"></text><text class="terminal-r4" x="24.4" y="239.6" textLength="414.8" clip-path="url(#terminal-line-9)">Permission&#160;for&#160;the&#160;write_file&#160;tool</text><text class="terminal-r1" x="1207.8" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"></text><text class="terminal-r1" x="1220" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"></text><text class="terminal-r5" x="24.4" y="239.6" textLength="414.8" clip-path="url(#terminal-line-9)">Permission&#160;for&#160;the&#160;write_file&#160;tool</text><text class="terminal-r1" x="1207.8" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"></text><text class="terminal-r1" x="1220" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="0" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"></text><text class="terminal-r1" x="24.4" y="264" textLength="97.6" clip-path="url(#terminal-line-10)">line_093</text><text class="terminal-r1" x="134.2" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">=</text><text class="terminal-r5" x="158.6" y="264" textLength="36.6" clip-path="url(#terminal-line-10)">651</text><text class="terminal-r1" x="1207.8" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"></text><text class="terminal-r1" x="1220" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="0" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"></text><text class="terminal-r1" x="24.4" y="264" textLength="97.6" clip-path="url(#terminal-line-10)">line_093</text><text class="terminal-r1" x="134.2" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">=</text><text class="terminal-r6" x="158.6" y="264" textLength="36.6" clip-path="url(#terminal-line-10)">651</text><text class="terminal-r1" x="1207.8" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"></text><text class="terminal-r1" x="1220" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r1" x="24.4" y="288.4" textLength="97.6" clip-path="url(#terminal-line-11)">line_094</text><text class="terminal-r1" x="134.2" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">=</text><text class="terminal-r5" x="158.6" y="288.4" textLength="36.6" clip-path="url(#terminal-line-11)">658</text><text class="terminal-r1" x="1207.8" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r1" x="24.4" y="288.4" textLength="97.6" clip-path="url(#terminal-line-11)">line_094</text><text class="terminal-r1" x="134.2" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">=</text><text class="terminal-r6" x="158.6" y="288.4" textLength="36.6" clip-path="url(#terminal-line-11)">658</text><text class="terminal-r1" x="1207.8" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"></text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"></text><text class="terminal-r1" x="24.4" y="312.8" textLength="97.6" clip-path="url(#terminal-line-12)">line_095</text><text class="terminal-r1" x="134.2" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">=</text><text class="terminal-r5" x="158.6" y="312.8" textLength="36.6" clip-path="url(#terminal-line-12)">665</text><text class="terminal-r1" x="1207.8" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"></text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"></text><text class="terminal-r1" x="24.4" y="312.8" textLength="97.6" clip-path="url(#terminal-line-12)">line_095</text><text class="terminal-r1" x="134.2" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">=</text><text class="terminal-r6" x="158.6" y="312.8" textLength="36.6" clip-path="url(#terminal-line-12)">665</text><text class="terminal-r1" x="1207.8" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"></text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"></text><text class="terminal-r1" x="24.4" y="337.2" textLength="97.6" clip-path="url(#terminal-line-13)">line_096</text><text class="terminal-r1" x="134.2" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">=</text><text class="terminal-r5" x="158.6" y="337.2" textLength="36.6" clip-path="url(#terminal-line-13)">672</text><text class="terminal-r1" x="1207.8" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"></text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"></text><text class="terminal-r1" x="24.4" y="337.2" textLength="97.6" clip-path="url(#terminal-line-13)">line_096</text><text class="terminal-r1" x="134.2" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">=</text><text class="terminal-r6" x="158.6" y="337.2" textLength="36.6" clip-path="url(#terminal-line-13)">672</text><text class="terminal-r1" x="1207.8" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"></text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"></text><text class="terminal-r1" x="24.4" y="361.6" textLength="97.6" clip-path="url(#terminal-line-14)">line_097</text><text class="terminal-r1" x="134.2" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">=</text><text class="terminal-r5" x="158.6" y="361.6" textLength="36.6" clip-path="url(#terminal-line-14)">679</text><text class="terminal-r1" x="1207.8" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"></text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"></text><text class="terminal-r1" x="24.4" y="361.6" textLength="97.6" clip-path="url(#terminal-line-14)">line_097</text><text class="terminal-r1" x="134.2" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">=</text><text class="terminal-r6" x="158.6" y="361.6" textLength="36.6" clip-path="url(#terminal-line-14)">679</text><text class="terminal-r1" x="1207.8" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"></text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="0" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"></text><text class="terminal-r1" x="24.4" y="386" textLength="97.6" clip-path="url(#terminal-line-15)">line_098</text><text class="terminal-r1" x="134.2" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">=</text><text class="terminal-r5" x="158.6" y="386" textLength="36.6" clip-path="url(#terminal-line-15)">686</text><text class="terminal-r1" x="1207.8" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"></text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"></text><text class="terminal-r1" x="24.4" y="386" textLength="97.6" clip-path="url(#terminal-line-15)">line_098</text><text class="terminal-r1" x="134.2" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">=</text><text class="terminal-r6" x="158.6" y="386" textLength="36.6" clip-path="url(#terminal-line-15)">686</text><text class="terminal-r1" x="1207.8" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"></text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r1" x="24.4" y="410.4" textLength="97.6" clip-path="url(#terminal-line-16)">line_099</text><text class="terminal-r1" x="134.2" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">=</text><text class="terminal-r5" x="158.6" y="410.4" textLength="36.6" clip-path="url(#terminal-line-16)">693</text><text class="terminal-r1" x="1207.8" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r1" x="24.4" y="410.4" textLength="97.6" clip-path="url(#terminal-line-16)">line_099</text><text class="terminal-r1" x="134.2" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">=</text><text class="terminal-r6" x="158.6" y="410.4" textLength="36.6" clip-path="url(#terminal-line-16)">693</text><text class="terminal-r1" x="1207.8" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"></text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="24.4" y="434.8" textLength="97.6" clip-path="url(#terminal-line-17)">line_100</text><text class="terminal-r1" x="134.2" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">=</text><text class="terminal-r5" x="158.6" y="434.8" textLength="36.6" clip-path="url(#terminal-line-17)">700</text><text class="terminal-r1" x="1207.8" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="24.4" y="434.8" textLength="97.6" clip-path="url(#terminal-line-17)">line_100</text><text class="terminal-r1" x="134.2" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">=</text><text class="terminal-r6" x="158.6" y="434.8" textLength="36.6" clip-path="url(#terminal-line-17)">700</text><text class="terminal-r1" x="1207.8" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"></text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r1" x="1207.8" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r1" x="1207.8" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"></text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r7" x="24.4" y="483.6" textLength="183" clip-path="url(#terminal-line-19)">&#160;1.&#160;Allow&#160;once</text><text class="terminal-r1" x="1207.8" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r7" x="24.4" y="483.6" textLength="183" clip-path="url(#terminal-line-19)">&#160;1.&#160;Allow&#160;once</text><text class="terminal-r1" x="1207.8" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"></text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r1" x="1207.8" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r1" x="1207.8" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"></text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

View file

@ -148,13 +148,13 @@
<text class="terminal-r1" x="1220" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> <text class="terminal-r1" x="1220" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">
</text><text class="terminal-r1" x="1220" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> </text><text class="terminal-r1" x="1220" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">
</text><text class="terminal-r1" x="1220" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> </text><text class="terminal-r1" x="1220" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">
</text><text class="terminal-r1" x="1220" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> </text><text class="terminal-r1" x="0" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
</text><text class="terminal-r1" x="1220" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="0" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="1220" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="0" y="142" textLength="134.2" clip-path="url(#terminal-line-5)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1220" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="1220" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="0" y="190.8" textLength="134.2" clip-path="url(#terminal-line-7)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="190.8" textLength="146.4" clip-path="url(#terminal-line-7)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="190.8" textLength="122" clip-path="url(#terminal-line-7)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="190.8" textLength="244" clip-path="url(#terminal-line-7)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="190.8" textLength="256.2" clip-path="url(#terminal-line-7)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r2" x="0" y="190.8" textLength="146.4" clip-path="url(#terminal-line-7)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="190.8" textLength="122" clip-path="url(#terminal-line-7)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="190.8" textLength="244" clip-path="url(#terminal-line-7)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="190.8" textLength="256.2" clip-path="url(#terminal-line-7)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="0" y="215.2" textLength="134.2" clip-path="url(#terminal-line-8)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="215.2" textLength="414.8" clip-path="url(#terminal-line-8)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="414.8" clip-path="url(#terminal-line-8)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="0" y="239.6" textLength="134.2" clip-path="url(#terminal-line-9)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">Type&#160;</text><text class="terminal-r3" x="231.8" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">/help</text><text class="terminal-r1" x="292.8" y="239.6" textLength="256.2" clip-path="url(#terminal-line-9)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="0" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">Type&#160;</text><text class="terminal-r3" x="61" y="239.6" textLength="61" clip-path="url(#terminal-line-9)">/help</text><text class="terminal-r1" x="122" y="239.6" textLength="256.2" clip-path="url(#terminal-line-9)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1220" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1220" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1220" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -37,7 +37,7 @@
.terminal-r3 { fill: #68a0b3 } .terminal-r3 { fill: #68a0b3 }
.terminal-r4 { fill: #c5c8c6;font-weight: bold } .terminal-r4 { fill: #c5c8c6;font-weight: bold }
.terminal-r5 { fill: #868887 } .terminal-r5 { fill: #868887 }
.terminal-r6 { fill: #6b753d } .terminal-r6 { fill: #98a84b }
</style> </style>
<defs> <defs>
@ -173,20 +173,20 @@
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r2" x="0" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="231.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="292.8" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="61" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="122" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r2" x="0" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">&gt;&#160;</text><text class="terminal-r4" x="24.4" y="556.8" textLength="231.8" clip-path="url(#terminal-line-22)">What&#160;is&#160;the&#160;answer?</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r2" x="0" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">&gt;&#160;</text><text class="terminal-r4" x="24.4" y="556.8" textLength="231.8" clip-path="url(#terminal-line-22)">What&#160;is&#160;the&#160;answer?</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r5" x="0" y="581.2" textLength="1464" clip-path="url(#terminal-line-23)">────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r5" x="0" y="581.2" textLength="1464" clip-path="url(#terminal-line-23)">────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r6" x="0" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r5" x="24.4" y="630" textLength="85.4" clip-path="url(#terminal-line-25)">Thought</text><text class="terminal-r5" x="122" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r6" x="0" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r5" x="24.4" y="630" textLength="85.4" clip-path="url(#terminal-line-25)">Thought</text><text class="terminal-r5" x="122" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"></text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="24.4" y="678.8" textLength="207.4" clip-path="url(#terminal-line-27)">The&#160;answer&#160;is&#160;42.</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="24.4" y="678.8" textLength="207.4" clip-path="url(#terminal-line-27)">The&#160;answer&#160;is&#160;42.</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -175,13 +175,13 @@
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="414.8" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -179,13 +179,13 @@
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r2" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="244" clip-path="url(#terminal-line-24)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="414.8" clip-path="url(#terminal-line-25)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -173,13 +173,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -173,13 +173,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -175,13 +175,13 @@
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="0" y="288.4" textLength="134.2" clip-path="url(#terminal-line-11)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="386" textLength="146.4" clip-path="url(#terminal-line-15)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="386" textLength="122" clip-path="url(#terminal-line-15)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="386" textLength="244" clip-path="url(#terminal-line-15)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="386" textLength="256.2" clip-path="url(#terminal-line-15)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r2" x="0" y="386" textLength="146.4" clip-path="url(#terminal-line-15)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="386" textLength="122" clip-path="url(#terminal-line-15)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="386" textLength="244" clip-path="url(#terminal-line-15)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="386" textLength="256.2" clip-path="url(#terminal-line-15)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="410.4" textLength="622.2" clip-path="url(#terminal-line-16)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="622.2" clip-path="url(#terminal-line-16)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="434.8" textLength="61" clip-path="url(#terminal-line-17)">Type&#160;</text><text class="terminal-r3" x="231.8" y="434.8" textLength="61" clip-path="url(#terminal-line-17)">/help</text><text class="terminal-r1" x="292.8" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="61" clip-path="url(#terminal-line-17)">Type&#160;</text><text class="terminal-r3" x="61" y="434.8" textLength="61" clip-path="url(#terminal-line-17)">/help</text><text class="terminal-r1" x="122" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After

View file

@ -169,13 +169,13 @@
</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="134.2" clip-path="url(#terminal-line-8)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="0" y="239.6" textLength="134.2" clip-path="url(#terminal-line-9)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-line-10)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="312.8" textLength="146.4" clip-path="url(#terminal-line-12)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="312.8" textLength="122" clip-path="url(#terminal-line-12)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="312.8" textLength="244" clip-path="url(#terminal-line-12)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="312.8" textLength="256.2" clip-path="url(#terminal-line-12)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r2" x="0" y="312.8" textLength="146.4" clip-path="url(#terminal-line-12)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="312.8" textLength="122" clip-path="url(#terminal-line-12)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="312.8" textLength="244" clip-path="url(#terminal-line-12)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="312.8" textLength="256.2" clip-path="url(#terminal-line-12)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="337.2" textLength="622.2" clip-path="url(#terminal-line-13)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="622.2" clip-path="url(#terminal-line-13)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">Type&#160;</text><text class="terminal-r3" x="231.8" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">/help</text><text class="terminal-r1" x="292.8" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">Type&#160;</text><text class="terminal-r3" x="61" y="361.6" textLength="61" clip-path="url(#terminal-line-14)">/help</text><text class="terminal-r1" x="122" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -167,13 +167,13 @@
</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> </text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">
</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> </text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">
</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> </text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">
</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> </text><text class="terminal-r1" x="0" y="166.4" textLength="134.2" clip-path="url(#terminal-line-6)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="0" y="190.8" textLength="134.2" clip-path="url(#terminal-line-7)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="0" y="215.2" textLength="134.2" clip-path="url(#terminal-line-8)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-line-10)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="264" textLength="146.4" clip-path="url(#terminal-line-10)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="264" textLength="122" clip-path="url(#terminal-line-10)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="264" textLength="244" clip-path="url(#terminal-line-10)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="264" textLength="256.2" clip-path="url(#terminal-line-10)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r2" x="0" y="264" textLength="146.4" clip-path="url(#terminal-line-10)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="264" textLength="122" clip-path="url(#terminal-line-10)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="264" textLength="244" clip-path="url(#terminal-line-10)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="264" textLength="256.2" clip-path="url(#terminal-line-10)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="0" y="288.4" textLength="134.2" clip-path="url(#terminal-line-11)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="288.4" textLength="622.2" clip-path="url(#terminal-line-11)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="0" y="288.4" textLength="622.2" clip-path="url(#terminal-line-11)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="312.8" textLength="61" clip-path="url(#terminal-line-12)">Type&#160;</text><text class="terminal-r3" x="231.8" y="312.8" textLength="61" clip-path="url(#terminal-line-12)">/help</text><text class="terminal-r1" x="292.8" y="312.8" textLength="256.2" clip-path="url(#terminal-line-12)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="61" clip-path="url(#terminal-line-12)">Type&#160;</text><text class="terminal-r3" x="61" y="312.8" textLength="61" clip-path="url(#terminal-line-12)">/help</text><text class="terminal-r1" x="122" y="312.8" textLength="256.2" clip-path="url(#terminal-line-12)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -175,13 +175,13 @@
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r2" x="0" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;3&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="414.8" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;3&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="231.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="292.8" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="61" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="122" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -174,13 +174,13 @@
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-line-10)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="0" y="288.4" textLength="134.2" clip-path="url(#terminal-line-11)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="361.6" textLength="146.4" clip-path="url(#terminal-line-14)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="361.6" textLength="244" clip-path="url(#terminal-line-14)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r2" x="0" y="361.6" textLength="146.4" clip-path="url(#terminal-line-14)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="361.6" textLength="244" clip-path="url(#terminal-line-14)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="386" textLength="585.6" clip-path="url(#terminal-line-15)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="585.6" clip-path="url(#terminal-line-15)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">Type&#160;</text><text class="terminal-r3" x="231.8" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">/help</text><text class="terminal-r1" x="292.8" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">Type&#160;</text><text class="terminal-r3" x="61" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">/help</text><text class="terminal-r1" x="122" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -178,13 +178,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="597.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="597.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r2" x="0" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="434.8" textLength="622.2" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="622.2" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="231.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="292.8" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="61" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="122" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r2" x="0" y="459.2" textLength="146.4" clip-path="url(#terminal-line-18)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="459.2" textLength="122" clip-path="url(#terminal-line-18)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="459.2" textLength="244" clip-path="url(#terminal-line-18)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="483.6" textLength="585.6" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="585.6" clip-path="url(#terminal-line-19)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="231.8" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="292.8" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="61" clip-path="url(#terminal-line-20)">Type&#160;</text><text class="terminal-r3" x="61" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="122" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r2" x="0" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="292.8" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="61" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="122" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -174,13 +174,13 @@
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r2" x="0" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="292.8" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="61" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="122" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r2" x="0" y="410.4" textLength="146.4" clip-path="url(#terminal-line-16)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="410.4" textLength="122" clip-path="url(#terminal-line-16)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="410.4" textLength="244" clip-path="url(#terminal-line-16)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="434.8" textLength="622.2" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="622.2" clip-path="url(#terminal-line-17)">1&#160;model&#160;·&#160;1/3&#160;connectors&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="231.8" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="292.8" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">Type&#160;</text><text class="terminal-r3" x="61" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="122" y="459.2" textLength="256.2" clip-path="url(#terminal-line-18)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -175,13 +175,13 @@
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="508" textLength="146.4" clip-path="url(#terminal-line-20)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="508" textLength="122" clip-path="url(#terminal-line-20)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="508" textLength="244" clip-path="url(#terminal-line-20)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r2" x="0" y="508" textLength="146.4" clip-path="url(#terminal-line-20)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="508" textLength="122" clip-path="url(#terminal-line-20)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="508" textLength="244" clip-path="url(#terminal-line-20)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="532.4" textLength="414.8" clip-path="url(#terminal-line-21)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="414.8" clip-path="url(#terminal-line-21)">1&#160;model&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="556.8" textLength="61" clip-path="url(#terminal-line-22)">Type&#160;</text><text class="terminal-r3" x="231.8" y="556.8" textLength="61" clip-path="url(#terminal-line-22)">/help</text><text class="terminal-r1" x="292.8" y="556.8" textLength="256.2" clip-path="url(#terminal-line-22)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="61" clip-path="url(#terminal-line-22)">Type&#160;</text><text class="terminal-r3" x="61" y="556.8" textLength="61" clip-path="url(#terminal-line-22)">/help</text><text class="terminal-r1" x="122" y="556.8" textLength="256.2" clip-path="url(#terminal-line-22)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="0" y="337.2" textLength="134.2" clip-path="url(#terminal-line-13)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r2" x="0" y="434.8" textLength="146.4" clip-path="url(#terminal-line-17)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="434.8" textLength="122" clip-path="url(#terminal-line-17)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="434.8" textLength="244" clip-path="url(#terminal-line-17)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="434.8" textLength="256.2" clip-path="url(#terminal-line-17)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="0" y="459.2" textLength="134.2" clip-path="url(#terminal-line-18)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="0" y="459.2" textLength="414.8" clip-path="url(#terminal-line-18)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="231.8" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="292.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="0" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">Type&#160;</text><text class="terminal-r3" x="61" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="122" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before After
Before After

View file

@ -176,13 +176,13 @@
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r2" x="0" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="483.6" textLength="244" clip-path="url(#terminal-line-19)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="414.8" clip-path="url(#terminal-line-20)">1&#160;model&#160;·&#160;2&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="292.8" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="61" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="122" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Before After
Before After

View file

@ -174,13 +174,13 @@
</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> </text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">
</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> </text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">
</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> </text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">
</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> </text><text class="terminal-r1" x="0" y="264" textLength="134.2" clip-path="url(#terminal-line-10)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">
</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> </text><text class="terminal-r1" x="0" y="288.4" textLength="134.2" clip-path="url(#terminal-line-11)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">
</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="0" y="312.8" textLength="134.2" clip-path="url(#terminal-line-12)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="0" y="361.6" textLength="134.2" clip-path="url(#terminal-line-14)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="361.6" textLength="146.4" clip-path="url(#terminal-line-14)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="361.6" textLength="244" clip-path="url(#terminal-line-14)">devstral-latest[off]</text><text class="terminal-r1" x="683.2" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r2" x="0" y="361.6" textLength="146.4" clip-path="url(#terminal-line-14)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="361.6" textLength="244" clip-path="url(#terminal-line-14)">devstral-latest[off]</text><text class="terminal-r1" x="512.4" y="361.6" textLength="256.2" clip-path="url(#terminal-line-14)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="386" textLength="585.6" clip-path="url(#terminal-line-15)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="585.6" clip-path="url(#terminal-line-15)">1&#160;model&#160;·&#160;2&#160;connectors&#160;·&#160;1&#160;MCP&#160;server&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">Type&#160;</text><text class="terminal-r3" x="231.8" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">/help</text><text class="terminal-r1" x="292.8" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">Type&#160;</text><text class="terminal-r3" x="61" y="410.4" textLength="61" clip-path="url(#terminal-line-16)">/help</text><text class="terminal-r1" x="122" y="410.4" textLength="256.2" clip-path="url(#terminal-line-16)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before After
Before After

View file

@ -179,13 +179,13 @@
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="0" y="556.8" textLength="134.2" clip-path="url(#terminal-line-22)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="0" y="605.6" textLength="134.2" clip-path="url(#terminal-line-24)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="605.6" textLength="158.6" clip-path="url(#terminal-line-24)">devstral[off]</text><text class="terminal-r1" x="597.8" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r2" x="0" y="605.6" textLength="146.4" clip-path="url(#terminal-line-24)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="605.6" textLength="122" clip-path="url(#terminal-line-24)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="605.6" textLength="158.6" clip-path="url(#terminal-line-24)">devstral[off]</text><text class="terminal-r1" x="427" y="605.6" textLength="256.2" clip-path="url(#terminal-line-24)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">
</text><text class="terminal-r1" x="0" y="630" textLength="134.2" clip-path="url(#terminal-line-25)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="630" textLength="427" clip-path="url(#terminal-line-25)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> </text><text class="terminal-r1" x="0" y="630" textLength="427" clip-path="url(#terminal-line-25)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">
</text><text class="terminal-r1" x="0" y="654.4" textLength="134.2" clip-path="url(#terminal-line-26)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="231.8" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="292.8" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> </text><text class="terminal-r1" x="0" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">Type&#160;</text><text class="terminal-r3" x="61" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/help</text><text class="terminal-r1" x="122" y="654.4" textLength="256.2" clip-path="url(#terminal-line-26)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">
</text><text class="terminal-r1" x="1220" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> </text><text class="terminal-r1" x="1220" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">
</text><text class="terminal-r1" x="1220" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> </text><text class="terminal-r1" x="1220" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">
</text><text class="terminal-r1" x="1220" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> </text><text class="terminal-r1" x="1220" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -177,13 +177,13 @@
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="483.6" textLength="158.6" clip-path="url(#terminal-line-19)">devstral[off]</text><text class="terminal-r1" x="597.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r2" x="0" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="483.6" textLength="158.6" clip-path="url(#terminal-line-19)">devstral[off]</text><text class="terminal-r1" x="427" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="508" textLength="427" clip-path="url(#terminal-line-20)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="427" clip-path="url(#terminal-line-20)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="292.8" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="61" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="122" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

View file

@ -178,13 +178,13 @@
</text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> </text><text class="terminal-r1" x="1220" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">
</text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> </text><text class="terminal-r1" x="1220" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">
</text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> </text><text class="terminal-r1" x="1220" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">
</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> </text><text class="terminal-r1" x="0" y="386" textLength="134.2" clip-path="url(#terminal-line-15)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r1" x="1220" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">
</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> </text><text class="terminal-r1" x="0" y="410.4" textLength="134.2" clip-path="url(#terminal-line-16)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="1220" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">
</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> </text><text class="terminal-r1" x="0" y="434.8" textLength="134.2" clip-path="url(#terminal-line-17)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="1220" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">
</text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> </text><text class="terminal-r1" x="1220" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">
</text><text class="terminal-r1" x="0" y="483.6" textLength="134.2" clip-path="url(#terminal-line-19)">&#160;&#160;⡠⣒⠄&#160;&#160;⡔⢄⠔⡄</text><text class="terminal-r2" x="170.8" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="317.2" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="439.2" y="483.6" textLength="158.6" clip-path="url(#terminal-line-19)">devstral[off]</text><text class="terminal-r1" x="597.8" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> </text><text class="terminal-r2" x="0" y="483.6" textLength="146.4" clip-path="url(#terminal-line-19)">Mistral&#160;Vibe</text><text class="terminal-r1" x="146.4" y="483.6" textLength="122" clip-path="url(#terminal-line-19)">&#160;v0.0.0&#160;·&#160;</text><text class="terminal-r3" x="268.4" y="483.6" textLength="158.6" clip-path="url(#terminal-line-19)">devstral[off]</text><text class="terminal-r1" x="427" y="483.6" textLength="256.2" clip-path="url(#terminal-line-19)">&#160;·&#160;[Subscription]&#160;Pro</text><text class="terminal-r1" x="1220" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">
</text><text class="terminal-r1" x="0" y="508" textLength="134.2" clip-path="url(#terminal-line-20)">&#160;⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="508" textLength="427" clip-path="url(#terminal-line-20)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> </text><text class="terminal-r1" x="0" y="508" textLength="427" clip-path="url(#terminal-line-20)">5&#160;models&#160;·&#160;0&#160;MCP&#160;servers&#160;·&#160;0&#160;skills</text><text class="terminal-r1" x="1220" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">
</text><text class="terminal-r1" x="0" y="532.4" textLength="134.2" clip-path="url(#terminal-line-21)">&#160;&#160;⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="231.8" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="292.8" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> </text><text class="terminal-r1" x="0" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">Type&#160;</text><text class="terminal-r3" x="61" y="532.4" textLength="61" clip-path="url(#terminal-line-21)">/help</text><text class="terminal-r1" x="122" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">&#160;for&#160;more&#160;information</text><text class="terminal-r1" x="1220" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">
</text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> </text><text class="terminal-r1" x="1220" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">
</text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> </text><text class="terminal-r1" x="1220" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">
</text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> </text><text class="terminal-r1" x="1220" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

Some files were not shown because too many files have changed in this diff Show more