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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
- ✅ **Multi-team collaboration** - Agents hand off work to teammates via chain execution and fan-out
- ✅ **Multi-channel** - Discord, WhatsApp, and Telegram
- ✅ **Team Observation** - You can observe agent teams conversations via `tinyclaw team visualize`
- ✅ **Multiple AI providers** - Anthropic Claude and OpenAI Codex using existing subscriptions without breaking ToS
- ✅ **Multiple AI providers** - Anthropic Claude, OpenAI Codex, and Qoder using existing subscriptions without breaking ToS
- ✅ **Parallel processing** - Agents process messages concurrently
- ✅ **Live TUI dashboard** - Real-time team visualizer for monitoring agent chains
- ✅ **Persistent sessions** - Conversation context maintained across restarts
Expand Down Expand Up @@ -80,7 +80,7 @@ The setup wizard will guide you through:
2. **Bot tokens** - Enter tokens for enabled channels
3. **Workspace setup** - Name your workspace directory
4. **Default agent** - Configure your main AI assistant
5. **AI provider** - Select Anthropic (Claude) or OpenAI
5. **AI provider** - Select Anthropic (Claude), OpenAI, or Qoder
6. **Model selection** - Choose model (e.g., Sonnet, Opus, GPT-5.3)
7. **Heartbeat interval** - Set proactive check-in frequency

Expand Down
264 changes: 199 additions & 65 deletions lib/setup-wizard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,60 +79,128 @@ for ch in "${ENABLED_CHANNELS[@]}"; do
done

# Provider selection
echo "Which AI provider?"
echo ""
echo " 1) Anthropic (Claude) (recommended)"
echo " 2) OpenAI (Codex/GPT)"
echo ""
read -rp "Choose [1-2]: " PROVIDER_CHOICE

case "$PROVIDER_CHOICE" in
1) PROVIDER="anthropic" ;;
2) PROVIDER="openai" ;;
*)
echo -e "${RED}Invalid choice${NC}"
exit 1
;;
esac
echo -e "${GREEN}✓ Provider: $PROVIDER${NC}"
echo ""

# Model selection based on provider
if [ "$PROVIDER" = "anthropic" ]; then
echo "Which Claude model?"
PROVIDER=""
while [ -z "$PROVIDER" ]; do
echo "Which AI provider?"
echo ""
echo " 1) Sonnet (fast, recommended)"
echo " 2) Opus (smartest)"
echo " 1) Anthropic (Claude) (recommended)"
echo " 2) OpenAI (Codex/GPT)"
echo " 3) Qoder"
echo " s) Skip (will use defaults)"
echo ""
read -rp "Choose [1-2]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="sonnet" ;;
2) MODEL="opus" ;;
read -rp "Choose [1-3, s]: " PROVIDER_CHOICE

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Break out when setup wizard input stream closes

The new retry loop does not check whether read succeeded, so if stdin reaches EOF (for example in a non-interactive/heredoc run with missing answers, or after Ctrl-D), PROVIDER_CHOICE stays empty, matches the * branch, and while [ -z "$PROVIDER" ] repeats forever. This is a regression from the previous behavior, which exited on invalid provider input, and it can hang automated setup jobs until they are force-killed.

Useful? React with 👍 / 👎.

Copy link
Author

@dagelf dagelf Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended behavior, it loops until a valid selection is made, so as to not invalidate previous entries erroneously, this is not an automated but an interactive setup tool.


case "$PROVIDER_CHOICE" in
1) PROVIDER="anthropic" ;;
2) PROVIDER="openai" ;;
3) PROVIDER="qoder" ;;
[sS])
echo -e "${YELLOW}Skipping provider selection (will use defaults)${NC}"
PROVIDER="anthropic"
break
;;
*)
echo -e "${RED}Invalid choice${NC}"
exit 1
echo -e "${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done

echo -e "${GREEN}✓ Provider: $PROVIDER${NC}"
echo ""

# Model selection based on provider
MODEL=""
if [ "$PROVIDER" = "anthropic" ]; then
while [ -z "$MODEL" ]; do
echo "Which Claude model?"
echo ""
echo " 1) Sonnet (fast, recommended)"
echo " 2) Opus (smartest)"
echo " s) Skip (use default)"
echo ""
read -rp "Choose [1-2, s]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="sonnet" ;;
2) MODEL="opus" ;;
[sS])
echo -e "${YELLOW}Using default model: sonnet${NC}"
MODEL="sonnet"
break
;;
*)
echo -e "${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
else
# OpenAI models
echo "Which OpenAI model?"
echo ""
echo " 1) GPT-5.3 Codex (recommended)"
echo " 2) GPT-5.2"
elif [ "$PROVIDER" = "qoder" ]; then
while [ -z "$MODEL" ]; do
echo "Which Qoder model?"
echo ""
echo " 1) auto (automatic selection)"
echo " 2) efficient (fast, cost-effective)"
echo " 3) gmodel"
echo " 4) kmodel"
echo " 5) lite (lightweight)"
echo " 6) mmodel"
echo " 7) performance (balanced)"
echo " 8) qmodel"
echo " 9) ultimate (most capable)"
echo " s) Skip (use default: auto)"
echo ""
read -rp "Choose [1-9, s]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="auto" ;;
2) MODEL="efficient" ;;
3) MODEL="gmodel" ;;
4) MODEL="kmodel" ;;
5) MODEL="lite" ;;
6) MODEL="mmodel" ;;
7) MODEL="performance" ;;
8) MODEL="qmodel" ;;
9) MODEL="ultimate" ;;
[sS])
echo -e "${YELLOW}Using default model: auto${NC}"
MODEL="auto"
break
;;
*)
echo -e "${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
read -rp "Choose [1-2]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="gpt-5.3-codex" ;;
2) MODEL="gpt-5.2" ;;
*)
echo -e "${RED}Invalid choice${NC}"
exit 1
;;
esac
elif [ "$PROVIDER" = "openai" ]; then
while [ -z "$MODEL" ]; do
echo "Which OpenAI model?"
echo ""
echo " 1) GPT-5.3 Codex (recommended)"
echo " 2) GPT-5.2"
echo " s) Skip (use default)"
echo ""
read -rp "Choose [1-2, s]: " MODEL_CHOICE

case "$MODEL_CHOICE" in
1) MODEL="gpt-5.3-codex" ;;
2) MODEL="gpt-5.2" ;;
[sS])
echo -e "${YELLOW}Using default model: gpt-5.3-codex${NC}"
MODEL="gpt-5.3-codex"
break
;;
*)
echo -e "${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
echo -e "${GREEN}✓ Model: $MODEL${NC}"
echo ""
fi
Expand Down Expand Up @@ -216,27 +284,91 @@ if [[ "$SETUP_AGENTS" =~ ^[yY] ]]; then
read -rp " Display name: " NEW_AGENT_NAME
[ -z "$NEW_AGENT_NAME" ] && NEW_AGENT_NAME="$NEW_AGENT_ID"

echo " Provider: 1) Anthropic 2) OpenAI"
read -rp " Choose [1-2, default: 1]: " NEW_PROVIDER_CHOICE
case "$NEW_PROVIDER_CHOICE" in
2) NEW_PROVIDER="openai" ;;
*) NEW_PROVIDER="anthropic" ;;
esac
NEW_PROVIDER=""
while [ -z "$NEW_PROVIDER" ]; do
echo " Provider: 1) Anthropic 2) OpenAI 3) Qoder"
echo " s) Skip (use default: anthropic)"
read -rp " Choose [1-3, s, default: 1]: " NEW_PROVIDER_CHOICE
case "$NEW_PROVIDER_CHOICE" in
2) NEW_PROVIDER="openai" ;;
3) NEW_PROVIDER="qoder" ;;
[sS])
echo -e " ${YELLOW}Using default provider: anthropic${NC}"
NEW_PROVIDER="anthropic"
break
;;
""|1) NEW_PROVIDER="anthropic" ;;
*)
echo -e " ${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done

NEW_MODEL=""
if [ "$NEW_PROVIDER" = "anthropic" ]; then
echo " Model: 1) Sonnet 2) Opus"
read -rp " Choose [1-2, default: 1]: " NEW_MODEL_CHOICE
case "$NEW_MODEL_CHOICE" in
2) NEW_MODEL="opus" ;;
*) NEW_MODEL="sonnet" ;;
esac
else
echo " Model: 1) GPT-5.3 Codex 2) GPT-5.2"
read -rp " Choose [1-2, default: 1]: " NEW_MODEL_CHOICE
case "$NEW_MODEL_CHOICE" in
2) NEW_MODEL="gpt-5.2" ;;
*) NEW_MODEL="gpt-5.3-codex" ;;
esac
while [ -z "$NEW_MODEL" ]; do
echo " Model: 1) Sonnet 2) Opus s) Skip (use default: sonnet)"
read -rp " Choose [1-2, s, default: 1]: " NEW_MODEL_CHOICE
case "$NEW_MODEL_CHOICE" in
2) NEW_MODEL="opus" ;;
[sS])
echo -e " ${YELLOW}Using default model: sonnet${NC}"
NEW_MODEL="sonnet"
break
;;
""|1) NEW_MODEL="sonnet" ;;
*)
echo -e " ${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
elif [ "$NEW_PROVIDER" = "qoder" ]; then
while [ -z "$NEW_MODEL" ]; do
echo " Model: 1) auto 2) efficient 3) gmodel 4) kmodel 5) lite"
echo " 6) mmodel 7) performance 8) qmodel 9) ultimate"
echo " s) Skip (use default: auto)"
read -rp " Choose [1-9, s, default: 1]: " NEW_MODEL_CHOICE
case "$NEW_MODEL_CHOICE" in
2) NEW_MODEL="efficient" ;;
3) NEW_MODEL="gmodel" ;;
4) NEW_MODEL="kmodel" ;;
5) NEW_MODEL="lite" ;;
6) NEW_MODEL="mmodel" ;;
7) NEW_MODEL="performance" ;;
8) NEW_MODEL="qmodel" ;;
9) NEW_MODEL="ultimate" ;;
[sS])
echo -e " ${YELLOW}Using default model: auto${NC}"
NEW_MODEL="auto"
break
;;
""|1) NEW_MODEL="auto" ;;
*)
echo -e " ${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
elif [ "$NEW_PROVIDER" = "openai" ]; then
while [ -z "$NEW_MODEL" ]; do
echo " Model: 1) GPT-5.3 Codex 2) GPT-5.2 s) Skip (use default: gpt-5.3-codex)"
read -rp " Choose [1-2, s, default: 1]: " NEW_MODEL_CHOICE
case "$NEW_MODEL_CHOICE" in
2) NEW_MODEL="gpt-5.2" ;;
[sS])
echo -e " ${YELLOW}Using default model: gpt-5.3-codex${NC}"
NEW_MODEL="gpt-5.3-codex"
break
;;
""|1) NEW_MODEL="gpt-5.3-codex" ;;
*)
echo -e " ${RED}Invalid choice, please try again${NC}"
echo ""
;;
esac
done
fi

NEW_AGENT_DIR="$WORKSPACE_PATH/$NEW_AGENT_ID"
Expand Down Expand Up @@ -270,6 +402,8 @@ 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}"'" } }'
elif [ "$PROVIDER" = "qoder" ]; then
MODELS_SECTION='"models": { "provider": "qoder", "qoder": { "model": "'"${MODEL}"'" } }'
else
MODELS_SECTION='"models": { "provider": "openai", "openai": { "model": "'"${MODEL}"'" } }'
fi
Expand Down
14 changes: 13 additions & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import { jsonrepair } from 'jsonrepair';
import { Settings, AgentConfig, TeamConfig, CLAUDE_MODEL_IDS, CODEX_MODEL_IDS } from './types';
import { Settings, AgentConfig, TeamConfig, CLAUDE_MODEL_IDS, CODEX_MODEL_IDS, QODER_MODEL_IDS } from './types';

export const SCRIPT_DIR = path.resolve(__dirname, '../..');
const _localTinyclaw = path.join(SCRIPT_DIR, '.tinyclaw');
Expand Down Expand Up @@ -52,6 +52,9 @@ export function getSettings(): Settings {
} else if (settings?.models?.anthropic) {
if (!settings.models) settings.models = {};
settings.models.provider = 'anthropic';
} else if (settings?.models?.qoder) {
if (!settings.models) settings.models = {};
settings.models.provider = 'qoder';
}
}

Expand All @@ -70,6 +73,8 @@ export function getDefaultAgentFromModels(settings: Settings): AgentConfig {
let model = '';
if (provider === 'openai') {
model = settings?.models?.openai?.model || 'gpt-5.3-codex';
} else if (provider === 'qoder') {
model = settings?.models?.qoder?.model || 'auto';
} else {
model = settings?.models?.anthropic?.model || 'sonnet';
}
Expand Down Expand Up @@ -118,3 +123,10 @@ export function resolveClaudeModel(model: string): string {
export function resolveCodexModel(model: string): string {
return CODEX_MODEL_IDS[model] || model || '';
}

/**
* Resolve the model ID for QoderCLI.
*/
export function resolveQoderModel(model: string): string {
return QODER_MODEL_IDS[model] || model || '';
}
23 changes: 22 additions & 1 deletion src/lib/invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { spawn } from 'child_process';
import fs from 'fs';
import path from 'path';
import { AgentConfig, TeamConfig } from './types';
import { SCRIPT_DIR, resolveClaudeModel, resolveCodexModel } from './config';
import { SCRIPT_DIR, resolveClaudeModel, resolveCodexModel, resolveQoderModel } from './config';
import { log } from './logging';
import { ensureAgentDirectory, updateAgentTeammates } from './agent-setup';

Expand Down Expand Up @@ -112,6 +112,27 @@ export async function invokeAgent(
}

return response || 'Sorry, I could not generate a response from Codex.';
} else if (provider === 'qoder') {
// QoderCLI provider
log('INFO', `Using QoderCLI provider (agent: ${agentId})`);

const continueConversation = !shouldReset;

if (shouldReset) {
log('INFO', `🔄 Resetting conversation for agent: ${agentId}`);
}

const modelId = resolveQoderModel(agent.model);
const qoderArgs = ['-w', workingDir];
if (modelId) {
qoderArgs.push('--model', modelId);
}
if (continueConversation) {
qoderArgs.push('-c');
}
qoderArgs.push('-p', message);

return await runCommand('qodercli', qoderArgs, workingDir);
} else {
// Default to Claude (Anthropic)
log('INFO', `Using Claude provider (agent: ${agentId})`);
Expand Down
Loading