Designed Reachy Mini app with button interface and fallback.
PlanImplemented app, verified Python code, and created README.
BuildGuided user to test app locally on Reachy Mini.
PlanExplained publishing process to Hugging Face Spaces.
PlanPrepared app for publishing and pushed to HF Space.
Buildcan you build an app for reachy mini which when I press a button, reachy mini says "good morning". You can use the reachy-mini github repo
{
"type": "tool_reference",
"tool_name": "WebSearch"
}
{
"type": "tool_reference",
"tool_name": "mcp__workspace__web_fetch"
}
{
"type": "tool_reference",
"tool_name": "mcp__workspace__bash"
}HTTP 404
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html
lang="en"
data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
data-a11y-animated-images="system" data-a11y-link-underlines="true"
>
<head>
<meta charset="utf-8">
<link rel="dns-prefetch" href="https://github.githubassets.com">
<link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
<link rel="preconnect" href="https://github.githubassets.com" crossorigin>
<link rel="preconnect" href="https://avatars.githubusercontent.com">
<link crossorigin="anonymous" rel="preload" as="script" href="https://github.githubassets.com/assets/global-banner-disable-7f8c9b12bd1db3c7.js" />
<link rel="preload" href="https://github.githubassets.com/assets/MonaSansVF-wdth-wght-opsz-902d64c7ad02.woff2" as="font" type="font/woff2" crossorigin>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-2ff56e1b36116ee2.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-f7f95d7633592089.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-2d1fe43dbc9adf1f.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-d530ee188d165539.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-2ff56e1b36116ee2.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-f7f95d7633592089.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-e4bbc49fc7b82570.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-cc3f126b45166b83.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-652ca611ea4e33fe.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-9e82d635cb6c3f49.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-2d1fe43dbc9adf1f.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-d530ee188d165539.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-3ad0ec21150df75b.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-5691ff467f71a3f6.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-abee7710893cd168.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-eafcf6cd46158360.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-c7974682a1a84c8d.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-f8dab3e04f94c501.css" />
<style type="text/css">
:root {
--tab-size-preference: 4;
}
pre, code {
tab-size: var(--tab-size-preference);
}
</style>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-7f694b60439d06c0.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-b48faa60c69660fa.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-b40ec823a1a6a1af.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-f825c0edd7ad57f8.c
... [293649 characters truncated] ...
0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
You can’t perform that action at this time.
</div>
<template id="site-details-dialog">
<details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
<summary role="button" aria-label="Close dialog"></summary>
<details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
<button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
<div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
</details-dialog>
</details>
</template>
<div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
<div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
</div>
</div>
<template id="snippet-clipboard-copy-button">
<div class="zeroclipboard-container position-absolute right-0 top-0">
<clipboard-copy aria-label="Copy" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
</clipboard-copy>
</div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
<div class="zeroclipboard-container">
<clipboard-copy aria-label="Copy" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
</clipboard-copy>
</div>
</template>
</div>
<div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
<div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
</body>
</html>Web search results for query: "reachy-mini github pollen-robotics SDK python"
Links: [{"title":"GitHub - pollen-robotics/reachy_mini: Reachy Mini's SDK · GitHub","url":"https://github.com/pollen-robotics/reachy_mini"},{"title":"reachy_mini/docs/SDK/python-sdk.md at develop · pollen-robotics/reachy_mini","url":"https://github.com/pollen-robotics/reachy_mini/blob/develop/docs/SDK/python-sdk.md"},{"title":"Pollen Robotics · GitHub","url":"https://github.com/pollen-robotics"},{"title":"reachy_mini/docs/SDK/quickstart.md at develop · pollen-robotics/reachy_mini","url":"https://github.com/pollen-robotics/reachy_mini/blob/develop/docs/SDK/quickstart.md"},{"title":"reachy_mini/docs/SDK/installation.md at develop · pollen-robotics/reachy_mini","url":"https://github.com/pollen-robotics/reachy_mini/blob/develop/docs/SDK/installation.md"},{"title":"GitHub - pollen-robotics/reachy-sdk: Reachy 2021 and 2023 Python SDK · GitHub","url":"https://github.com/pollen-robotics/reachy-sdk"},{"title":"GitHub - pollen-robotics/reachy_mini_conversation_app: Talk with Reachy Mini! · GitHub","url":"https://github.com/pollen-robotics/reachy_mini_conversation_app"},{"title":"GitHub - pollen-robotics/reachy-mini-desktop-app · GitHub","url":"https://github.com/pollen-robotics/reachy-mini-desktop-app"},{"title":"GitHub - pollen-robotics/reachy2-sdk: Reachy 2 Python SDK · GitHub","url":"https://github.com/pollen-robotics/reachy2-sdk"},{"title":"Reachy 2023 - Reachy 2023 Full Documentation","url":"https://pollen-robotics.github.io/reachy-2023-docs/"}]
Based on the search results, here's what I found about the reachy-mini GitHub repository and its Python SDK from Pollen Robotics:
The Reachy Mini repository includes documentation on installation, a quickstart guide, and a Python SDK to learn how to move, see, speak, and hear. The reachy_mini repository is written in Python and contains Reachy Mini's SDK.
Reachy Mini comes with an app store powered by Hugging Face Spaces, and you can install these apps directly from your robot's dashboard with one click. The SDK enables control of various robot functions:
The guide will help you install the Python SDK and daemon to start controlling your robot. If you want to use the SDK from a remote laptop with a wireless Reachy Mini, you'll need to install GStreamer to receive video and audio streams.
After installing the SDK, you can control the robot in just a few lines of code using the ReachyMini class.
The GitHub repository is available at: https://github.com/pollen-robotics/reachy_mini
REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.
HTTP 404
Content-Type: text/plain; charset=utf-8
404: Not FoundHTTP 404
Content-Type: text/plain; charset=utf-8
404: Not FoundError: result (329,421 characters across 1,915 lines) exceeds maximum allowed tokens. Output has been saved to /var/folders/dw/zt1ncz810_j9j8x1hkv2lbgm0000gn/T/claude-hostloop-plugins/3c918a1b0d2f15ba/projects/-Users-clem-Library-Application-Support-Claude-local-agent-mode-sessions-60820195-31c6-4093-8c9e-37c09903f4a5-6e545699-813d-4c34-a80c-8667623c108d-local-8906e6df-1974-4b95-a48d-96cc2a2d91db-outputs/e647f442-7cdb-4e5d-8667-3b77a261289d/tool-results/mcp-workspace-web_fetch-1777470532955.txt.
Format: Plain text
- For targeted searches (find a line, locate a string): use grep on the file directly.
- For analysis or summarization that requires reading the full content: read /var/folders/dw/zt1ncz810_j9j8x1hkv2lbgm0000gn/T/claude-hostloop-plugins/3c918a1b0d2f15ba/projects/-Users-clem-Library-Application-Support-Claude-local-agent-mode-sessions-60820195-31c6-4093-8c9e-37c09903f4a5-6e545699-813d-4c34-a80c-8667623c108d-local-8906e6df-1974-4b95-a48d-96cc2a2d91db-outputs/e647f442-7cdb-4e5d-8667-3b77a261289d/tool-results/mcp-workspace-web_fetch-1777470532955.txt in chunks of ~1 lines using offset/limit until you have read 100% of it.
- If the Agent tool is available, do this inside a subagent so the full output stays out of your main context. Give it the instruction above verbatim, and be explicit about what it must return — e.g. "Read /var/folders/dw/zt1ncz810_j9j8x1hkv2lbgm0000gn/T/claude-hostloop-plugins/3c918a1b0d2f15ba/projects/-Users-clem-Library-Application-Support-Claude-local-agent-mode-sessions-60820195-31c6-4093-8c9e-37c09903f4a5-6e545699-813d-4c34-a80c-8667623c108d-local-8906e6df-1974-4b95-a48d-96cc2a2d91db-outputs/e647f442-7cdb-4e5d-8667-3b77a261289d/tool-results/mcp-workspace-web_fetch-1777470532955.txt in chunks of ~1 lines using offset/limit until you have read all 1,915 lines, then summarize and quote any key findings verbatim." A vague "summarize this" may lose detail.
HTTP 404
Content-Type: text/html; charset=utf-8
<!DOCTYPE html>
<html
lang="en"
data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
data-a11y-animated-images="system" data-a11y-link-underlines="true"
>
<head>
<meta charset="utf-8">
<link rel="dns-prefetch" href="https://github.githubassets.com">
<link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
<link rel="preconnect" href="https://github.githubassets.com" crossorigin>
<link rel="preconnect" href="https://avatars.githubusercontent.com">
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-2ff56e1b36116ee2.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-f7f95d7633592089.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-2d1fe43dbc9adf1f.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-d530ee188d165539.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-2ff56e1b36116ee2.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-f7f95d7633592089.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-e4bbc49fc7b82570.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-cc3f126b45166b83.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-652ca611ea4e33fe.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-9e82d635cb6c3f49.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-2d1fe43dbc9adf1f.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-d530ee188d165539.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-3ad0ec21150df75b.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-5691ff467f71a3f6.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-abee7710893cd168.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-eafcf6cd46158360.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-c7974682a1a84c8d.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-f8dab3e04f94c501.css" />
<style type="text/css">
:root {
--tab-size-preference: 4;
}
pre, code {
tab-size: var(--tab-size-preference);
}
</style>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-7f694b60439d06c0.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-b48faa60c69660fa.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-b40ec823a1a6a1af.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-f825c0edd7ad57f8.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/repository-0751f482f5210958.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-c22301b4e838281c.css" />
<script type="app
... [193288 characters truncated] ...
0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
You can’t perform that action at this time.
</div>
<template id="site-details-dialog">
<details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
<summary role="button" aria-label="Close dialog"></summary>
<details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
<button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
<div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
</details-dialog>
</details>
</template>
<div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
<div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
</div>
</div>
<template id="snippet-clipboard-copy-button">
<div class="zeroclipboard-container position-absolute right-0 top-0">
<clipboard-copy aria-label="Copy" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
</clipboard-copy>
</div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
<div class="zeroclipboard-container">
<clipboard-copy aria-label="Copy" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
</clipboard-copy>
</div>
</template>
</div>
<div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
<div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
</body>
</html>I have everything I need. The entrypoint signature is `def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None`, subclassing `ReachyMiniApp`. I have enough — let me write up the report.
---
# Reachy Mini SDK — Research Report
## 1. Installation
Install via `uv` (recommended) or pip from PyPI. Package name is **`reachy-mini`** (PyPI) which imports as **`reachy_mini`**.
```bash
# Recommended (after installing uv + creating a venv):
uv venv reachy_mini_env --python 3.12
source reachy_mini_env/bin/activate
uv pip install "reachy-mini"
# Plain pip works too:
pip install reachy-mini
```
Python 3.10–3.12 supported. On Linux you also need GStreamer + USB udev rules. Source: https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/docs/source/SDK/installation.md
## 2. Instantiating the Robot
Class is `ReachyMini`. No host/IP arg needed — it auto-detects USB/localhost vs network. Use as a context manager:
```python
from reachy_mini import ReachyMini
with ReachyMini() as mini:
# control code here
...
```
Optional override: `ReachyMini(connection_mode="localhost_only" | "network")` and `ReachyMini(robot_name="...")`. There is no `host=`/`ip=` argument in the public surface — connection is mediated by the local daemon (`localhost:8000` for Lite, `reachy-mini.local:8000` for Wireless).
## 3. Speaking text out loud — there is NO `.speak()` / `.tts()` / `.audio.play()`
Confirmed from `docs/source/SDK/python-sdk.md`: the SDK exposes raw audio I/O only. You generate audio with an external TTS lib, then push float32 samples to the speaker via `mini.media.push_audio_sample(...)`. Verbatim from the docs:
```python
from reachy_mini import ReachyMini
from scipy.signal import resample
import time
with ReachyMini(media_backend="default") as mini:
mini.media.start_recording()
mini.media.start_playing()
# Play
mini.media.push_audio_sample(samples)
time.sleep(len(samples) / mini.media.get_output_audio_samplerate())
mini.media.stop_recording()
mini.media.stop_playing()
```
Notes from the doc:
- `push_audio_sample()` expects shape `(samples, 1 or 2)`, dtype `float32`, **16 kHz**.
- It is **non-blocking** — sleep for `len(samples)/samplerate` to wait for playback.
- Use `mini.media.get_output_audio_samplerate()` to confirm the rate; resample if your TTS produces 22.05 / 24 kHz.
For "good morning" the simplest stack: **Piper** or **pyttsx3** → WAV → load with `soundfile`/`scipy.io.wavfile` → convert to float32 mono → resample to 16 kHz → `mini.media.push_audio_sample(samples)`. (The conversation app uses OpenAI/Gemini Realtime APIs that stream PCM directly into `push_audio_sample`.)
## 4. App Framework — pyproject entry point
A Reachy Mini app **is a Python package** registered through a setuptools entry point group named `reachy_mini_apps`, exposing a class that subclasses `reachy_mini.ReachyMiniApp` and implements `run(self, reachy_mini, stop_event)`.
**Do not scaffold by hand** — the official tool is:
```bash
reachy-mini-app-assistant create <app_name> <path> --publish
# or for a TTS-friendly fork:
reachy-mini-app-assistant create --template conversation <app_name> <path> --publish
```
Generated layout (verbatim from `skills/create-app.md`):
```
my_app/
├── index.html # HF Space landing page
├── style.css
├── pyproject.toml # contains the reachy_mini entry point
├── README.md # YAML frontmatter must include reachy_mini tag
├── .gitignore
└── my_app/
├── __init__.py
├── main.py # your app code (run method)
└── static/ # optional web UI
```
The required `pyproject.toml` block (from the conversation app):
```toml
[project.entry-points."reachy_mini_apps"]
my_app = "my_app.main:MyApp"
```
The required class signature (verbatim from `reachy_mini_conversation_app/main.py`):
```python
from reachy_mini import ReachyMini, ReachyMiniApp
import threading
class ReachyMiniConversationApp(ReachyMiniApp):
custom_app_url = "http://0.0.0.0:7860/"
dont_start_webserver = False
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None:
...
```
The README YAML frontmatter must carry the discovery tag `reachy_mini` (and `reachy_mini_python_app` for Python apps) for the HF app store to pick it up.
## 5. The "Button" — use the antennas
Reachy Mini has **no physical button**, but per the official AGENTS.md: *"Antennas: 2 motors, also usable as physical buttons."* They use low-P PID so they are semi-passive and safe to push. The canonical pattern (verbatim from `skills/interaction-patterns.md`):
```python
ANTENNA_THRESHOLD = 0.3 # radians, tune as needed
def check_antenna_press(mini):
_, antennas = mini.get_current_joint_positions()
if antennas is None:
return None
left, right = antennas
if abs(left) > ANTENNA_THRESHOLD:
return "left"
if abs(right) > ANTENNA_THRESHOLD:
return "right"
return None
```
Reference implementation: `reachy_mini_simon` (no-GUI app driven entirely by antenna presses) at https://huggingface.co/spaces/apirrone/reachy_mini_simon. Tip from the docs: debounce with a cooldown.
If antennas don't fit the use case, alternatives that are also documented as supported:
- **Web UI button**: put `static/index.html` with a button that hits the daemon REST API (`http://localhost:8000/docs`) or your own FastAPI mounted in `run()`.
- **Keyboard input**: plain `input()` / `keyboard` lib — fine for a quick local demo, but not "appy".
## Recommended minimal app for your use case
Antenna-as-button + Piper TTS in a `ReachyMiniApp` subclass: scaffold with `reachy-mini-app-assistant create good_morning . --publish`, then in `good_morning/main.py` poll `check_antenna_press(reachy_mini)` and on press: synthesize "good morning" with Piper to a numpy float32 mono buffer at 16 kHz and call `reachy_mini.media.push_audio_sample(samples)` (after `start_playing()`). Loop until `stop_event.is_set()`.
## Key links
- SDK repo: https://github.com/pollen-robotics/reachy_mini
- AGENTS.md (best single doc): https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/AGENTS.md
- Installation: https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/docs/source/SDK/installation.md
- Python SDK (audio API): https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/docs/source/SDK/python-sdk.md
- App scaffolding skill: https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/skills/create-app.md
- Antenna button pattern: https://raw.githubusercontent.com/pollen-robotics/reachy_mini/main/skills/interaction-patterns.md
- Conversation app (reference for `ReachyMiniApp` subclass + entry point): https://github.com/pollen-robotics/reachy_mini_conversation_app, pyproject: https://raw.githubusercontent.com/pollen-robotics/reachy_mini_conversation_app/main/pyproject.toml, main.py: https://raw.githubusercontent.com/pollen-robotics/reachy_mini_conversation_app/main/src/reachy_mini_conversation_app/main.py
agentId: a813d1db77eacff3e (use SendMessage with to: 'a813d1db77eacff3e' to continue this agent)
<usage>total_tokens: 84228
tool_uses: 17
duration_ms: 105255</usage>Now I have everything I need. Let me build the app. I'll structure it as a proper Reachy Mini app (Python package with the right entry point), with a web button as the primary interface (matching "press a button" literally) plus antenna-press as a hardware fallback.
Let me sanity-check the Python compiles cleanly before handing it over:
OK
./README.md
./pyproject.toml
./src/good_morning_app/__init__.py
./src/good_morning_app/__pycache__/__init__.cpython-310.pyc
./src/good_morning_app/__pycache__/main.cpython-310.pyc
./src/good_morning_app/main.py
Compiles cleanly. Here's what I built:
[View the README](computer:///Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning/README.md) [View main.py (the app)](computer:///Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning/src/good_morning_app/main.py) [View pyproject.toml](computer:///Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning/pyproject.toml)
It's packaged as a proper Reachy Mini app (subclass of ReachyMiniApp, registered via the reachy_mini_apps entry point — same shape as reachy_mini_conversation_app). On startup it pre-renders "Good morning!" once with pyttsx3, caches it as a WAV, and on every press loads it as mono float32 @ 16 kHz and pushes it to the speaker via reachy_mini.media.push_audio_sample(...) — that's the canonical TTS path because the SDK doesn't expose a .speak() helper.
You get two buttons:
A big on-screen button at http://<robot>:7860/ (also embedded in the dashboard), and either antenna as a physical button — Reachy Mini doesn't have a real push-button, but the antennas are spec'd that way; deflection >0.3 rad on either antenna fires the same trigger, with a 1.5 s debounce so a single push doesn't repeat.
Run it standalone with python -m good_morning_app.main after pip install -e ., or install it and launch through the Reachy Mini dashboard.
Sources:
how can I try it?
Quickest path, assuming you have a Reachy Mini (Lite plugged in via USB, or Wireless on your network):
1. Set up a Python env with the Reachy Mini SDK
# Python 3.10–3.12 requiredpython3.12 -m venv ~/reachy_mini_envsource ~/reachy_mini_env/bin/activatepip install reachy-miniIf you're on Linux you'll also need GStreamer + the udev rules for the USB connection — see the SDK installation doc. On macOS/Windows, just pip install reachy-mini is usually enough.
2. Make sure the robot daemon is running and the robot is reachable
reachy-mini-daemon # starts the daemon (Lite, USB)# or for Wireless: it runs on the robot itself; just check reachy-mini.local:8000 respondsQuick sanity check the SDK can see it:
from reachy_mini import ReachyMiniwith ReachyMini() as mini: print(mini.get_current_joint_positions())3. Install the Good Morning app
From the folder I just created (reachy-mini-good-morning/):
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"pip install -e .(-e = editable install, so any tweak you make to main.py takes effect immediately.)
On Linux, pyttsx3 needs espeak: sudo apt install espeak-ng. On macOS it uses the built-in say voices, no extra install. On Windows it uses SAPI5.
4. Run it — two ways
The "I just want to see it work" way:
python -m good_morning_app.mainYou'll see logs like Good Morning app starting (web on :7860, antennas armed). Then:
The "as a real Reachy Mini app" way (shows up in the dashboard alongside the other installed apps):
# the app is already registered via the entry point in pyproject.toml,# so the dashboard / launcher will pick it up automatically.# Open the Reachy Mini dashboard and click "Good Morning".5. If you don't have the robot yet
You can still verify the TTS half works on its own:
pip install pyttsx3 soundfile scipy numpypython -c "from good_morning_app.main import _synthesize_phrase, _load_phrase_for_robotfrom pathlib import Path_synthesize_phrase('Good morning!', Path('test.wav'))print(_load_phrase_for_robot(Path('test.wav')).shape)"You should see something like (20480, 1) and a playable test.wav next to you. The robot half (mini.media.push_audio_sample) only runs when a real Reachy Mini is connected.
Common gotchas
mini.media.start_playing() was skipped (the app does this for you in run()); also check the speaker isn't muted via alsamixer on the robot.ANTENNA_THRESHOLD in main.py from 0.3 to 0.2.WEB_PORT in main.py.pip isn't on your PATH on stock macOS — pip3 usually is, or even better, use a virtual environment so the SDK doesn't fight your system Python.
Try this from the same folder you're already in:
# 1. Check what Python you havepython3 --versionIf it says 3.10, 3.11, or 3.12 you're set. If it's 3.13 or you get "command not found", install Python 3.12 first (brew install python@3.12 if you have Homebrew, otherwise grab the installer from python.org).
# 2. Make a venv and activate itpython3 -m venv .venvsource .venv/bin/activate
# 3. Now `pip` exists inside the venv. Install the app + the SDK.pip install --upgrade pippip install -e .
# 4. Run itpython -m good_morning_app.mainThen open http://localhost:7860/ in a browser.
If python3 -m venv complains, fall back to:
python3 -m pip install --user -e .python3 -m good_morning_app.mainLet me know what python3 --version prints and I'll tailor from there if anything errors out.
doesn't work, can I publish the app on the reachy mini app directory?
HTTP 200
Content-Type: text/plain; charset=utf-8
---
title: Reachy Mini Conversation App
emoji: 🎤
colorFrom: red
colorTo: blue
sdk: static
pinned: false
short_description: Talk with Reachy Mini!
suggested_storage: large
tags:
- reachy_mini
- reachy_mini_python_app
---
# Reachy Mini conversation app
Conversational app for the Reachy Mini robot combining real-time voice APIs (OpenAI Realtime or Gemini Live), vision pipelines, and choreographed motion libraries.

## Table of contents
- [Overview](#overview)
- [Architecture](#architecture)
- [Installation](#installation)
- [Configuration](#configuration)
- [Running the app](#running-the-app)
- [LLM tools](#llm-tools-exposed-to-the-assistant)
- [Advanced features](#advanced-features)
- [Contributing](#contributing)
- [License](#license)
## Overview
- Real-time audio conversation loop with `fastrtc` for low-latency streaming. Supports two backends:
- **OpenAI Realtime** (`gpt-realtime`) — default
- **Gemini Live** (`gemini-3.1-flash-live-preview`) — alternative, using the Google GenAI SDK
- Vision processing uses the selected realtime backend by default (when camera tool is used), with optional on-device local vision using SmolVLM2 (CPU/GPU/MPS) via `--local-vision`.
- Layered motion system queues primary moves (dances, emotions, goto poses, breathing) while blending speech-reactive wobble and head-tracking.
- Async tool dispatch integrates robot motion, camera capture, and optional head-tracking capabilities through a Gradio web UI with live transcripts.
## Architecture
The app follows a layered architecture connecting the user, AI services, and robot hardware:
<p align="center">
<img src="docs/assets/conversation_app_arch.svg" alt="Architecture Diagram" width="600"/>
</p>
## Installation
> [!IMPORTANT]
> Before using this app, you need to install [Reachy Mini's SDK](https://github.com/pollen-robotics/reachy_mini/).<br>
> Windows support is currently experimental and has not been extensively tested. Use with caution.
<details open>
<summary><b>Using uv (recommended)</b></summary>
Set up the project quickly using [uv](https://docs.astral.sh/uv/):
```bash
# macOS (Homebrew)
uv venv --python /opt/homebrew/bin/python3.12 .venv
# Linux / Windows (Python in PATH)
uv venv --python python3.12 .venv
source .venv/bin/activate
uv sync
```
> **Note:** To reproduce the exact dependency set from this repo's `uv.lock`, run `uv sync --frozen`. This ensures `uv` installs directly from the lockfile without re-resolving or updating any versions.
**Install optional features:**
```bash
uv sync --extra local_vision # Local PyTorch/Transformers vision
uv sync --extra yolo_vision # YOLO face-detection backend for head tracking
uv sync --extra mediapipe_vision # MediaPipe-based head-tracking
uv sync --extra all_vision # All vision features
```
Combine extras or include dev dependencies:
```bash
uv sync --extra all_vision --group dev
```
</details>
<details>
<summary><b>Using pip</b></summary>
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e .
```
**Install optional features:**
```bash
pip install -e .[local_vision] # Local vision stack
pip install -e .[yolo_vision] # YOLO face-detection backend for head tracking
pip install -e .[mediapipe_vision] # MediaPipe-based vision
pip install -e .[all_vision] # All vision features
pip install -e .[dev] # Development tools
```
Some wheels (like PyTorch) are large and require compatible CUDA or CPU builds—make sure your platform matches the binaries pulled in by each extra.
</details>
### Optional dependency groups
| Extra | Purpose | Notes |
|-------|---------|-------|
| `local_vision` | Run the local VLM (SmolVLM2) through PyTorch/Transformers | GPU recommended. Ensure compatible PyTorch builds for your platform. |
| `yolo_vision` | YOLOv11n face detection via `ultralytics` and `supervision` | Used as the `yolo` head-tracking backend. Runs on CPU (default). GPU improves performance. |
| `mediapipe_vision` | Lightweight landmark tracking with MediaPipe | Works on CPU. Enables `--head-tracker mediapipe`. |
| `all_vision` | Convenience alias installing every vision extra | Install when you want the flexibility to experiment with every provider. |
| `dev` | Developer tooling (`pytest`, `ruff`, `mypy`) | Development-only dependencies. Use `--group dev` with uv or `[dev]` with pip. |
**Note:** `dev` is a dependency group (not an optional dependency). With uv, use `--group dev`. With pip, use `[dev]`.
## Configuration
1. Copy `.env.example` to `.env`
2. Fill in your API key and backend choice
| Variable | Description |
|----------|-------------|
| `OPENAI_API_KEY` | Optional override or fallback for OpenAI mode. In the headless settings flow, the app can use bundled OpenAI access when available; set your own key to override it or provide a fallback. |
| `GEMINI_API_KEY` | Required for Gemini mode. Also accepts `GOOGLE_API_KEY`. Get one at [aistudio.google.com](https://aistudio.google.com/apikey). |
| `BACKEND_PROVIDER` | Realtime backend to use: `openai` (default) or `gemini`. |
| `MODEL_NAME` | Optional model override for the selected backend. Defaults to `gpt-realtime` for OpenAI and `gemini-3.1-flash-live-preview` for Gemini Live. |
| `HF_HOME` | Cache directory for local Hugging Face downloads (only used with `--local-vision` flag, defaults to `./cache`). |
| `HF_TOKEN` | Optional token for Hugging Face access (for gated/private assets). |
| `LOCAL_VISION_MODEL` | Hugging Face model path for local vision processing (only used with `--local-vision` flag, defaults to `HuggingFaceTB/SmolVLM2-2.2B-Instruct`). |
### Switching to Gemini Live
To use Gemini Live instead of OpenAI Realtime, update your `.env`:
```env
BACKEND_PROVIDER="gemini"
MODEL_NAME="gemini-3.1-flash-live-preview"
GEMINI_API_KEY=[REDACTED]
```
`BACKEND_PROVIDER` is the primary switch. The app still falls back to `MODEL_NAME` for compatibility with older configs, and all features (tools, profiles, head tracking) work with both backends.
> [!NOTE]
> Gemini Live uses a different set of voices: Aoede, Charon, Fenrir, Kore (default), Leda, Orus, Puck, Zephyr. If your profile's `voice.txt` specifies an OpenAI voice, it will fall back to Kore.
## Running the app
Activate your virtual environment, then launch:
```bash
reachy-mini-conversation-app
```
> [!TIP]
> Make sure the Reachy Mini daemon is running before launching the app. If you see a `TimeoutError`, it means the daemon isn't started. See [Reachy Mini's SDK](https://github.com/pollen-robotics/reachy_mini/) for setup instructions.
The app runs in console mode by default. Add `--gradio` to launch a web UI at http://127.0.0.1:7860/ (required for simulation mode). Vision and head-tracking options are described in the CLI table below.
### CLI options
| Option | Default | Description |
|--------|---------|-------------|
| `--head-tracker {yolo,mediapipe}` | `None` | Select a head-tracking backend when a camera is available. `yolo` uses a local YOLO face detector, `mediapipe` comes from the `reachy_mini_toolbox` package. Requires the matching optional extra. |
| `--no-camera` | `False` | Run without camera capture or head tracking. |
| `--local-vision` | `False` | Use the local vision model (SmolVLM2) for camera-tool requests instead of the selected realtime backend vision. Requires `local_vision` extra to be installed. |
| `--gradio` | `False` | Launch the Gradio web UI. Without this flag, runs in console mode. Required when running in simulation mode. |
| `--robot-name` | `None` | Optional. Connect to a specific robot by name when running multiple daemons on the same subnet. See [Multiple robots on the same subnet](#advanced-features). |
| `--debug` | `False` | Enable verbose logging for troubleshooting. |
### Examples
```bash
# Run with MediaPipe head tracking
reachy-mini-conversation-app --head-tracker mediapipe
# Run with the YOLO face-detection backend for head tracking
reachy-mini-conversation-app --head-tracker yolo
# Run with local vision processing (requires local_vision extra)
reachy-mini-conversation-app --local-vision
# Audio-only conversation (no camera)
reachy-mini-conversation-app --no-camera
# Launch with Gradio web interface
reachy-mini-conversation-app --gradio
```
> [!WARNING]
> `--local-vision` is not supported when running the conversation app directly on Reachy Mini Wireless / the Raspberry Pi. For local vision, keep the daemon running on the robot and start the conversation app from your laptop or workstation instead.
## LLM tools exposed to the assistant
| Tool | Action | Dependencies |
|------|--------|--------------|
| `move_head` | Queue a head pose change (left/right/up/down/front). | Core install only. |
| `camera` | Capture the latest camera frame and analyze it with the selected realtime backend or the local vision model. | Requires camera worker. Uses local vision when `--local-vision` is enabled. |
| `head_tracking` | Enable or disable head-tracking offsets (not identity recognition - only detects and tracks head position). | Camera worker with configured head tracker (`--head-tracker`). |
| `dance` | Queue a dance from `reachy_mini_dances_library`. | Core install only. |
| `stop_dance` | Clear queued dances. | Core install only. |
| `play_emotion` | Play a recorded emotion clip via Hugging Face datasets. | Core install only. Uses the default open emotions dataset: [`pollen-robotics/reachy-mini-emotions-library`](https://huggingface.co/datasets/pollen-robotics/reachy-mini-emotions-library). |
| `stop_emotion` | Clear queued emotions. | Core install only. |
| `do_nothing` | Explicitly remain idle. | Core install only. |
## Advanced features
Built-in motion content is published as open Hugging Face datasets:
- Emotions: [`pollen-robotics/reachy-mini-emotions-library`](https://huggingface.co/datasets/pollen-robotics/reachy-mini-emotions-library)
- Dances: [`pollen-robotics/reachy-mini-dances-library`](https://huggingface.co/datasets/pollen-robotics/reachy-mini-dances-library)
<details>
<summary><b>Custom profiles</b></summary>
Create custom profiles with dedicated instructions and enabled tools.
For normal usage, select a profile from the UI and save it for startup. That selection is persisted in `startup_settings.json`.
If no startup settings have been saved yet, you can still seed startup from the environment with `REACHY_MINI_CUSTOM_PROFILE=<name>` to load `profiles/<name>/`. If neither is set, the `default` profile is used.
Each profile should include `instructions.txt` (prompt text). `tools.txt` (list of allowed tools) is recommended. If missing for a non-default profile, the app falls back to `profiles/default/tools.txt`. Profiles can optionally contain custom tool implementations.
**Custom instructions:**
Write plain-text prompts in `instructions.txt`. To reuse shared prompt pieces, add lines like:
```
[passion_for_lobster_jokes]
[identities/witty_identity]
```
Each placeholder pulls the matching file under `src/reachy_mini_conversation_app/prompts/` (nested paths allowed). See `profiles/example/` for a reference layout.
**Enabling tools:**
List enabled tools in `tools.txt`, one per line. Prefix with `#` to comment out:
```
play_emotion
# move_head
# My custom tool defined locally
sweep_look
```
Tools are resolved first from Python files in the profile folder (custom tools), then from the core library `src/reachy_mini_conversation_app/tools/` (like `dance`, `head_tracking`).
**Custom tools:**
On top of built-in tools found in the core library, you can implement custom tools specific to your profile by adding Python files in the profile folder.
Custom tools must subclass `reachy_mini_conversation_app.tools.core_tools.Tool` (see `profiles/example/sweep_look.py`).
**Edit personalities from the UI:**
When running with `--gradio`, open the "Personality" accordion:
- Select among available profiles (folders under `profiles/`) or the built‑in default.
- Click "Apply" to update the current session instructions live.
- Create a new personality by entering a name and instructions text. It stores files under `profiles/<name>/` and copies `tools.txt` from the `default` profile.
Note: The "Personality" panel updates the conversation instructions. Tool sets are loaded at startup from `tools.txt` and are not hot‑reloaded.
</details>
<details>
<summary><b>Locked profile mode</b></summary>
To create a locked variant of the app that cannot switch profiles, edit `src/reachy_mini_conversation_app/config.py` and set the `LOCKED_PROFILE` constant to the desired profile name:
```python
LOCKED_PROFILE: str | None = "mars_rover" # Lock to this profile
```
When `LOCKED_PROFILE` is set, the app always uses that profile, ignoring saved startup settings, `REACHY_MINI_CUSTOM_PROFILE`, and the Gradio UI. The UI shows "(locked)" and disables all profile editing controls.
This is useful for creating dedicated clones of the app with a fixed personality. Clone scripts can simply edit this constant to lock the variant.
</details>
<details>
<summary><b>External profiles and tools</b></summary>
You can extend the app with profiles/tools stored outside the repository defaults.
- Core profiles are under `profiles/`.
- Core tools are under `src/reachy_mini_conversation_app/tools/`.
**Recommended layout:**
```text
external_content/
├── external_profiles/
│ └── my_profile/
│ ├── instructions.txt
│ ├── tools.txt # optional (see fallback behavior below)
│ └── voice.txt # optional
└── external_tools/
└── my_custom_tool.py
```
**Environment variables:**
Set these values in your `.env` when you want env-driven external profile/tool selection:
```env
# Optional fallback/manual profile selector:
REACHY_MINI_CUSTOM_PROFILE=my_profile
REACHY_MINI_EXTERNAL_PROFILES_DIRECTORY=./external_content/external_profiles
REACHY_MINI_EXTERNAL_TOOLS_DIRECTORY=./external_content/external_tools
# Optional convenience mode:
# AUTOLOAD_EXTERNAL_TOOLS=1
```
**Loading behavior:**
- **Default/strict mode**: `tools.txt` defines enabled tools explicitly. Every name in `tools.txt` must resolve to either a built-in tool (`src/reachy_mini_conversation_app/tools/`) or an external tool module in `REACHY_MINI_EXTERNAL_TOOLS_DIRECTORY`.
- **Convenience mode** (`AUTOLOAD_EXTERNAL_TOOLS=1`): all valid `*.py` tool files in `REACHY_MINI_EXTERNAL_TOOLS_DIRECTORY` are auto-added.
- **External profile fallback**: if the selected external profile has no `tools.txt`, the app falls back to built-in `profiles/default/tools.txt`.
This supports both:
1. Downloaded external tools used with built-in/default profile.
2. Downloaded external profiles used with built-in default tools.
</details>
<details>
<summary><b>Multiple robots on the same subnet</b></summary>
If you run multiple Reachy Mini daemons on the same network, use:
```bash
reachy-mini-conversation-app --robot-name <name>
```
`<name>` must match the daemon's `--robot-name` value so the app connects to the correct robot.
</details>
## Contributing
We welcome bug fixes, features, profiles, and documentation improvements. Please review our
[contribution guide](CONTRIBUTING.md) for branch conventions, quality checks, and PR workflow.
Quick start:
- Fork and clone the repo
- Follow the [installation steps](#installation) (include the `dev` dependency group)
- Run contributor checks listed in [CONTRIBUTING.md](CONTRIBUTING.md)
## License
Apache 2.0
HTTP 200
Content-Type: text/plain; charset=utf-8
# Skill: Create App
## When to Use
- User wants to create a new Reachy Mini application
- User asks how to structure an app
- User wants to publish an app to Hugging Face
## Important: Always Python Apps
**Always create Python apps using the app assistant.** Python apps are:
- Easily discoverable on Hugging Face
- Shareable via the robot's app store
- Can include web GUIs (via the `static/` folder)
JS-only apps are not yet supported for discovery/sharing. If the user needs a web UI, create a Python app with a web frontend in `static/`.
## Quick Check
If an app folder already exists with `README.md` containing `reachy_mini_python_app` tag, the app structure is probably already set up. In doubt, double check.
---
## Procedure
### Step 1: Use the App Assistant
**CRITICAL: Never create app folders manually.** Always use the assistant - it handles boilerplate, metadata tags, entry points, and proper structure. Manual creation leads to subtle issues that are hard to debug.
**IMPORTANT: Always use `--publish` unless the user explicitly requests a local-only app.** Publishing immediately creates a Git repo on Hugging Face, enabling proper version control and sharing from the start.
**If the command fails for any reason:** Ask the user to run it manually in their terminal rather than attempting complex workarounds.
```bash
# Default template - minimal blank app (recommended for most cases):
reachy-mini-app-assistant create <app_name> <path> --publish
# Conversation template - fork of the conversation app:
reachy-mini-app-assistant create --template conversation <app_name> <path> --publish
# Only if user explicitly wants local-only (no HF repo):
reachy-mini-app-assistant create <app_name> <path>
```
#### Which template to choose?
| Template | Use when |
|----------|----------|
| **default** | Most apps. Gives you a minimal working structure to build from scratch. |
| **conversation** | App needs LLM integration, speech, or making the robot talk. Forks the conversation app with a locked profile - includes audio pipeline, LLM tools, and all the plumbing already set up. |
**IMPORTANT: Both `app_name` AND `path` are required for non-interactive mode.** If either is omitted, the command will prompt interactively (which fails in non-TTY environments like Claude Code).
Options:
- `--template conversation` - Fork the conversation app (for LLM/speech apps)
- `--publish` - Create a Git repo on Hugging Face immediately **(always use this)**
- `--private` - Make the HF Space private (default is public)
**Prerequisite for `--publish`:** Must be logged in to Hugging Face first:
```bash
hf auth login
```
Example:
```bash
reachy-mini-app-assistant create my_app_name . --publish
reachy-mini-app-assistant create --template conversation my_assistant . --publish
```
### Step 2: Understand the Generated Structure
```
my_app/
├── index.html # HuggingFace Space landing page
├── style.css # Landing page styles
├── pyproject.toml # Package config (includes reachy_mini tag!)
├── README.md # Must contain reachy_mini tag in YAML frontmatter
├── .gitignore
└── my_app/
├── __init__.py
├── main.py # Your app code (run method)
└── static/ # Optional web UI
├── index.html
├── style.css
└── main.js
```
### Step 3: Development Workflow
1. **Create and publish immediately** with `--publish` to get a Git repo
2. **Develop iteratively** using standard git: `git add`, `git commit`, `git push`
3. **Validate** with `reachy-mini-app-assistant check /path/to/app`
### Step 4: Before Writing Code
Create a `plan.md` file in the app directory with:
- Your understanding of what the user wants
- Technical approach (components, patterns)
- Questions that need clarification
Wait for user to answer questions before implementing.
---
## Full Tutorial
For detailed guide with screenshots: https://huggingface.co/blog/pollen-robotics/make-and-publish-your-reachy-mini-apps
---
## Common Patterns to Consider
When planning the app, consider which patterns apply:
| Pattern | When to use | Reference app |
|---------|-------------|---------------|
| Web UI | User needs visual interface | Most apps have optional static/ folder |
| No-GUI (antenna trigger) | Simple apps, kiosk mode | `reachy_mini_simon` |
| Control loop | Real-time reactivity needed | `reachy_mini_conversation_app/moves.py` |
| Head as controller | Games, recording | `fire_nation_attacked`, `marionette` |
| LLM integration | AI-powered behavior | `reachy_mini_conversation_app` |
---
## Creating a Beautiful Landing Page (index.html)
The root `index.html` is the landing page shown on Hugging Face Spaces. A well-designed landing page makes your app look professional and helps users understand what it does.
**Reference template:** Use the [Marionette app](https://huggingface.co/spaces/RemiFabre/marionette) as a template for the structure and styling.
### Structure
A good landing page has three sections:
1. **Hero Section** - Video/image + title + description
2. **Technical Section** - "How it works" steps + features
3. **Footer** - Resources, links, social media
### Key Elements
```
index.html
├── Hero Section
│ ├── Demo video (autoplay, loop, muted)
│ ├── App emoji + title
│ ├── Tags (categories)
│ └── Short description (1-2 sentences)
│
├── Technical Section
│ ├── "How it works" (numbered steps)
│ └── "Features" or additional info
│
└── Footer
├── Resources (docs, troubleshooting)
├── Reachy Mini Apps links
└── Social media icons
```
### Assets
Put demo videos/images in `<app_name>/assets/`:
```
my_app/
├── index.html # References my_app/assets/demo.mp4
├── my_app/
│ ├── assets/
│ │ └── demo.mp4 # Demo video
│ ├── main.py
│ └── static/ # Web UI (if any)
```
Video tag example:
```html
<video autoplay loop muted playsinline>
<source src="my_app/assets/demo.mp4" type="video/mp4" />
</video>
```
### Styling
Use the Marionette CSS as a starting point:
- Inter font from Google Fonts
- CSS variables for colors (`--primary: #FF9900` for Pollen orange)
- Responsive grid layout (`grid-template-columns: 1fr 1fr` on desktop)
- Numbered steps with orange circles
- Footer with social media SVG icons
### Quick Checklist
- [ ] Hero with video/image showing the app in action
- [ ] Clear title and short tagline
- [ ] Tags describing the app's purpose
- [ ] "How it works" numbered steps (4 steps max)
- [ ] Footer with standard Pollen/Reachy links
- [ ] Responsive design (works on mobile)
- [ ] All CSS inline in `<style>` tag (no external stylesheet needed)
Yes — the Reachy Mini app directory IS Hugging Face Spaces with the reachy_mini tag. You publish by pushing the app to a Hugging Face Space and the robot's dashboard discovers it. Let me make our app publish-ready (it's missing the HF Space landing page) and walk you through it.
The app folder is now publish-ready (HF Space frontmatter, landing page, styles, .gitignore). Here are the two ways to publish — the easy path uses Pollen's official tool, the manual path is just git push to a Hugging Face Space.
[Open the app folder](computer:///Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning/README.md)
There's no separate "app store" to submit to. The directory is just Hugging Face Spaces tagged with reachy_mini. The robot's dashboard scans HF for spaces with that tag and lists them. So to publish you literally just need an HF Space with the right README frontmatter (which our README already has) — git push and you're listed.
This avoids the broken pip problem because we'll go through uv (faster, doesn't need a system pip).
# 1. Install uv (modern Python installer/runner)brew install uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Make a Python 3.12 envcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"uv venv --python 3.12source .venv/bin/activate
# 3. Install the SDK + the app-assistant CLIuv pip install reachy-mini reachy-mini-app-assistant huggingface_hub
# 4. Log in to Hugging Face (creates the Space under your account)hf auth login # paste a token from https://huggingface.co/settings/tokens (write scope)
# 5. Validate our app's structure first (catches missing frontmatter, bad entry points, etc.)reachy-mini-app-assistant check .
# 6. Push it as a Space — this creates huggingface.co/spaces/clementdelangue/good-morning# and the Reachy Mini dashboard will pick it up.reachy-mini-app-assistant publish .If publish isn't a subcommand on your version, regenerate the scaffold with our code merged in:
reachy-mini-app-assistant create good_morning ../tmp_good_morning --publish# then copy our src/good_morning_app/main.py, index.html, style.css over the scaffold and `git push`Cleanest if the assistant gives you any trouble:
# 1. Create an empty Space on the web:# https://huggingface.co/new-space → SDK = "Static" → name "good-morning"
# 2. From inside the app folder:cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/.../reachy-mini-good-morning"git initgit add .git commit -m "Initial commit"
# 3. Push to the Space (replace clementdelangue with your HF username if different)git remote add origin https://huggingface.co/spaces/clementdelangue/good-morninggit push -u origin mainYou'll be prompted for credentials — username is your HF handle, password is an HF access token (not your password) from https://huggingface.co/settings/tokens. Pick write scope.
https://huggingface.co/spaces/<your-username>/good-morning — you should see the orange landing page.Good Morning should show up because of the reachy_mini and reachy_mini_python_app tags in the README. One-click install from there, then launch it.hf: command not found after uv pip install huggingface_hub → use huggingface-cli login instead (older CLI name).Space not appearing in the dashboard → double-check the README frontmatter has both reachy_mini and reachy_mini_python_app tags. Ours does.git push rejected for "LFS required" → your audio cache file got committed. Our .gitignore excludes good_morning.wav so it should be fine; if you generated it before adding the .gitignore, run git rm --cached src/good_morning_app/good_morning.wav and recommit.Want me to also add a short demo video/GIF slot to the landing page (reserved spot you can drop a clip into later), or change the published Space name from good-morning to something else?
The reachy-mini-app-assistant package isn't on PyPI — it's an internal Pollen tool that's hard to get. Skip it. Path B (just git push to a Hugging Face Space) is actually simpler and gets the same result, since the "app directory" is just HF Spaces with the right tag.
Let's go straight to publishing. Run these in the same terminal where you still have (reachy-mini-good-morning) active:
1. Install only what you actually need
uv pip install huggingface_hub2. Get a Hugging Face write token
Open https://huggingface.co/settings/tokens → New token → name it whatever, select Write access → copy the token (starts with hf_…). Keep it on your clipboard.
3. Log in
hf auth loginIf that says "command not found", use the older name:
huggingface-cli loginPaste the token when prompted. Answer n to "Add token as git credential?" — we'll handle git auth manually below (cleaner).
4. Create the empty Space on the web
Open https://huggingface.co/new-space and:
good-morningClick Create Space. It'll show you a fresh empty repo page — leave it open.
5. Push our folder to it
Back in the terminal:
# you should still be in reachy-mini-good-morning/git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"
# Embed the token in the remote URL so git won't prompt — replace hf_xxx with your tokengit remote add origin https://clementdelangue:[REDACTED]/spaces/clementdelangue/good-morninggit push -u origin main(After the push works, you can swap the remote back to a tokenless URL with git remote set-url origin https://huggingface.co/spaces/clementdelangue/good-morning so the token isn't sitting in .git/config.)
6. Verify
https://huggingface.co/spaces/clementdelangue/good-morning — you should see the orange landing page rendered.reachy_mini tag — Good Morning should appear within a minute or two. Click install, then launch.That's it — the app is now in the directory.
If git push errors out, paste me the exact message. The two common ones:
remote: Invalid username or password → token is wrong or doesn't have write scope. Regenerate with the right scope.pre-receive hook declined with an LFS message → something big slipped in. Run git ls-files | xargs ls -la | sort -k5 -n | tail to see the largest file and git rm --cached <file> it before re-pushing.Already logged in. Now the remaining steps. You're in ~ — first hop back into the project folder, then create the Space and push.
1. Create the empty Space directly from the CLI
You don't need the web UI, since huggingface_hub is installed. From anywhere:
python -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"That should print something like https://huggingface.co/spaces/clementdelangue/good-morning. If it errors with "already exists", great, it's ready.
2. cd back into the project and push
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
source .venv/bin/activate # in case the shell is fresh
git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"git remote add origin https://huggingface.co/spaces/clementdelangue/good-morninggit push -u origin mainWhen git push prompts you:
clementdelanguehf_…) from https://huggingface.co/settings/tokens — needs Write scope. (Your normal HF password won't work.)If it complains about credentials repeatedly, set the token inline in the URL once:
git remote set-url origin https://clementdelangue:[REDACTED]/spaces/clementdelangue/good-morninggit push -u origin main# then strip the token back out:git remote set-url origin https://huggingface.co/spaces/clementdelangue/good-morning3. Done — verify
Open https://huggingface.co/spaces/clementdelangue/good-morning — you'll see the orange landing page render. The Reachy Mini dashboard scans HF for the reachy_mini tag, so the app shows up there too within a minute.
Paste the output of step 1 (or any error) if you hit a snag.
Same python vs python3 thing — on macOS the binary is python3, not python. Use this:
python3 -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"If huggingface_hub was only installed inside the project's venv (likely), you'll get ModuleNotFoundError. Two fixes — pick one:
Option A — install it system-wide so any python works
python3 -m pip install --user --break-system-packages huggingface_hubpython3 -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"Option B — re-activate the venv first
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"source .venv/bin/activatepython -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"Inside the venv python exists; outside it, only python3 does. Either should print the new Space's URL. Paste me what it prints (or the error).
Space is live at https://huggingface.co/spaces/clem/good-morning (your HF username is clem, good to know — I'd assumed clementdelangue). The SSL warning is harmless.
Now push the code into it:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"git remote add origin https://huggingface.co/spaces/clem/good-morninggit push -u origin mainWhen git prompts for credentials:
clemhf_…) from https://huggingface.co/settings/tokens with Write scope. Your normal password won't work — has to be a token.If you'd rather skip the prompt entirely, embed the token in the remote once:
git remote set-url origin https://clem:[REDACTED]/spaces/clem/good-morninggit push -u origin main# then clean it back out so the token isn't sitting in .git/config:git remote set-url origin https://huggingface.co/spaces/clem/good-morningAfter the push lands, refresh https://huggingface.co/spaces/clem/good-morning — you'll see the orange landing page render, and the app will start showing up in your Reachy Mini's app dashboard within a minute or two (it polls HF for spaces with the reachy_mini tag).
Paste the git push output if anything errors.
Hugging Face auto-generates a starter README.md when a Space is created, so the remote already has one commit. Your local repo doesn't know about it. Easiest fix is to force-push and overwrite — you want your README (the one with reachy_mini tags) to win anyway, not the auto-generated one.
git push -u origin main --forceThat's it. Refresh https://huggingface.co/spaces/clem/good-morning and you should see the orange landing page.
(If you'd rather merge instead of overwrite — say, in case HF added something useful like LFS config — you'd run git pull origin main --rebase --allow-unrelated-histories first, but for a brand-new Space there's nothing worth keeping, so force-push is fine.)
dont find the app in the reachy mini app store
Let me check what actually got pushed and compare it against an app that IS showing up.
HTTP 200
Content-Type: text/plain; charset=utf-8
---
title: Good Morning
emoji: 👋
colorFrom: yellow
colorTo: red
sdk: static
pinned: false
short_description: Press a button and Reachy Mini says good morning.
tags:
- reachy_mini
- reachy_mini_python_app
---
# Good Morning — a Reachy Mini app
Press a button → Reachy Mini says **"Good morning!"**.
You get two buttons for the price of one:
- **A big on-screen button** at `http://<robot>:7860/` (also embedded in the
Reachy Mini dashboard once installed).
- **Either of the robot's antennas** — Reachy Mini doesn't have a physical
push-button, but the antennas are spec'd as physical buttons. Push one
and it triggers the same greeting.
## Install
From the project root:
```bash
# Same Python env you use for the reachy-mini SDK (3.10–3.12).
pip install -e .
```
This installs the `reachy_mini_apps` entry point `good_morning`, so the app
shows up in the Reachy Mini dashboard's app list.
## Run
Two options:
**1) Through the Reachy Mini app system** (preferred — this is what makes it
"an app"). Once installed, start it the way you start any Reachy Mini app
(dashboard, or `reachy-mini-app-launcher good_morning`, etc.).
**2) Standalone, for quick testing:**
```bash
python -m good_morning_app.main
```
Then open `http://localhost:7860/` and click the button. Pushing either
antenna also triggers it.
## How it works
The first time the app starts, `pyttsx3` synthesises "Good morning!" once
and caches it as `good_morning.wav` next to the code. On every press we:
1. Load the cached WAV → mono float32, resampled to 16 kHz, shape `(N, 1)`.
2. Push it to the speaker with
`reachy_mini.media.push_audio_sample(samples)` (the canonical TTS path
per the SDK docs — there is no `.speak()` helper).
3. Sleep for the audio's duration since `push_audio_sample` is
non-blocking.
Antennas are read with `reachy_mini.get_current_joint_positions()`; a
deflection over 0.3 rad on either antenna counts as a press, with a 1.5s
debounce.
## Tweaking
Open `src/good_morning_app/main.py`:
- `PHRASE` — change what it says.
- `ANTENNA_THRESHOLD` — make antenna presses more / less sensitive.
- `PRESS_COOLDOWN_S` — debounce window.
- `WEB_PORT` — where the button page lives.
To swap the TTS engine (e.g. for Piper or an online voice), replace
`_synthesize_phrase()` — anything that produces a WAV will work; the loader
handles mono/stereo and resampling.
## Project layout
```
reachy-mini-good-morning/
├── pyproject.toml # registers the reachy_mini_apps entry point
├── README.md # this file (HF Space frontmatter at the top)
└── src/
└── good_morning_app/
├── __init__.py
└── main.py # GoodMorningApp(ReachyMiniApp)
```
## References
- Reachy Mini SDK: <https://github.com/pollen-robotics/reachy_mini>
- Audio API (`push_audio_sample`):
<https://github.com/pollen-robotics/reachy_mini/blob/main/docs/source/SDK/python-sdk.md>
- Reference app (entry-point shape, `ReachyMiniApp` subclass):
<https://github.com/pollen-robotics/reachy_mini_conversation_app>
HTTP 200
Content-Type: text/plain; charset=utf-8
---
title: Gemini Config Helper
emoji: "🔧"
colorFrom: yellow
colorTo: yellow
sdk: static
pinned: false
short_description: Configure Google API key for Reachy Mini Gemini app
tags:
- reachy_mini
- reachy_mini_python_app
---
# Gemini Config Helper
A simple configuration helper for the Reachy Mini Gemini app.
This app provides a web UI to set your Google API key for the Gemini app without needing SSH access to the robot.
## Usage
1. Install this app on your Reachy Mini from the dashboard
2. Start the app - it will open a settings page at http://reachy-mini.local:8043
3. Enter your Google API key and click Save
4. Stop this app and start the Gemini app - it will now have your API key configured
## Get a Google API Key
Visit https://aistudio.google.com/apikey to create your free API key.
HTTP 200
Content-Type: application/json; charset=utf-8
[{"_id":"69f2106c14f3cca430340ec9","id":"clem/good-morning","author":"clem","cardData":{"title":"Good Morning","emoji":"👋","colorFrom":"yellow","colorTo":"red","sdk":"static","pinned":false,"short_description":"Press a button and Reachy Mini says good morning.","tags":["reachy_mini","reachy_mini_python_app"]},"lastModified":"2026-04-29T14:07:23.000Z","likes":0,"trendingScore":0,"private":false,"sha":"b6e7ff42752788b5f56999a1e4b793ee72924b32","subdomain":"clem-good-morning","sdk":"static","tags":["static","reachy_mini","reachy_mini_python_app","region:us"],"createdAt":"2026-04-29T14:06:36.000Z","siblings":[{"rfilename":".gitignore"},{"rfilename":"README.md"},{"rfilename":"index.html"},{"rfilename":"pyproject.toml"},{"rfilename":"src/good_morning_app/__init__.py"},{"rfilename":"src/good_morning_app/main.py"},{"rfilename":"style.css"}]}]Good news — your Space is correctly tagged. The HF API call confirms clem/good-morning is indexed under the reachy_mini and reachy_mini_python_app tags, identical setup to the apps that DO show up (e.g. gemini-config-helper):
"id": "clem/good-morning","tags": ["static","reachy_mini","reachy_mini_python_app","region:us"],"sdk": "static"So the app store will see it. The reason the dashboard's search isn't finding it right now is just the dashboard's local cache. The "Discover Apps" widget pre-loaded its 176-app list before you pushed; it's not re-querying HF on every keystroke.
Three things to try, in order:
1. Force a refresh
Close the "Discover Apps" panel and reopen it (the X in the top right, then click Discover Apps again). On most builds that re-fetches the list. If that doesn't update it, fully quit and relaunch the Reachy Mini desktop app.
2. Wait a couple of minutes
HF's tag indexing can lag a minute or two behind a fresh push. Your space was created at 14:06 and last modified at 14:07 UTC — give it ~5 minutes from then and try again.
3. Filter by the "Robotics" tag
Click the Robotics (9) tag chip — your app has the right tags but might be filtered out of the default view by some other heuristic (likes/trending). Same goes for typing the exact owner: clem/good-morning instead of "Good".
If after a relaunch + 5 min wait it still doesn't appear, two things worth double-checking:
"private": false, so this should be fine.I'd give it a relaunch + a minute and try again — 90% chance that's all it is.
getting this when I press "get a reachy mini" in the space
That's the HF Space iframing GitHub, which GitHub refuses to be embedded in. Two-line fix in index.html: add target="_blank" to any link that leaves the page, and point "Get a Reachy Mini" at the Pollen product page instead of the SDK repo (more appropriate for that CTA anyway).
Push the update:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.htmlgit commit -m "Open external links in new tab; fix CTA destination"git pushRefresh the Space (the App tab usually needs a hard reload — Cmd+Shift+R) and the orange button now opens https://www.pollen-robotics.com/reachy-mini/ in a new tab. The footer links to GitHub/Pollen also pop out instead of trying to load inside the iframe.
For the bigger picture: any external link inside an HF Space's index.html will hit this — sites like github.com, twitter.com, openai.com all set X-Frame-Options: DENY. Always use target="_blank" rel="noopener" for outbound links from a Space.
I don't see the button to tap for reachy to say "good morning"
[Image: original 2600x1329, displayed at 2576x1317. Multiply coordinates by 1.01 to map to original image.]
Right — that landing page is the catalog listing (the orange page that shows up when someone browses the app store). The actual button only appears once the app is installed and running on a Reachy Mini — at that point it gets embedded in the dashboard's App tab. The HF Space is sdk: static so it can't talk to the robot directly; if it could host the live button, anyone visiting the page would be triggering your robot.
Let me make that obvious on the page — add an explicit "How to use" section, and a visual preview of what the in-robot button looks like:
Push the changes:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.html style.cssgit commit -m "Add preview button + clear install/use instructions on landing page"git pushHard-refresh the Space (Cmd+Shift+R) and you'll now see:
The actual functional button only exists in two places:
http://localhost:7860/ if you're running the Python app directly on a machine connected to the robot.The HF Space can't host the working button itself — it's a static page, has no way to reach your robot, and even if it could, it'd be triggering anyone's robot who happens to view the page.
on the desktop app I dont get the button
The app is running (green border + stop button on the good_morning tile, "App Running" badge under the robot). Just need to find where the desktop app surfaces our web UI. Three things to try, in order — most likely the first one is enough.
1. Click the green good_morning tile
The running-app tile usually expands into a panel that embeds the app's web UI (custom_app_url). Click on the tile itself, not the red stop button.
2. Confirm the web server is actually up
Open this in any browser on the same machine:
http://localhost:7860/If you see the orange "Good morning!" page, FastAPI is running fine and the only question is where the desktop app exposes it. If the page doesn't load, the FastAPI server didn't start — see step 4 below.
If localhost doesn't work, try the robot's hostname:
http://reachy-mini.local:7860/Either of those is the real, live button. You can use it from a browser tab even if the desktop app never embeds it.
3. Look for an "App" tab or "Open in browser" link
In the desktop app, depending on the version, you'll see either:
The tab probably scrolled off the screenshot — try scrolling the right column or resizing the window.
4. If http://localhost:7860/ doesn't load
Check the app's logs for our app's output. In the LOGS panel of the dashboard, filter for good_morning — you should see a line like:
[good_morning] Good Morning app starting (web on :7860, antennas armed)If you don't see that line at all, the app failed to start — paste the last 20 lines of logs and I'll diagnose. Most likely culprits: pyttsx3 couldn't synthesize on first run (macOS may need a permission prompt), or fastapi/uvicorn aren't in the dependencies the dashboard installed.
The quickest sanity check is step 2 — http://localhost:7860/ in your browser. Tell me what that shows and we'll go from there.
see it on localhost but not on the desktop app. This is the logs I get
✓ Install good-morning completed 10:22:32 Start good_morning 10:22:36 [app] reachy_mini.apps.manager.runner - WARNING - :128: RuntimeWarning: 'good_morning_app.main' found in sys.modules after import of package 'good_morning_app', but prior to execution of 'good_morning_app.main'; this may result in unpredictable behaviour 10:22:37 [app] reachy_mini.apps.manager.runner - WARNING - 2026-04-29 10:22:38,685 [good_morning] INFO: Good Morning app starting (web on :7860, antennas armed) 10:22:38 [app] [2m2026-04-29T14:22:42.893108Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:22:42 [app] [2m2026-04-29T14:22:42.893135Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:22:42 [app] [2m2026-04-29T14:22:42.912286Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:22:42 [app] [2m2026-04-29T14:22:42.940792Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:22:42 [app] reachy_mini.apps.manager.runner - WARNING - 2026-04-29 10:22:43,387 [reachy_mini.media.cameragstreamer] INFO: Pipeline latency (live=True, min_latency=20000000, max_latency=1000000000) 10:22:43 [app] reachy_mini.apps.manager.runner - WARNING - 2026-04-29 10:22:44,388 [reachymini.media.audio_gstreamer] INFO: Audio pipeline latency (live=False, min_latency=0, max_latency=18446744073709551615) 10:22:44 [app] reachy_mini.apps.manager.runner - WARNING - 2026-04-29 10:22:55,972 [good_morning] INFO: Triggered by web 10:22:55 [app] reachy_mini.apps.manager.runner - WARNING - 2026-04-29 10:22:55,972 [good_morning] INFO: Saying 'Good morning!' (0.73s) 10:22:55 [app] [2m2026-04-29T14:23:12.894588Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:23:12 [app] [2m2026-04-29T14:23:12.894611Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:23:12 [app] [2m2026-04-29T14:23:12.914752Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b"")) 10:23:12 [app] [2m2026-04-29T14:23:12.941428Z[0m [32m INFO[0m ThreadId(01) [2mgst_plugin_webrtc_signalling::server[0m[2m:[0m Received message Ok(Pong(b""))
Excellent — the logs prove the app is working end-to-end:
[good_morning] INFO: Good Morning app starting (web on :7860, antennas armed)[good_morning] INFO: Triggered by web[good_morning] INFO: Saying 'Good morning!' (0.73s)So the FastAPI server, the web button, and the audio playback to the speaker all work. The only remaining question is why the desktop app isn't iframing the UI in its window — the button is right there at localhost:7860, but the dashboard isn't embedding it.
Two things to try inside the desktop app:
Click the green good_morning tile itself (not the red stop button). On most builds, the running app tile is the clickable target — it expands to a panel or switches to a view that embeds custom_app_url. The screenshot you shared looks like the "Home" view; the embedded UI usually lives one click deeper.
Look for a top nav tab. v0.9.29 has tabs like "Home / Apps / Settings" along the top — your screenshot may have cropped them. The "Apps" tab is typically where the running app's UI gets iframed.
If neither does it, the desktop app on your version simply doesn't iframe custom_app_url from the home screen, and you're meant to open it in a browser. That's fine — http://localhost:7860/ (or http://reachy-mini.local:7860/ from any other device on the same network) is the real button. It's the exact same FastAPI route the dashboard would embed.
A small quality-of-life patch I can make: I can change the app to log a clickable URL when it starts, so the LOGS panel shows something like → Open the button at http://localhost:7860/ and you can click straight from there. Want me to push that?
For now though, you have a fully working Reachy Mini app. To recap what you've built:
https://huggingface.co/spaces/clem/good-morning with a tagged Space, a landing page, and the right entry point so the dashboard discovers it.Push either antenna and check the logs — you should also see Triggered by antenna. That's the second button working.
can't click on the green and there's no top nav tab. Is there a way to force a redirect to the localhost page maybe?
Yes — let me add a guaranteed fallback that pops the localhost page in your default browser when the app starts. That way you don't depend on the dashboard iframing it correctly.
HTTP 200
Content-Type: text/plain; charset=utf-8
"""Entrypoint for the Reachy Mini conversation app."""
import os
import sys
import time
import asyncio
import argparse
import threading
from typing import Any, Dict, List, Optional
from pathlib import Path
import gradio as gr
from fastapi import FastAPI
from fastrtc import Stream
from gradio.utils import get_space
from reachy_mini import ReachyMini, ReachyMiniApp
from reachy_mini_conversation_app.utils import (
CameraVisionInitializationError,
parse_args,
setup_logger,
initialize_camera_and_vision,
log_connection_troubleshooting,
)
def update_chatbot(chatbot: List[Dict[str, Any]], response: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Update the chatbot with AdditionalOutputs."""
chatbot.append(response)
return chatbot
def main() -> None:
"""Entrypoint for the Reachy Mini conversation app."""
args, _ = parse_args()
run(args)
def run(
args: argparse.Namespace,
robot: ReachyMini = None,
app_stop_event: Optional[threading.Event] = None,
settings_app: Optional[FastAPI] = None,
instance_path: Optional[str] = None,
) -> None:
"""Run the Reachy Mini conversation app."""
# Putting these dependencies here makes the dashboard faster to load when the conversation app is installed
from reachy_mini_conversation_app.moves import MovementManager
from reachy_mini_conversation_app.config import config, is_gemini_model, refresh_runtime_config_from_env
from reachy_mini_conversation_app.startup_settings import (
StartupSettings,
load_startup_settings_into_runtime,
)
logger = setup_logger(args.debug)
logger.info("Starting Reachy Mini Conversation App")
startup_settings = StartupSettings()
if instance_path is not None:
try:
from dotenv import load_dotenv
env_path = Path(instance_path) / ".env"
if env_path.exists():
load_dotenv(dotenv_path=str(env_path), override=True)
refresh_runtime_config_from_env()
logger.info("Loaded instance configuration from %s", env_path)
except Exception as e:
logger.warning("Failed to load instance configuration: %s", e)
try:
startup_settings = load_startup_settings_into_runtime(instance_path)
except Exception as e:
logger.warning("Failed to load startup settings: %s", e)
from reachy_mini_conversation_app.console import LocalStream
from reachy_mini_conversation_app.tools.core_tools import ToolDependencies
from reachy_mini_conversation_app.audio.head_wobbler import HeadWobbler
if args.no_camera and args.head_tracker is not None:
logger.warning("Head tracking disabled: --no-camera flag is set. Remove --no-camera to enable head tracking.")
if robot is None:
try:
robot_kwargs = {}
if args.robot_name is not None:
robot_kwargs["robot_name"] = args.robot_name
logger.info("Initializing ReachyMini (SDK will auto-detect appropriate backend)")
robot = ReachyMini(**robot_kwargs)
except TimeoutError as e:
logger.error(f"Connection timeout: Failed to connect to Reachy Mini daemon. Details: {e}")
log_connection_troubleshooting(logger, args.robot_name)
sys.exit(1)
except ConnectionError as e:
logger.error(f"Connection failed: Unable to establish connection to Reachy Mini. Details: {e}")
log_connection_troubleshooting(logger, args.robot_name)
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error during robot initialization: {type(e).__name__}: {e}")
logger.error("Please check your configuration and try again.")
sys.exit(1)
# Auto-enable Gradio in simulation mode (both MuJoCo for daemon and mockup-sim for desktop app)
status = robot.client.get_status()
if isinstance(status, dict):
simulation_enabled = status.get("simulation_enabled", False)
mockup_sim_enabled = status.get("mockup_sim_enabled", False)
else:
simulation_enabled = getattr(status, "simulation_enabled", False)
mockup_sim_enabled = getattr(status, "mockup_sim_enabled", False)
is_simulation = simulation_enabled or mockup_sim_enabled
if is_simulation and not args.gradio:
logger.info("Simulation mode detected. Automatically enabling gradio flag.")
args.gradio = True
try:
camera_worker, vision_processor = initialize_camera_and_vision(args, robot)
except CameraVisionInitializationError as e:
logger.error("Failed to initialize camera/vision: %s", e)
sys.exit(1)
movement_manager = MovementManager(
current_robot=robot,
camera_worker=camera_worker,
)
head_wobbler = HeadWobbler(set_speech_offsets=movement_manager.set_speech_offsets)
deps = ToolDependencies(
reachy_mini=robot,
movement_manager=movement_manager,
camera_worker=camera_worker,
vision_processor=vision_processor,
head_wobbler=head_wobbler,
)
current_file_path = os.path.dirname(os.path.abspath(__file__))
logger.debug(f"Current file absolute path: {current_file_path}")
chatbot = gr.Chatbot(
type="messages",
resizable=True,
avatar_images=(
os.path.join(current_file_path, "images", "user_avatar.png"),
os.path.join(current_file_path, "images", "reachymini_avatar.png"),
),
)
logger.debug(f"Chatbot avatar images: {chatbot.avatar_images}")
if is_gemini_model():
from reachy_mini_conversation_app.gemini_live import GeminiLiveHandler
logger.info("Using Gemini Live handler for model: %s", config.MODEL_NAME)
handler = GeminiLiveHandler(
deps,
gradio_mode=args.gradio,
instance_path=instance_path,
startup_voice=startup_settings.voice,
)
else:
from reachy_mini_conversation_app.openai_realtime import OpenaiRealtimeHandler
logger.info("Using OpenAI Realtime handler for model: %s", config.MODEL_NAME)
handler = OpenaiRealtimeHandler(
deps,
gradio_mode=args.gradio,
instance_path=instance_path,
startup_voice=startup_settings.voice,
) # type: ignore[assignment]
stream_manager: gr.Blocks | LocalStream | None = None
if args.gradio:
uses_gemini_backend = is_gemini_model()
api_key_textbox = gr.Textbox(
label="GEMINI_API_KEY" if uses_gemini_backend else "OPENAI API Key",
type="password",
value=(os.getenv("GEMINI_API_KEY") if uses_gemini_backend else os.getenv("OPENAI_API_KEY"))
if not get_space()
else "",
)
from reachy_mini_conversation_app.gradio_personality import PersonalityUI
personality_ui = PersonalityUI()
personality_ui.create_components()
stream = Stream(
handler=handler,
mode="send-receive",
modality="audio",
additional_inputs=[
chatbot,
api_key_textbox,
*personality_ui.additional_inputs_ordered(),
],
additional_outputs=[chatbot],
additional_outputs_handler=update_chatbot,
ui_args={"title": "Talk with Reachy Mini"},
)
stream_manager = stream.ui
if not settings_app:
app = FastAPI()
else:
app = settings_app
personality_ui.wire_events(handler, stream_manager)
app = gr.mount_gradio_app(app, stream.ui, path="/")
else:
# In headless mode, wire settings_app + instance_path to console LocalStream
stream_manager = LocalStream(
handler,
robot,
settings_app=settings_app,
instance_path=instance_path,
)
# Each async service → its own thread/loop
movement_manager.start()
head_wobbler.start()
if camera_worker:
camera_worker.start()
def poll_stop_event() -> None:
"""Poll the stop event to allow graceful shutdown."""
if app_stop_event is not None:
app_stop_event.wait()
logger.info("App stop event detected, shutting down...")
try:
stream_manager.close()
except Exception as e:
logger.error(f"Error while closing stream manager: {e}")
if app_stop_event:
threading.Thread(target=poll_stop_event, daemon=True).start()
try:
stream_manager.launch()
except KeyboardInterrupt:
logger.info("Keyboard interruption in main thread... closing server.")
finally:
movement_manager.stop()
head_wobbler.stop()
if camera_worker:
camera_worker.stop()
# Ensure media is explicitly closed before disconnecting
try:
robot.media.close()
except Exception as e:
logger.debug(f"Error closing media during shutdown: {e}")
# prevent connection to keep alive some threads
robot.client.disconnect()
time.sleep(1)
logger.info("Shutdown complete.")
class ReachyMiniConversationApp(ReachyMiniApp): # type: ignore[misc]
"""Reachy Mini Apps entry point for the conversation app."""
custom_app_url = "http://0.0.0.0:7860/"
dont_start_webserver = False
def run(self, reachy_mini: ReachyMini, stop_event: threading.Event) -> None:
"""Run the Reachy Mini conversation app."""
asyncio.set_event_loop(asyncio.new_event_loop())
args, _ = parse_args()
instance_path = self._get_instance_path().parent
run(
args,
robot=reachy_mini,
app_stop_event=stop_event,
settings_app=self.settings_app,
instance_path=instance_path,
)
if __name__ == "__main__":
app = ReachyMiniConversationApp()
try:
app.wrapped_run()
except KeyboardInterrupt:
app.stop()
Got it — the cleanest fix is to have the app pop your default browser open to the URL when it starts. Then you don't depend on the desktop app's iframe behavior at all.
OK
Compiles clean. Here's what to do:
1. Push the update
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.pygit commit -m "Auto-open the button page in the user's browser on app start"git push2. Re-install on the robot
In the desktop app, hit Stop on the running good_morning tile, then in the Discover Apps panel uninstall + reinstall (or pull-update if the dashboard offers that — the check-updates line in your earlier logs suggests it does).
3. Start it again
When you press Start on good_morning, two things will happen:
Good Morning app starting (web on :7860, antennas armed) line in LOGS.Opening the button page in your browser → http://localhost:7860/ appears in LOGS, and your default browser pops a tab with the orange button. Click it → robot says good morning.What this does under the hood: a small thread polls http://localhost:7860/healthz (the endpoint already in the FastAPI app) until it answers 200 — so it only opens the browser once the server is actually ready, never before — then calls Python's webbrowser.open(). That maps to open on macOS, so it pops your default browser straight to the button page. Independent of whether the desktop app iframes custom_app_url or not.
I also changed custom_app_url from http://0.0.0.0:7860/ to http://localhost:7860/. 0.0.0.0 is a valid bind address but not a valid URL for a browser to navigate to, which may be why the dashboard's iframe was silently failing on your version.
If after the update you'd rather the browser NOT open automatically (say, you've found where the dashboard iframes it), it's a one-line tweak — just delete the _open_browser_when_ready thread block in run(). Let me know how the next launch goes.
great, can you update so that the text said is "Good morning Clem. Today is a good day to help more people become AI builders, not just AI users" instead of just "good morning"? + update the space to remove the mention of the robot's antennas (it's not working) + add the fact that you have to go to localhost when the app is running to find the button? Also mention at the top that this is the first reachy mini app created for the new version of reachy mini by clem delangue, cofounder and ceo of hugging face
On it. Four files to update — phrase in the app, plus the README, landing page, and any antenna mentions across the board. I'll also make the audio cache auto-invalidate on phrase change so you don't have to clear it manually.
Now the landing page — remove all antenna mentions, add the Clem credit banner, and add the localhost callout:
Now strip the antennas out of the "How it works" and "Features" sections:
Now style the new "First!" credit banner:
Now the README — same edits, plus update the Space's short_description:
Now also clean up the in-app web page text — it still says "or push an antenna":
Quick syntax check, then push:
Python OK
src/good_morning_app/main.py:17
User-facing files (README, index.html, style.css) are clean — no antenna mentions. The 17 references in main.py are internal: the antenna-watching code path still exists silently in the background but isn't promoted anywhere visible. Let me also clean the top-level docstring in main.py since people will read it:
OK
Compiles clean. Push everything:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.md index.html style.css src/good_morning_app/main.pygit commit -m "New phrase, Clem credit, drop antenna mentions, document localhost:7860"git pushThen on the robot: stop the app → reinstall (or update if the dashboard offers it) → start. On launch your browser will open to localhost:7860, click the button, and Reachy Mini will speak the new line.
Summary of what changed:
The spoken phrase is now "Good morning Clem. Today is a good day to help more people become AI builders, not just AI users." — about 5 seconds of audio. The cache file is hashed off the phrase content (good_morning.<hash>.wav) so the moment you change PHRASE, the old WAV is ignored and a fresh one gets synthesised on next start. No manual cache-clearing needed.
The HF Space landing page now leads with a "First!" credit banner crediting you as co-founder and CEO of Hugging Face, links to your HF profile, and the lede quotes the full new phrase. The "How to use it on your robot" steps now point at http://localhost:7860/ for the actual button instead of mentioning a non-working dashboard tab. Every antenna reference is gone from the README, the landing page, the in-app web UI status text, and the top-of-file docstring — antennas are no longer advertised anywhere a user will look.
HF Spaces caps short_description at 60 chars and ours was 64. Tighten it and amend the rejected commit:
51 chars: First app for the new Reachy Mini, by Clem Delangue
51 chars, comfortably under the limit. Now amend and push:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.mdgit commit --amend --no-editgit push--amend --no-edit rewrites the rejected commit with the fix and keeps the same message. The push should go through this time.
If the prompt about identity (git config --global --edit) was on screen earlier, it's a separate, unrelated nag — git is just letting you know it inferred your name/email from the system. You can ignore it for now, or set them explicitly later with:
git config --global user.name "Clem Delangue"git config --global user.email "[REDACTED]"Doesn't block the push.
great, can you update the text to "Good morning. Today is a good day to help more people become AI builders, not just AI users thanks to open-source!"
OK
All three files updated and main.py compiles. Push:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.py index.html README.mdgit commit -m "Update phrase: drop name, add open-source line"git pushThen on the robot: stop the app and start it again. Because the cache filename is hashed off the phrase, the old WAV is automatically ignored and a fresh one gets synthesised on first press — no manual cleanup. The Space's landing page will refresh as soon as the push lands.
also in the spaces landing page, can you keep just "Press a button and Reachy Mini greets you out loud" (remove the quote after). Don't change what the robot says when we press the button though
Done — only the landing page changed. The PHRASE constant in main.py is untouched, so the robot still says the full open-source line. Push:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.htmlgit commit -m "Trim quote from landing page lede"git pushcan you add the emoji sun as the app icon? (instead of this box)
Let me find out where the dashboard icon actually comes from — the box default suggests our 👋 emoji in the README isn't getting picked up. Let me check an app that DOES show a proper icon (hand_tracker_v2 shows 👋 in the screenshot):
HTTP 200
Content-Type: text/plain; charset=utf-8
---
title: Hand Tracker V2
emoji: 👋
colorFrom: yellow
colorTo: pink
sdk: static
pinned: false
short_description: Reachy Mini follows your hand !
tags:
- reachy_mini
- reachy_mini_python_app
---HTTP 200
Content-Type: text/plain; charset=utf-8
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "hand_tracker_v2"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"reachy-mini",
"reachy-mini-toolbox[vision]>=1.1.2"
]
keywords = ["reachy-mini-app"]
[project.entry-points."reachy_mini_apps"]
hand_tracker_v2 = "hand_tracker_v2.main:HandTrackerV2"
[tool.setuptools]
package-dir = { "" = "." }
include-package-data = true
[tool.setuptools.packages.find]
where = ["."]
[tool.setuptools.package-data]
hand_tracker_v2 = ["**/*"] # Also include all non-.py filesHTTP 200
Content-Type: text/plain; charset=utf-8
---
title: Reachy Phone Home
emoji: 📱
colorFrom: yellow
colorTo: blue
sdk: static
pinned: false
short_description: Phone focus companion for Reachy Mini
tags:
- reachy_mini
- reachy_mini_python_app
---
# reachy_phone_home
Phone focus companion for Reachy Mini. Reachy watches for your phone, encourages focus, and responds with movements.

## How it works
- Uses Reachy Mini camera + YOLO phone/person detection.
- Tracks phone use and triggers movements after configured heartbeats.
- Includes a manual simulation app for development.
## Run locally
```bash
python reachy_phone_home_app.py
```
The YOLO26 weights are downloaded automatically on first run.
## Configure
Key flags (examples):
```bash
python reachy_phone_home_app.py --conf 0.15 --process-every 1
```
## Publishing
Use the Reachy Mini App Assistant to check and publish this app.
## License
This repository is licensed under the Apache License 2.0. It uses Ultralytics YOLO
for detection, which is licensed under AGPL-3.0. Use of the Ultralytics component
is subject to the AGPL-3.0 terms. See `NOTICE` for details.
The dashboard reads the icon from the emoji field in the README's YAML frontmatter — same as hand_tracker_v2 (👋) and reachy_phone_home (📱). Yours has emoji: 👋 already, but the dashboard cached the box default at install time, so we need to bump the emoji and reinstall.
Also swap the 👋 in the landing page hero so the visual identity is consistent across the Space and the dashboard:
Make the animation suit a sun better — a gentle radiating pulse instead of a wave:
Push, then on the robot uninstall and reinstall the app — the dashboard caches the icon at install time, so re-pushing the README isn't enough on its own:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.md index.html style.cssgit commit -m "Switch icon to sun ☀️"git pushThen on your Reachy Mini desktop app:
good_morning app (red square button).good-morning.If after reinstall it still shows 📦, the dashboard might cache via HF's own image CDN — give it a few minutes and try a force-refresh of the desktop app. The other apps in your screenshot (reachy_phone_home showing 📱, hand_tracker_v2 showing 👋) use the exact same emoji: frontmatter mechanism, so this approach is correct.
can reachy mini move a bit when it says the text?
Let me check the SDK's movement API so I get the calls right:
HTTP 200
Content-Type: text/plain; charset=utf-8
# Python SDK Reference
> **💡 Reminder:** The SDK now auto-detects whether it should connect over USB/localhost or over the network, so `ReachyMini()` works out of the box. You can still force a mode with `ReachyMini(connection_mode="localhost_only" | "network")` if needed.
## Movement
### Basic Control (`goto_target`)
Smooth interpolation between points. You can control `head`, `antennas`, and `body_yaw`.
```python
from reachy_mini import ReachyMini
from reachy_mini.utils import create_head_pose
import numpy as np
with ReachyMini() as mini:
# Move everything at once
mini.goto_target(
head=create_head_pose(z=10, mm=True), # Up 10mm
antennas=np.deg2rad([45, 45]), # Antennas out
body_yaw=np.deg2rad(30), # Turn body
duration=2.0, # Take 2 seconds
method="minjerk" # Smooth acceleration
)
```
**Interpolation methods:** `linear`, `minjerk` (default), `ease_in_out`, `cartoon`.
### Instant Control (`set_target`)
Bypasses interpolation. Use this for high-frequency control (e.g., following a joystick or generated trajectory).
## Sensors & Media
The media architecture is described in detail in the [Media Architecture](media-architecture.md) section. Although accesssing audio and video from the SDK is similar across Reachy Mini versions, the underlying implementation differs.
### Camera 📷
The frames of the camera can be accessed as follows :
```python
from reachy_mini import ReachyMini
with ReachyMini(media_backend="default") as mini:
frame = mini.media.get_frame()
```
The returned frame is a numpy array with shape `(height, width, 3)` and data type `uint8`.
### IMU 🧭
> ⚠️ The IMU is only available with the wireless version of Reachy Mini
Take a look at [this example](https://github.com/pollen-robotics/reachy_mini/tree/main/examples/imu_example.py)
```python
with ReachyMini() as mini:
imu_data = mini.imu
accel_x, accel_y, accel_z = imu_data["accelerometer"] # (m/s^2)
gyro_x, gyro_y, gyro_z = imu_data["gyroscope"] # (rad/s)
quat_w, quat_x, quat_y, quat_z = imu_data["quaternion"] # (w, x, y, z)
temperature = imu_data["temperature"] # (°C)
```
### Audio 🎙️ 🔊
Audio inputs (microphones) and outputs (speaker) is handled as follows:
```python
from reachy_mini import ReachyMini
from scipy.signal import resample
import time
with ReachyMini(media_backend="default") as mini:
# Initialization - After this point, both audio devices (input/output) will be seen as busy by other applications!
mini.media.start_recording()
mini.media.start_playing()
# Record
samples = mini.media.get_audio_sample()
# Resample (if needed)
samples = resample(samples, mini.media.get_output_audio_samplerate()*len(samples)/mini.media.get_input_audio_samplerate())
# Play
mini.media.push_audio_sample(samples)
time.sleep(len(samples) / mini.media.get_output_audio_samplerate())
# Get Direction of Arrival
# 0 radians is left, π/2 radians is front/back, π radians is right.
doa, is_speech_detected = mini.media.get_DoA()
print(doa, is_speech_detected)
# Release audio devices (input/output)
mini.media.stop_recording()
mini.media.stop_playing()
```
**Audio data format:**
- `get_audio_sample()` returns a numpy array with shape `(samples, 2)` and data type `float32`, sampled at 16kHz.
- `push_audio_sample()` expects a numpy array with shape `(samples, 1 or 2)` and data type `float32`, sampled at 16kHz.
In both cases, the channels and samplerate information can be reliably retrieved with `get_input/output_audio_samplerate()` and `get_input/output_channels()`.
> **⚠️ Note:** `push_audio_sample()` is non-blocking, meaning it returns immediately while audio plays in the background. If you need to wait for playback completion, calculate the duration based on sample length and sample rate.
## Media Backend Options
Choose the appropriate media backend based on your Reachy Mini version and requirements:
- `media_backend="default"` - Auto-detects the best backend: LOCAL when running on the same machine as the daemon, WEBRTC when remote (recommended for most users).
- `media_backend="local"` - Forces the LOCAL backend (GStreamer IPC camera + GStreamer audio). Use when running on the same machine as the daemon.
- `media_backend="webrtc"` - Forces the WEBRTC backend. The daemon streams H.264 video and Opus audio over WebRTC to the client.
- `media_backend="no_media"` - Deactivates the media manager and tells the daemon to release camera and audio hardware. Use this when you need direct access via OpenCV, sounddevice, or any other external library. The hardware is automatically re-acquired when the context manager exits. See [Media Architecture - Disabling Media](media-architecture.md#disabling-media--direct-hardware-access) and the [Custom Media Manager](../examples/custom_media_manager.md) example.
> **💡 Tip:** For most setups, the backend is automatically selected based on whether you're running locally or remotely. No need to specify the `media_backend` value!
> **💡 Tip:** The WebRTC backend requires GStreamer to be installed on the client machine. See [GStreamer Installation](gstreamer-installation.md). For now only Linux is fully supported as a remote client. Other platforms (Windows, macOS) will be supported in [future releases](https://github.com/pollen-robotics/reachy_mini/issues/572).
## Recording Moves
You can record a motion by moving the robot (compliant mode) or sending commands, and save it for later replay.
```python
from reachy_mini import ReachyMini
with ReachyMini() as mini:
mini.start_recording()
# ... robot moves ...
recorded_data = mini.stop_recording()
```
## Next Steps
* **[Browse the Examples Folder](https://github.com/pollen-robotics/reachy_mini/tree/main/examples)**
* **[AI Integrations](integration.md)**: Connect LLMs, build Apps, and publish to Hugging Face.
* **[Core Concepts](core-concept.md)**: Architecture, coordinate systems, and safety limits.
## ❓ Troubleshooting
Encountering an issue? 👉 **[Check the Troubleshooting & FAQ Guide](../troubleshooting.md)**
Yes. I'll add a gentle wobble — head bob, antenna wiggle, and a slight body sway — synced to the audio duration, returning to neutral when it's done. Using set_target at 30 Hz for the live motion and goto_target for the return-to-rest pose, both wrapped in best-effort try/except so any SDK glitch doesn't prevent the greeting.
Now add the motion thread and wire it into _say():
OK
Compiles clean. Push and reinstall:
cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.pygit commit -m "Animate head, antennas, body sway while speaking"git pushOn the robot: stop → uninstall → reinstall → start. Press the button and Reachy Mini should now bob gently, sway its body, and wiggle its antennas for the full duration of the phrase, then settle smoothly back to a neutral resting pose.
What's happening under the hood — a speak-motion thread runs alongside the audio. Every ~33 ms it computes three sinusoids and calls mini.set_target(head=..., antennas=..., body_yaw=...):
The head bobs vertically: a 5 mm baseline lift with ±3 mm bob at 1 Hz — gives the impression of "leaning into" the conversation. Antennas wiggle independently with a 90° phase offset between left and right, peaking at ±12° at 1.5 Hz — the asymmetry reads as expressive rather than mechanical. The body yaws gently ±4° at a slow 0.4 Hz — slow enough that it feels like a conversational sway, not a head shake.
When the phrase finishes, a goto_target smoothly interpolates everything back to zero over 0.6 s with a min-jerk profile, so the robot doesn't snap to rest.
If any of those motions is too much for your taste, the constants are at the top of main.py:
HEAD_BOB_AMPLITUDE_MM from 3.0 to 5.0 or higher.ANTENNA_AMPLITUDE_DEG from 12.0 to 6.0.BODY_YAW_AMPLITUDE_DEG = 0.0.Want me to add a more specific motif — e.g. a single nod on the word "morning," or have it look toward the microphone — instead of the continuous wobble?
on the spaces, can you mention that you can try it in simulation even if you don't have reachy mini yet (and explain how?)
HTTP 200
Content-Type: text/plain; charset=utf-8
# 📦 Installation Guide
> **Welcome to Reachy Mini!** This guide will help you install the Python SDK and daemon to start controlling your robot.
<div align="center">
| 🐧 **Linux** | 🍎 **macOS** | 🪟 **Windows** |
|:---:|:---:|:---:|
| ✅ Supported | ✅ Supported | ✅ Supported |
</div>
**Need help?** Feel free to open an [issue](https://github.com/pollen-robotics/reachy_mini/issues) if you encounter any problem.
## First time using the command line? 🖥️
<details>
<summary>Click here if you're new to using a terminal/command line</summary>
A **command line** (also called terminal or command prompt) is a text-based interface where you can type commands to interact with your computer. Don't worry—it's simpler than it looks!
**How to open the command line:**
* **Windows:** Press `Win + R`, type `cmd` or `powershell`, and press Enter
* **macOS:** Press `Cmd + Space`, type `Terminal`, and press Enter
* **Linux:** Press `Ctrl + Alt + T` or search for "Terminal" in your applications
**Basic tips:**
* Type commands exactly as shown in the instructions
* Press `Enter` after typing each command to run it
* You can copy and paste commands (right-click to paste in most command line interfaces)
> [!TIP]
> _Don't be intimidated!_ The command line is just another way to give instructions to your computer. Follow the commands step by step, and you'll be controlling your Reachy Mini in no time!
</details>
## 1. 📋 Prerequisites
<div align="center">
| Tool | Version | Purpose |
|------|---------|---------|
| 🐍 **Python** | 3.10 - 3.12 | Run Reachy Mini SDK |
| 📂 **Git** | Latest | Download source code and apps |
| 📦 **Git LFS** | Latest | Download model assets |
</div>
### 🐍 Install Python
We'll use `uv` - a fast Python package manager that makes installation simple!
#### Step 1: Install uv
<hfoptions id="install-uv">
<hfoption id="Linux / macOS">
In your terminal, run:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
</hfoption>
<hfoption id="Windows">
In your terminal, run:
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
</hfoption>
</hfoptions>
**✅ Verify installation:**
Once the installation is completed, close your terminal and open a new one. You can check if everything went well with :
```bash
uv --version
```
#### Step 2: Install Python
In your terminal, run:
```bash
uv python install 3.12 --default
```
> [!TIP]
> We recommend Python 3.12 as it's the latest supported version for Reachy Mini.
### 📂 Install Git and Git LFS
<hfoptions id="install-git">
<hfoption id="Linux">
#### Install Git and Git LFS
In your terminal, run:
```bash
sudo apt install git git-lfs
```
</hfoption>
<hfoption id="macOS">
#### 1. Install Homebrew (if not already installed)
In your terminal, run:
```zsh
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
For Apple Silicon (M1, M2, etc.), you will also be prompted to run:
```zsh
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
```
✅ Verify Homebrew:
Once the installation is completed you can check if it went fine with:
```zsh
brew --version
```
#### 2. Install Git and Git LFS
In your terminal, run:
```zsh
brew install git git-lfs
```
</hfoption>
<hfoption id="Windows">
#### Download and install Git for Windows
Download and install Git for Windows:
https://git-scm.com/install/windows
</hfoption>
</hfoptions>
**✅ Finalize installation:**
Finally, Git LFS then needs to be initialized with the command :
```bash
git lfs install
```
## 2. 🏠 Set up a Virtual Environment
> **Why use a virtual environment?** It keeps your Reachy Mini installation isolated and prevents conflicts with other Python projects. Modern Python development requires this!
### Create the environment
In your terminal, run:
```bash
uv venv reachy_mini_env --python 3.12
```
### Activate the environment
<hfoptions id="activate-venv">
<hfoption id="Linux / macOS">
In your terminal, run:
```bash
source reachy_mini_env/bin/activate
```
</hfoption>
<hfoption id="Windows">
> [!WARNING]
> _First-time setup:_ Before you can activate your virtual environment, Windows needs permission to run scripts. You only need to do this once!
**Step 1:** Open terminal as Administrator
- Press `Win + R`, type `powershell`
- Right-click on "Windows PowerShell" and select "Run as administrator"
**Step 2:** Enable script execution
In the administrator terminal, run:
```powershell
powershell Set-ExecutionPolicy RemoteSigned
```
**Step 3:** Close the administrator terminal and open a regular terminal
Now you can activate your virtual environment by running:
```powershell
reachy_mini_env\Scripts\activate
```
</hfoption>
</hfoptions>
> **✅ Success indicator:** You should see `(reachy_mini_env)` at the start of your command line prompt!
## 3. 🚀 Install Reachy Mini
Choose your installation method:
<hfoptions id="install-method">
<hfoption id="📦 Option A: PyPI">
> **Recommended for most users** - Just want to control your robot? This is for you!
In your terminal, run:
```bash
uv pip install "reachy-mini"
```
If you want to use the simulation mode, you need to add the `mujoco` extra:
```bash
uv pip install "reachy-mini[mujoco]"
```
> [!TIP]
> The post installation of gstreamer is due to an [issue](https://github.com/pypi/support/issues/8847#issuecomment-3899714506) with PyPi and should be solved in the future.
<details>
<summary>🐧 <strong>Linux users: additional steps required</strong></summary>
**GStreamer** must be installed manually on Linux:
<div align="center">
[](gstreamer-installation)
</div>
**USB permissions** — needed for the USB connection to Reachy Mini:
```bash
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d3", MODE="0666", GROUP="dialout"
SUBSYSTEM=="usb", ATTRS{idVendor}=="38fb", ATTRS{idProduct}=="1001", MODE="0666", GROUP="dialout"' \
| sudo tee /etc/udev/rules.d/99-reachy-mini.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
sudo usermod -aG dialout $USER
```
> [!WARNING]
> Log out and log back in for the changes to take effect!
</details>
</hfoption>
<hfoption id="🔧 Option B: Source">
> **For developers** - Want to modify the SDK or contribute? Choose this option!
In your terminal, run:
```bash
git clone https://github.com/pollen-robotics/reachy_mini && cd reachy_mini
uv sync
```
If you want to use the simulation mode, you need to add the `mujoco` extra:
```bash
uv sync --extra mujoco
```
<details>
<summary>🐧 <strong>Linux users: additional steps required</strong></summary>
**GStreamer** must be installed manually on Linux:
<div align="center">
[](gstreamer-installation)
</div>
**USB permissions** — needed for the USB connection to Reachy Mini:
```bash
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d3", MODE="0666", GROUP="dialout"
SUBSYSTEM=="usb", ATTRS{idVendor}=="38fb", ATTRS{idProduct}=="1001", MODE="0666", GROUP="dialout"' \
| sudo tee /etc/udev/rules.d/99-reachy-mini.rules
sudo udevadm control --reload-rules && sudo udevadm trigger
sudo usermod -aG dialout $USER
```
> [!WARNING]
> Log out and log back in for the changes to take effect!
</details>
</hfoption>
</hfoptions>
## 🎉 Congratulations!
You've successfully installed Reachy Mini! Your robot is ready to come to life.
## ❓ Troubleshooting
Encountering an issue? 👉 **[Check the Troubleshooting & FAQ Guide](../troubleshooting.md)**
## Next Steps
* **[Quickstart Guide](quickstart.md)**: Run your first behavior on Reachy Mini
* **[Python SDK](python-sdk.md)**: Learn to move, see, speak, and hear.
* **[AI Integrations](integration.md)**: Connect LLMs, build Apps, and publish to Hugging Face.
* **[Core Concepts](core-concept.md)**: Architecture, coordinate systems, and safety limits.
Web search results for query: "reachy mini simulation mujoco how to run simulator without robot"
Links: [{"title":"Reachy Mini Simulation - Setup Guide · Hugging Face","url":"https://huggingface.co/docs/reachy_mini/platforms/simulation/get_started"},{"title":"Reachy Mini Simulation Setup | Seeed Studio Wiki","url":"https://wiki.seeedstudio.com/reachymini_platforms_simulation_get_started/"},{"title":"How to Run Reachy 2 Simulation with Docker: Step-by-Step Setup Guide","url":"https://docs.pollen-robotics.com/developing-with-reachy-2/simulation/simulation-installation/"},{"title":"Running Reachy Mini Simulation on NVIDIA Jetson Thor | by Kabilankb | Feb, 2026 | Medium","url":"https://medium.com/@kabilankb2003/running-reachy-mini-simulation-on-nvidia-jetson-thor-e7ba07a3bcf4"},{"title":"How I built a Physical AI App without owning the robot | by Sathvicknarahari | Medium","url":"https://medium.com/@sathvicknarahari/how-i-built-a-physical-ai-app-without-owning-the-robot-6cf08206af45"},{"title":"reachy-mini · PyPI","url":"https://pypi.org/project/reachy-mini/1.2.0/"},{"title":"Build a Talking Robot with Gemini Live and Reachy Mini - DEV Community","url":"https://dev.to/googleai/build-a-talking-robot-with-gemini-live-and-reachy-mini-20e2"},{"title":"GitHub - pollen-robotics/reachy_mini: Reachy Mini's SDK · GitHub","url":"https://github.com/pollen-robotics/reachy_mini"},{"title":"ReachyMini Getting Started | Seeed Studio Wiki","url":"https://wiki.seeedstudio.com/reachymini_getting_started/"},{"title":"Reachy simulation | Reachy manual","url":"https://pollen-robotics.github.io/reachy-2019-docs/docs/simulation/"}]
Based on the search results, here's how to run the Reachy Mini simulator with MuJoCo without needing a physical robot:
The simulation requires the mujoco python bindings, which you can install alongside the Reachy Mini software with the extra tag [mujoco].
You first have to install the optional dependency mujoco, then run the daemon with the --sim argument.
The basic command to start the simulation is:
reachy-mini-daemon --simA window should open displaying the 3D view of the robot.
On macOS, MuJoCo requires a specific launcher to work correctly with the GUI. Instead of the command above, use mjpython.
Once the simulation is running, it behaves exactly like a real Reachy Mini Lite connected via USB. The daemon listens on localhost, and you can run any Python SDK script without modification. You can access a simple dashboard to monitor the robot's status at http://localhost:8000/ when the daemon is running. This lets you turn your robot on and off, run some basic movements, and browse spaces for Reachy Mini.
You don't need a physical robot to start building! The Reachy Mini simulation runs on MuJoCo and provides a realistic physics environment to prototype, test, and debug your applications.
REMINDER: You MUST include the sources above in your response to the user using markdown hyperlinks.
I'll add a clear "Try it without a robot" section to the landing page with the actual install + daemon-sim commands:
Now style the new section so it stands out as a callout card:
# Python 3.10–3.12 requiredpython3.12 -m venv ~/reachy_mini_envsource ~/reachy_mini_env/bin/activatepip install reachy-minireachy-mini-daemon # starts the daemon (Lite, USB)# or for Wireless: it runs on the robot itself; just check reachy-mini.local:8000 respondsfrom reachy_mini import ReachyMiniwith ReachyMini() as mini: print(mini.get_current_joint_positions())cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"pip install -e .python -m good_morning_app.main# the app is already registered via the entry point in pyproject.toml,# so the dashboard / launcher will pick it up automatically.# Open the Reachy Mini dashboard and click "Good Morning".pip install pyttsx3 soundfile scipy numpypython -c "from good_morning_app.main import _synthesize_phrase, _load_phrase_for_robotfrom pathlib import Path_synthesize_phrase('Good morning!', Path('test.wav'))print(_load_phrase_for_robot(Path('test.wav')).shape)"# 1. Check what Python you havepython3 --version# 2. Make a venv and activate itpython3 -m venv .venvsource .venv/bin/activate
# 3. Now `pip` exists inside the venv. Install the app + the SDK.pip install --upgrade pippip install -e .
# 4. Run itpython -m good_morning_app.mainpython3 -m pip install --user -e .python3 -m good_morning_app.main# 1. Install uv (modern Python installer/runner)brew install uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Make a Python 3.12 envcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"uv venv --python 3.12source .venv/bin/activate
# 3. Install the SDK + the app-assistant CLIuv pip install reachy-mini reachy-mini-app-assistant huggingface_hub
# 4. Log in to Hugging Face (creates the Space under your account)hf auth login # paste a token from https://huggingface.co/settings/tokens (write scope)
# 5. Validate our app's structure first (catches missing frontmatter, bad entry points, etc.)reachy-mini-app-assistant check .
# 6. Push it as a Space — this creates huggingface.co/spaces/clementdelangue/good-morning# and the Reachy Mini dashboard will pick it up.reachy-mini-app-assistant publish .reachy-mini-app-assistant create good_morning ../tmp_good_morning --publish# then copy our src/good_morning_app/main.py, index.html, style.css over the scaffold and `git push`# 1. Create an empty Space on the web:# https://huggingface.co/new-space → SDK = "Static" → name "good-morning"
# 2. From inside the app folder:cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/.../reachy-mini-good-morning"git initgit add .git commit -m "Initial commit"
# 3. Push to the Space (replace clementdelangue with your HF username if different)git remote add origin https://huggingface.co/spaces/clementdelangue/good-morninggit push -u origin mainuv pip install huggingface_hubhf auth loginhuggingface-cli login# you should still be in reachy-mini-good-morning/git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"
# Embed the token in the remote URL so git won't prompt — replace hf_xxx with your tokengit remote add origin https://clementdelangue:[REDACTED]/spaces/clementdelangue/good-morninggit push -u origin mainpython -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
source .venv/bin/activate # in case the shell is fresh
git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"git remote add origin https://huggingface.co/spaces/clementdelangue/good-morninggit push -u origin maingit remote set-url origin https://clementdelangue:[REDACTED]/spaces/clementdelangue/good-morninggit push -u origin main# then strip the token back out:git remote set-url origin https://huggingface.co/spaces/clementdelangue/good-morningpython3 -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"python3 -m pip install --user --break-system-packages huggingface_hubpython3 -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"source .venv/bin/activatepython -c "from huggingface_hub import create_repo; print(create_repo('good-morning', repo_type='space', space_sdk='static').url)"cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git init -b maingit add .git commit -m "Initial commit: Good Morning Reachy Mini app"git remote add origin https://huggingface.co/spaces/clem/good-morninggit push -u origin maingit remote set-url origin https://clem:[REDACTED]/spaces/clem/good-morninggit push -u origin main# then clean it back out so the token isn't sitting in .git/config:git remote set-url origin https://huggingface.co/spaces/clem/good-morninggit push -u origin main --force"id": "clem/good-morning","tags": ["static","reachy_mini","reachy_mini_python_app","region:us"],"sdk": "static"cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.htmlgit commit -m "Open external links in new tab; fix CTA destination"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.html style.cssgit commit -m "Add preview button + clear install/use instructions on landing page"git pushhttp://localhost:7860/http://reachy-mini.local:7860/[good_morning] Good Morning app starting (web on :7860, antennas armed)[good_morning] INFO: Good Morning app starting (web on :7860, antennas armed)[good_morning] INFO: Triggered by web[good_morning] INFO: Saying 'Good morning!' (0.73s)cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.pygit commit -m "Auto-open the button page in the user's browser on app start"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.md index.html style.css src/good_morning_app/main.pygit commit -m "New phrase, Clem credit, drop antenna mentions, document localhost:7860"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.mdgit commit --amend --no-editgit pushgit config --global user.name "Clem Delangue"git config --global user.email "[REDACTED]"cd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.py index.html README.mdgit commit -m "Update phrase: drop name, add open-source line"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add index.htmlgit commit -m "Trim quote from landing page lede"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add README.md index.html style.cssgit commit -m "Switch icon to sun ☀️"git pushcd "/Users/clem/Library/Application Support/Claude/local-agent-mode-sessions/60820195-31c6-4093-8c9e-37c09903f4a5/6e545699-813d-4c34-a80c-8667623c108d/local_8906e6df-1974-4b95-a48d-96cc2a2d91db/outputs/reachy-mini-good-morning"
git add src/good_morning_app/main.pygit commit -m "Animate head, antennas, body sway while speaking"git pushreachy-mini-daemon --sim