Skip to content
Open
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
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,42 @@ Commands work with `tinyclaw` (if CLI installed) or `./tinyclaw.sh` (direct scri
| `reset` | Reset all conversations | `tinyclaw reset` |
| `channels reset <channel>` | Reset channel authentication | `tinyclaw channels reset whatsapp` |

### OpenAI (Codex CLI) Provider

If you select the `openai` provider, TinyClaw uses the Codex CLI.

Environment variables:

```bash
export OPENAI_API_KEY="..."
```

Note: model availability can depend on how the Codex CLI is authenticated. If you see errors about a model being unsupported with a ChatGPT account, switch to a supported Codex model (e.g. `gpt-5.3-codex`) or authenticate with an API-key style credential appropriate for your endpoint.

### Cerebras (Qwen/LLama) Provider (Optional)

Codex CLI currently calls the OpenAI **Responses** API (`/v1/responses`). Some OpenAI-compatible providers (including Cerebras) only expose **Chat Completions** (`/v1/chat/completions`), so TinyClaw includes an optional `cerebras` provider that talks to Cerebras directly.

Environment variables:

```bash
export CEREBRAS_API_KEY="..."
export TINYCLAW_CEREBRAS_BASE_URL="https://api.cerebras.ai/v1" # optional (defaults to Cerebras)
```

In `settings.json`, set the agent provider/model:

```json
{
"agents": {
"assistant": {
"provider": "cerebras",
"model": "qwen-3-32b"
}
}
}
```

### Pairing Commands

Use sender pairing to control who can message your agents.
Expand Down Expand Up @@ -206,6 +242,12 @@ Pairing behavior:
tinyclaw update
```

**Non-interactive update (CI/automation):**

```bash
TINYCLAW_UPDATE_YES=1 tinyclaw update
```

This will:

1. Check for latest release
Expand Down
4 changes: 2 additions & 2 deletions docs/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ claude --dangerously-skip-permissions \
**OpenAI (Codex):**
```bash
cd "$agent_working_directory" # e.g., ~/tinyclaw-workspace/coder/
codex exec resume --last \
codex --ask-for-approval never exec --ephemeral \
--model gpt-5.3-codex \
--skip-git-repo-check \
--dangerously-bypass-approvals-and-sandbox \
--sandbox danger-full-access \
--json \
"User message here"
```
Expand Down
4 changes: 2 additions & 2 deletions docs/QUEUE.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ claude --dangerously-skip-permissions \
**Codex (OpenAI):**
```bash
cd ~/workspace/coder/
codex exec resume --last \
codex --ask-for-approval never exec --ephemeral \
--model gpt-5.3-codex \
--skip-git-repo-check \
--dangerously-bypass-approvals-and-sandbox \
--sandbox danger-full-access \
--json "fix the authentication bug"
```

Expand Down
42 changes: 32 additions & 10 deletions lib/daemon.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,38 @@ start_daemon() {
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
echo -e "${YELLOW}Installing Node.js dependencies...${NC}"
cd "$SCRIPT_DIR"
PUPPETEER_SKIP_DOWNLOAD=true npm install
if command -v bun >/dev/null 2>&1; then
# bun is typically much faster than npm. Avoid writing lockfiles in user installs.
PUPPETEER_SKIP_DOWNLOAD=true bun install --no-save
else
PUPPETEER_SKIP_DOWNLOAD=true npm install
fi
fi

# Build TypeScript if any src file is newer than its dist counterpart
# Build TypeScript if any src file is newer than its dist counterpart.
# Check recursively so changes under src/lib/* also trigger rebuilds.
local needs_build=false
if [ ! -d "$SCRIPT_DIR/dist" ]; then
needs_build=true
else
for ts_file in "$SCRIPT_DIR"/src/*.ts; do
local js_file="$SCRIPT_DIR/dist/$(basename "${ts_file%.ts}.js")"
while IFS= read -r -d '' ts_file; do
local rel="${ts_file#"$SCRIPT_DIR/src/"}"
local js_rel="${rel%.ts}.js"
local js_file="$SCRIPT_DIR/dist/$js_rel"
if [ ! -f "$js_file" ] || [ "$ts_file" -nt "$js_file" ]; then
needs_build=true
break
fi
done
done < <(find "$SCRIPT_DIR/src" -type f -name '*.ts' -print0)
fi
if [ "$needs_build" = true ]; then
echo -e "${YELLOW}Building TypeScript...${NC}"
cd "$SCRIPT_DIR"
npm run build
if command -v bun >/dev/null 2>&1; then
bun run build
else
npm run build
fi
fi

# Load settings or run setup wizard
Expand Down Expand Up @@ -104,6 +116,14 @@ start_daemon() {

tmux new-session -d -s "$TMUX_SESSION" -n "tinyclaw" -c "$SCRIPT_DIR"

# Ensure critical env vars are present inside tmux panes.
# tmux servers can outlive the calling shell, so relying on inheritance is brittle.
for k in CEREBRAS_API_KEY OPENAI_API_KEY TINYCLAW_CEREBRAS_BASE_URL; do
if [ -n "${!k:-}" ]; then
tmux set-environment -t "$TMUX_SESSION" "$k" "${!k}"
fi
done

# Create remaining panes (pane 0 already exists)
for ((i=1; i<total_panes; i++)); do
tmux split-window -t "$TMUX_SESSION" -c "$SCRIPT_DIR"
Expand All @@ -115,23 +135,25 @@ start_daemon() {
local whatsapp_pane=-1
for ch in "${ACTIVE_CHANNELS[@]}"; do
[ "$ch" = "whatsapp" ] && whatsapp_pane=$pane_idx
tmux send-keys -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && node ${CHANNEL_SCRIPT[$ch]}" C-m
# Use respawn-pane instead of send-keys so the target command reliably starts
# even if the pane shell hasn't finished initializing yet.
tmux respawn-pane -k -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && node ${CHANNEL_SCRIPT[$ch]}"
tmux select-pane -t "$TMUX_SESSION:0.$pane_idx" -T "${CHANNEL_DISPLAY[$ch]}"
pane_idx=$((pane_idx + 1))
done

# Queue pane
tmux send-keys -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && node dist/queue-processor.js" C-m
tmux respawn-pane -k -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && node dist/queue-processor.js"
tmux select-pane -t "$TMUX_SESSION:0.$pane_idx" -T "Queue"
pane_idx=$((pane_idx + 1))

# Heartbeat pane
tmux send-keys -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && ./lib/heartbeat-cron.sh" C-m
tmux respawn-pane -k -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && ./lib/heartbeat-cron.sh"
tmux select-pane -t "$TMUX_SESSION:0.$pane_idx" -T "Heartbeat"
pane_idx=$((pane_idx + 1))

# Logs pane
tmux send-keys -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && $log_tail_cmd" C-m
tmux respawn-pane -k -t "$TMUX_SESSION:0.$pane_idx" "cd '$SCRIPT_DIR' && $log_tail_cmd"
tmux select-pane -t "$TMUX_SESSION:0.$pane_idx" -T "Logs"

echo ""
Expand Down
2 changes: 1 addition & 1 deletion lib/heartbeat-cron.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fi
LOG_FILE="$TINYCLAW_HOME/logs/heartbeat.log"
QUEUE_INCOMING="$TINYCLAW_HOME/queue/incoming"
QUEUE_OUTGOING="$TINYCLAW_HOME/queue/outgoing"
SETTINGS_FILE="$PROJECT_ROOT/.tinyclaw/settings.json"
SETTINGS_FILE="$TINYCLAW_HOME/settings.json"

# Read interval from settings.json, default to 3600
if [ -f "$SETTINGS_FILE" ]; then
Expand Down
29 changes: 26 additions & 3 deletions lib/setup-wizard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ echo "Which AI provider?"
echo ""
echo " 1) Anthropic (Claude) (recommended)"
echo " 2) OpenAI (Codex/GPT)"
echo " 3) Cerebras (Qwen)"
echo ""
read -rp "Choose [1-2]: " PROVIDER_CHOICE
read -rp "Choose [1-3]: " PROVIDER_CHOICE

case "$PROVIDER_CHOICE" in
1) PROVIDER="anthropic" ;;
2) PROVIDER="openai" ;;
3) PROVIDER="cerebras" ;;
*)
echo -e "${RED}Invalid choice${NC}"
exit 1
Expand Down Expand Up @@ -116,7 +118,7 @@ if [ "$PROVIDER" = "anthropic" ]; then
esac
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
else
elif [ "$PROVIDER" = "openai" ]; then
# OpenAI models
echo "Which OpenAI model?"
echo ""
Expand All @@ -135,6 +137,25 @@ else
esac
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
else
# Cerebras models
echo "Which Cerebras model?"
echo ""
echo " 1) Qwen 3 235B Instruct (may require model access)"
echo " 2) Qwen 3 32B (generally available)"
echo ""
read -rp "Choose [1-2]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="qwen-3-235b-a22b-instruct-2507" ;;
2) MODEL="qwen-3-32b" ;;
*)
echo -e "${RED}Invalid choice${NC}"
exit 1
;;
esac
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
fi

# Heartbeat interval
Expand Down Expand Up @@ -270,8 +291,10 @@ TELEGRAM_TOKEN="${TOKENS[telegram]:-}"
# Use jq to build valid JSON to avoid escaping issues with agent prompts
if [ "$PROVIDER" = "anthropic" ]; then
MODELS_SECTION='"models": { "provider": "anthropic", "anthropic": { "model": "'"${MODEL}"'" } }'
else
elif [ "$PROVIDER" = "openai" ]; then
MODELS_SECTION='"models": { "provider": "openai", "openai": { "model": "'"${MODEL}"'" } }'
else
MODELS_SECTION='"models": { "provider": "cerebras", "cerebras": { "model": "'"${MODEL}"'" } }'
fi

cat > "$SETTINGS_FILE" <<EOF
Expand Down
24 changes: 20 additions & 4 deletions lib/update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ show_update_notification() {
echo ""
}

# Confirm a yes/no prompt.
# - Interactive: prompts.
# - Non-interactive: requires TINYCLAW_UPDATE_YES=1 (otherwise cancels with a helpful message).
confirm_update() {
local prompt="$1"

if [ "${TINYCLAW_UPDATE_YES:-}" = "1" ]; then
return 0
fi

local CONFIRM
if ! read -rp "$prompt" CONFIRM; then
echo "No input available. Re-run with TINYCLAW_UPDATE_YES=1 to auto-confirm."
return 1
fi
[[ "$CONFIRM" =~ ^[yY] ]]
}

# Perform update
do_update() {
echo -e "${BLUE}TinyClaw Update${NC}"
Expand All @@ -128,8 +146,7 @@ do_update() {
if session_exists; then
echo -e "${YELLOW}Warning: TinyClaw is currently running${NC}"
echo ""
read -rp "Stop and update? [y/N]: " CONFIRM
if [[ ! "$CONFIRM" =~ ^[yY] ]]; then
if ! confirm_update "Stop and update? [y/N]: "; then
echo "Update cancelled."
return 1
fi
Expand Down Expand Up @@ -165,8 +182,7 @@ do_update() {
echo " https://github.com/$GITHUB_REPO/releases/v${latest_version}"
echo ""

read -rp "Update to v${latest_version}? [y/N]: " CONFIRM
if [[ ! "$CONFIRM" =~ ^[yY] ]]; then
if ! confirm_update "Update to v${latest_version}? [y/N]: "; then
echo "Update cancelled."
return 1
fi
Expand Down
19 changes: 16 additions & 3 deletions scripts/bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,21 +52,34 @@ echo ""
# Step 2: Install dependencies for build
echo -e "${BLUE}[2/5] Installing dependencies...${NC}"
echo "This may take a few minutes..."
PUPPETEER_SKIP_DOWNLOAD=true npm install --silent
if command -v bun >/dev/null 2>&1; then
PUPPETEER_SKIP_DOWNLOAD=true bun install --no-save --silent
else
PUPPETEER_SKIP_DOWNLOAD=true npm install --silent
fi
echo -e "${GREEN}✓ Dependencies installed${NC}"
echo ""

# Step 3: Build TypeScript
echo -e "${BLUE}[3/5] Building TypeScript...${NC}"
npm run build --silent
if command -v bun >/dev/null 2>&1; then
bun run build
else
npm run build --silent
fi
echo -e "${GREEN}✓ Build complete${NC}"
echo ""

# Step 4: Create bundle directory
echo -e "${BLUE}[4/5] Creating bundle...${NC}"

# Keep runtime bundle lean: remove development-only dependencies after build.
npm prune --omit=dev --silent
if command -v bun >/dev/null 2>&1; then
rm -rf node_modules
PUPPETEER_SKIP_DOWNLOAD=true bun install --production --no-save --silent
else
npm prune --omit=dev --silent
fi

mkdir -p "$BUNDLE_DIR"

Expand Down
32 changes: 23 additions & 9 deletions scripts/remote-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ if ! command_exists node; then
MISSING_DEPS+=("node")
fi

if ! command_exists npm; then
MISSING_DEPS+=("npm")
if ! command_exists bun && ! command_exists npm; then
MISSING_DEPS+=("bun or npm")
fi

if ! command_exists tmux; then
Expand All @@ -60,7 +60,9 @@ if [ ${#MISSING_DEPS[@]} -ne 0 ]; then
done
echo ""
echo "Install instructions:"
echo " - Node.js/npm: https://nodejs.org/"
echo " - Node.js: https://nodejs.org/"
echo " - bun (recommended): https://bun.sh/"
echo " - npm: included with Node.js"
echo " - tmux: sudo apt install tmux (or brew install tmux)"
echo " - Claude Code: https://claude.com/claude-code"
echo ""
Expand Down Expand Up @@ -152,14 +154,26 @@ if [ "$USE_BUNDLE" = false ]; then
echo -e "${BLUE}[5/6] Installing dependencies...${NC}"
cd "$INSTALL_DIR"

echo "Running npm install (this may take a few minutes)..."
PUPPETEER_SKIP_DOWNLOAD=true npm install --silent
if command_exists bun; then
echo "Running bun install..."
PUPPETEER_SKIP_DOWNLOAD=true bun install --no-save --silent

echo "Building TypeScript..."
npm run build --silent
echo "Building TypeScript..."
bun run build

echo "Pruning development dependencies..."
npm prune --omit=dev --silent
echo "Installing production-only dependencies..."
rm -rf node_modules
PUPPETEER_SKIP_DOWNLOAD=true bun install --production --no-save --silent
else
echo "Running npm install (this may take a few minutes)..."
PUPPETEER_SKIP_DOWNLOAD=true npm install --silent

echo "Building TypeScript..."
npm run build --silent

echo "Pruning development dependencies..."
npm prune --omit=dev --silent
fi

echo -e "${GREEN}✓ Dependencies installed${NC}"
echo ""
Expand Down
Loading