Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

## 2026-04-27

- Renamed repo from `buildingopen/moto` to `floomhq/fstack`
- Renamed GitHub org from `buildingopen` to `floomhq`
- Added 17th hook: `sandbox-heavy-tasks.sh` for memory-hungry command interception
- Added `ai-sidecar-health` script for provider verification
- Expanded skills catalog to 60+ commands
Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# fstack 🛵
# moto 🛵

### A terminal IDE for AI agents, extracted from 500+ real sessions

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Claude Code](https://img.shields.io/badge/Claude_Code-compatible-blueviolet)](https://docs.anthropic.com/en/docs/claude-code)

`fstack` is Floom's terminal IDE for AI agents: Claude as the control plane, Codex for backend/infra/debugging, opencode as an optional worker, subagent workflows, Docker-first runtime, CLAUDE.md / AGENTS.md-style context, safety hooks, 60+ skills, Gmail/WhatsApp integrations, memory, browser automation, and cost tracking.
`moto` is Floom's terminal IDE for AI agents: Claude as the control plane, Codex for backend/infra/debugging, opencode as an optional worker, subagent workflows, Docker-first runtime, CLAUDE.md / AGENTS.md-style context, safety hooks, 60+ skills, Gmail/WhatsApp integrations, memory, browser automation, and cost tracking.

Formerly floomhq/moto and earlier buildingopen/moto. Old GitHub URLs redirect.
Formerly buildingopen/moto. Old GitHub URLs redirect.

<p align="center">
<img src="assets/hero.gif" alt="fstack hook demo" width="800">
<img src="assets/hero.gif" alt="moto hook demo" width="800">
<br>
<em>A safety hook firing on a destructive command.</em>
</p>
Expand All @@ -27,7 +27,7 @@ Formerly floomhq/moto and earlier buildingopen/moto. Old GitHub URLs redirect.

## What makes this different?

Most AI coding setups are a prompt file plus a pile of tools. `fstack` is the full operating environment: context, hooks, skills, memory, terminal sessions, remote server runtime, browser automation, Docker sandboxes, and launcher commands for Claude, Codex, and opencode.
Most AI coding setups are a prompt file plus a pile of tools. `moto` is the full operating environment: context, hooks, skills, memory, terminal sessions, remote server runtime, browser automation, Docker sandboxes, and launcher commands for Claude, Codex, and opencode.

The philosophy is simple:

Expand All @@ -44,8 +44,8 @@ The philosophy is simple:
## Quick Start

```bash
git clone https://github.com/floomhq/fstack.git
cd fstack
git clone https://github.com/floomhq/moto.git
cd moto
./install.sh # Symlinks configs into ~/.claude/
```

Expand All @@ -69,11 +69,11 @@ $EDITOR .env

## Comparison

| | bare Claude Code | Claude Code + dotfiles | **fstack** |
| | bare Claude Code | Claude Code + dotfiles | **moto** |
|---|---|---|---|
| Context | manual per session | static files | CLAUDE.md + AGENTS.md + MEMORY.md |
| Safety | none | none | **17 hooks** blocking destructive commands, secret leaks, config tampering |
| Remote workflow | DIY SSH | DIY SSH | **`fstack up`** + SSHFS + systemd + tmux |
| Remote workflow | DIY SSH | DIY SSH | **`moto up`** + SSHFS + systemd + tmux |
| Skills | none | none | **60+ slash commands** (/cost, /qa, /ship, /debug, etc.) |
| Sidecars | none | none | **Groq / Gemini / NVIDIA** routing for cheap stateless work |
| Memory | none | none | **MEMORY.md** auto-updated across sessions |
Expand All @@ -85,7 +85,7 @@ $EDITOR .env

## Architecture

`fstack` splits agent work between a Mac control plane and a remote Linux runtime. The Mac stays where the human works, while the server owns long-running sessions, containers, cheap sidecar calls, browser automation, and messaging integrations.
`moto` splits agent work between a Mac control plane and a remote Linux runtime. The Mac stays where the human works, while the server owns long-running sessions, containers, cheap sidecar calls, browser automation, and messaging integrations.

```mermaid
flowchart LR
Expand Down Expand Up @@ -153,7 +153,7 @@ Read the deep dive: [`docs/architecture.md`](docs/architecture.md)
| Directory | Contents | Learn more |
|-----------|----------|------------|
| [`claude/`](claude/) | CLAUDE.md, hooks, scripts, skills, memory | [`claude/skills/README.md`](claude/skills/README.md) · [`claude/hooks/README.md`](claude/hooks/README.md) · [`claude/memory/README.md`](claude/memory/README.md) |
| [`mac/`](mac/) | `fstack` CLI, iTerm automation, shell aliases, launchd + SSH templates | [`mac/README.md`](mac/README.md) |
| [`mac/`](mac/) | `moto` CLI, iTerm automation, shell aliases, launchd + SSH templates | [`mac/README.md`](mac/README.md) |
| [`server/`](server/) | Systemd services, Docker stack, safety utils, browser automation, tmux | [`docs/bootstrap.md`](docs/bootstrap.md) |
| [`whatsapp/`](whatsapp/) | OpenClaw gateway, verified send, SQLite contact lookup | [`whatsapp/README.md`](whatsapp/README.md) |
| [`gmail/`](gmail/) | IMAP checker, multi-account support | [`gmail/`](gmail/) |
Expand All @@ -167,7 +167,7 @@ Read the deep dive: [`docs/architecture.md`](docs/architecture.md)
No. `./install.sh` works locally. Add `./install.sh mac` + `./install.sh server-remote` only when you want the integrated remote workflow.

**Does this replace Claude Code?**
No. It's the operating environment around Claude Code (and Codex, and opencode). You still use Anthropic's CLI; fstack just makes it reliable, safe, and reproducible.
No. It's the operating environment around Claude Code (and Codex, and opencode). You still use Anthropic's CLI; moto just makes it reliable, safe, and reproducible.

**Is this safe?**
17 hooks run on every tool call blocking `rm -rf /`, secret leaks, wrong package managers, unverified WhatsApp sends, and more. See [`claude/hooks/README.md`](claude/hooks/README.md).
Expand Down
4 changes: 2 additions & 2 deletions assets/architecture.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion claude/scripts/ai-provider-key
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Store provider API keys for fstack sidecars without writing them to repos."""
"""Store provider API keys for moto sidecars without writing them to repos."""

from __future__ import annotations

Expand Down
4 changes: 2 additions & 2 deletions claude/scripts/ai-sidecar
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ def call_provider(provider: str, model: str, prompt: str, max_tokens: int, timeo
headers = {
"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 fstack-ai-sidecar/1.0",
"User-Agent": "Mozilla/5.0 moto-ai-sidecar/1.0",
}
if provider == "openrouter":
headers.update({"HTTP-Referer": "https://localhost", "X-Title": "fstack AI Sidecar"})
headers.update({"HTTP-Referer": "https://localhost", "X-Title": "moto AI Sidecar"})

request = urllib.request.Request(
cfg["url"],
Expand Down
10 changes: 5 additions & 5 deletions claude/scripts/ai-sidecar-health
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Health check for fstack low-cost AI sidecars."""
"""Health check for moto low-cost AI sidecars."""

from __future__ import annotations

Expand Down Expand Up @@ -98,9 +98,9 @@ def check_openrouter() -> dict:
{
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 fstack-ai-sidecar-health/1.0",
"User-Agent": "Mozilla/5.0 moto-ai-sidecar-health/1.0",
"HTTP-Referer": "https://localhost",
"X-Title": "fstack AI Sidecar Health",
"X-Title": "moto AI Sidecar Health",
},
)
usage = data.get("usage", {})
Expand All @@ -123,7 +123,7 @@ def check_groq() -> dict:
{
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 fstack-ai-sidecar-health/1.0",
"User-Agent": "Mozilla/5.0 moto-ai-sidecar-health/1.0",
},
)
usage = data.get("usage", {})
Expand All @@ -147,7 +147,7 @@ def check_nvidia() -> dict:
{
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 fstack-ai-sidecar-health/1.0",
"User-Agent": "Mozilla/5.0 moto-ai-sidecar-health/1.0",
},
)
usage = data.get("usage", {})
Expand Down
4 changes: 2 additions & 2 deletions claude/skills/session-learn/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ Check if a topic file already exists before adding to MEMORY.md directly.
Topic files live at `~/.claude/projects/-root/memory/<topic>.md`.
If MEMORY.md exceeds 200 lines, add to a topic file and reference it.

### Sync to fstack repo (if applicable)
### Sync to moto repo (if applicable)

```bash
# Check if the skill directory is tracked by a setup repo
ls ~/fstack/ 2>/dev/null || ls ~/claude-setup/ 2>/dev/null || ls ~/.claude-setup/ 2>/dev/null
ls ~/moto/ 2>/dev/null || ls ~/claude-setup/ 2>/dev/null || ls ~/.claude-setup/ 2>/dev/null
# If it exists, copy new skill files there and commit
```

Expand Down
10 changes: 5 additions & 5 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# fstack — architecture
# moto — architecture

## Physical layout

Expand All @@ -10,7 +10,7 @@
│ iTerm ─┐ │
│ │ AppleScript │
│ ▼ │
fstack CLI │
moto CLI │
│ │ │
│ ▼ │
│ ssh ax41 (ControlMaster)│
Expand Down Expand Up @@ -64,7 +64,7 @@ its children (your 20+ agent sessions) are touched.

### 3. One iTerm window, many tabs

The Mac's `fstack up` (in zsh: `axo`) does this dance:
The Mac's `moto up` (in zsh: `axo`) does this dance:

1. Detaches all existing tmux clients on the server
2. Lists sessions sorted by activity
Expand All @@ -75,7 +75,7 @@ The Mac's `fstack up` (in zsh: `axo`) does this dance:
5. Waits 12s, then retries any sessions that didn't attach (up to 5 rounds,
then a final single-pass for stubborn ones)

This is idempotent: calling `fstack up` at any time reconstructs the exact same
This is idempotent: calling `moto up` at any time reconstructs the exact same
window layout on your Mac.

### 4. Logged-in browser for agents
Expand Down Expand Up @@ -120,7 +120,7 @@ network are up. It:
Tmux sessions survive automatically because the tmux server itself is
restarted by its own systemd unit and sessions were started by
`cs`/`cx`/`co`, which use `new-session -A` (attach-or-create). They're
empty until you re-run `fstack up` — but tmux state persists, so prior
empty until you re-run `moto up` — but tmux state persists, so prior
scrollback and the Claude CLI process come back cleanly *if* they were
still alive before the reboot (they won't be, because the box rebooted —
but the session *slots* are restored automatically when `./cs NAME` is re-run).
Expand Down
16 changes: 8 additions & 8 deletions docs/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ and Ubuntu 22.04+.
## 1. Clone on your Mac

```bash
git clone https://github.com/floomhq/fstack.git ~/fstack
cd ~/fstack
git clone https://github.com/floomhq/moto.git ~/moto
cd ~/moto
cp .env.example .env
$EDITOR .env
```
Expand All @@ -34,7 +34,7 @@ Fill in at minimum:
./install.sh mac
```

This installs the local `~/.claude` config, adds `~/.local/bin/fstack`, sources
This installs the local `~/.claude` config, adds `~/.local/bin/moto`, sources
shell functions into `~/.zshrc`, adds a `Host ax41` block to `~/.ssh/config`,
and loads the launchd agent for the reverse tunnel.

Expand Down Expand Up @@ -62,7 +62,7 @@ If the installer printed a new public key, add it to your Mac's

```bash
# On the Mac:
echo 'ssh-ed25519 AAAA... fstack-server-hostname' >> ~/.ssh/authorized_keys
echo 'ssh-ed25519 AAAA... moto-server-hostname' >> ~/.ssh/authorized_keys
```

## 4. Kick the reverse tunnel
Expand All @@ -79,11 +79,11 @@ ssh ax41 'ssh mac hostname'
# → prints your Mac's hostname
```

## 5. Run `fstack doctor`
## 5. Run `moto doctor`

```bash
# On the Mac:
fstack doctor
moto doctor
```

Every check turns green when the setup is healthy. If `reverse tunnel` is down, the SSH server on
Expand All @@ -93,11 +93,11 @@ the server's key.
## 6. Create your first session

```bash
fstack new hello/world
moto new hello/world
# → iTerm opens a new tab with Claude Code running on the server.
```

From now on, `fstack up` will always restore your full set of sessions into one
From now on, `moto up` will always restore your full set of sessions into one
iTerm window.

## 7. (Optional) Browser login
Expand Down
2 changes: 1 addition & 1 deletion docs/browser-login.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ ssh ax41 'bash /root/authenticated-browser/vnc-login.sh stop'

## 6. Use it from agents

From any Docker container on the fstack network, Chrome is reachable at:
From any Docker container on the moto network, Chrome is reachable at:

```
ws://172.17.0.1:9222/devtools/browser
Expand Down
4 changes: 2 additions & 2 deletions docs/claude-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Two mounts, configured in `server/bin/check-mac-mounts`:
| `/mnt/mac` | `~/` (the Mac home dir) | Full Mac access from the server|

The full home mount is what lets `aximg` (copy image to server) and
`fstack img` work — but it's also a footgun (you can `rm -rf ~/Desktop` from a
`moto img` work — but it's also a footgun (you can `rm -rf ~/Desktop` from a
server terminal). Keep an eye on that.

## If you want the opposite direction
Expand All @@ -79,4 +79,4 @@ mkdir -p ~/mnt/ax41
sshfs ax41:/root ~/mnt/ax41
```

This isn't used by fstack but can be handy.
This isn't used by moto but can be handy.
8 changes: 4 additions & 4 deletions docs/demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This repo is powerful but dense. Every major feature should have a 10–30 secon

`assets/demos/hero.gif` — the full 30-second pitch:
```
fstack new myproj/feature
moto new myproj/feature
# → iTerm spawns 4 tabs
# → SSH to remote
# → tmux session attaches
Expand All @@ -18,12 +18,12 @@ fstack new myproj/feature
| Feature | Target file | What to record |
|---------|-------------|----------------|
| **WhatsApp setup** | `assets/demos/whatsapp-setup.gif` | `clawdbot-ctl login` → QR code → phone scan → `safe-wa-send "Name" "test"` → verify output → `--confirmed` send |
| **Install** | `assets/demos/install.gif` | `git clone … && cd fstack && ./install.sh` → hooks installed → skills symlinked |
| **Install** | `assets/demos/install.gif` | `git clone … && cd moto && ./install.sh` → hooks installed → skills symlinked |
| **Safety hook block** | `assets/demos/hook-block.gif` | Claude tries `rm -rf /` → hook intercepts → red error |
| **fstack CLI** | `assets/demos/fstack-cli.gif` | `fstack ls` → `fstack new proj` → `fstack up` → tabs restore |
| **moto CLI** | `assets/demos/moto-cli.gif` | `moto ls` → `moto new proj` → `moto up` → tabs restore |
| **Skills** | `assets/demos/skills.gif` | `/cost` → `/bouncer` → `/qa` in a Claude session |
| **Memory** | `assets/demos/memory.gif` | Claude updates `MEMORY.md` → next session recalls preference |
| **Server bootstrap** | `assets/demos/server.gif` | `./install.sh server-remote` → systemd units → `fstack doctor` |
| **Server bootstrap** | `assets/demos/server.gif` | `./install.sh server-remote` → systemd units → `moto doctor` |
| **Sidecar** | `assets/demos/sidecar.gif` | `ai-sidecar --provider groq --prompt "review this diff"` |

## Recording workflow
Expand Down
2 changes: 1 addition & 1 deletion docs/demos/hero-hook-demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ sleep 0.5
printf '\n'

sleep 0.3
printf '\033[31m[fstack hook] BLOCKED: destructive command outside allowlist\033[0m\n'
printf '\033[31m[moto hook] BLOCKED: destructive command outside allowlist\033[0m\n'

sleep 0.3
printf '\033[33m reason: rm -rf without explicit --i-know-what-im-doing flag\033[0m\n'
Expand Down
6 changes: 3 additions & 3 deletions docs/demos/record.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ set -euo pipefail

# Record a reproducible terminal demo using asciinema + agg
# Usage: ./docs/demos/record.sh <feature>
# feature = whatsapp | install | fstack-cli | hook-block | skills | memory | server | sidecar
# feature = whatsapp | install | moto-cli | hook-block | skills | memory | server | sidecar

FEATURE="${1:-}"
DEMO_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$DEMO_DIR/../.." && pwd)"
CAST_DIR="/tmp/fstack-demos"
CAST_DIR="/tmp/moto-demos"
mkdir -p "$CAST_DIR"

if [[ -z "$FEATURE" ]]; then
Expand All @@ -31,7 +31,7 @@ fi
command -v asciinema >/dev/null 2>&1 || { echo "Install asciinema first: brew install asciinema"; exit 1; }

echo "Recording $FEATURE → $CAST_FILE"
asciinema rec "$CAST_FILE" --command "bash $DEMO_SCRIPT" --title "fstack: $FEATURE"
asciinema rec "$CAST_FILE" --command "bash $DEMO_SCRIPT" --title "moto: $FEATURE"

if command -v agg >/dev/null 2>&1; then
mkdir -p "$(dirname "$GIF_FILE")"
Expand Down
8 changes: 4 additions & 4 deletions docs/demos/whatsapp.demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# This is run inside asciinema. Use echo + sleep to pace the narrative.

clear
echo -e "\033[1;36m=== fstack WhatsApp Setup ===\033[0m"
echo -e "\033[1;36m=== moto WhatsApp Setup ===\033[0m"
sleep 1

echo ""
Expand All @@ -23,15 +23,15 @@ sleep 2
echo -e "\033[32m[+] QR code scanned. Session linked.\033[0m"

echo ""
echo "$ ./safe-wa-send \"Alice\" \"Hey from fstack 👋\""
echo "$ ./safe-wa-send \"Alice\" \"Hey from moto 👋\""
sleep 0.5
echo -e "\033[36m Resolved: Alice (+1 555-0199)\033[0m"
echo -e "\033[36m JID: 15550199@s.whatsapp.net\033[0m"
echo -e "\033[36m Message: Hey from fstack 👋\033[0m"
echo -e "\033[36m Message: Hey from moto 👋\033[0m"
echo -e "\033[31m [DRY RUN] Add --confirmed to send.\033[0m"

echo ""
echo "$ ./safe-wa-send \"Alice\" \"Hey from fstack 👋\" --confirmed"
echo "$ ./safe-wa-send \"Alice\" \"Hey from moto 👋\" --confirmed"
sleep 0.5
echo -e "\033[32m[+] Message sent to Alice (+1 555-0199)\033[0m"

Expand Down
2 changes: 1 addition & 1 deletion docs/proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Why: many services (Google sign-in, LinkedIn, Zomato, Instagram) flag or block
data-center IPs. Routing agent traffic through a residential IP makes you look
like a normal user.

## How fstack does it
## How moto does it

`server/docker/compose.yaml` includes an optional `proxy` service (tinyproxy,
built locally from `server/docker/proxy/`) under the `proxy` profile. It:
Expand Down
Loading
Loading