From 70b1db9544fdf57d22c2c1787e86611fc46c8dc0 Mon Sep 17 00:00:00 2001 From: Youhana Sheriff Date: Thu, 26 Feb 2026 16:37:42 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20update=20(cli):=20add=20--docker?= =?UTF-8?q?=20flag=20for=20container=20environments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add --docker flag that routes to web-based setup - Auto-detect Docker/container environments via: - CI, CONTAINER, DOCKER_CONTAINER env vars - /.dockerenv file - /proc/1/cgroup container indicators - Add container detection warning in interactive setup - Update help text with --docker documentation Closes #12 --- src/cli/src/commands/setup.ts | 45 ++++++++++++++++++++++++ src/cli/src/index.ts | 64 +++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 11 deletions(-) diff --git a/src/cli/src/commands/setup.ts b/src/cli/src/commands/setup.ts index de57bbe..0856a3a 100644 --- a/src/cli/src/commands/setup.ts +++ b/src/cli/src/commands/setup.ts @@ -53,6 +53,28 @@ import { createWebUI } from '@tinyclaw/web'; import QRCode from 'qrcode'; import { showBanner } from '../ui/banner.js'; import { theme } from '../ui/theme.js'; +import { existsSync, readFileSync } from 'fs'; + +/** + * Detect if running inside a Docker container or CI environment. + */ +function isRunningInContainer(): boolean { + if (process.env.CI || process.env.CONTAINER || process.env.DOCKER_CONTAINER) { + return true; + } + try { + if (existsSync('/.dockerenv')) { + return true; + } + } catch { /* ignore */ } + try { + const cgroup = readFileSync('/proc/1/cgroup', 'utf8'); + if (/docker|containerd|kubepods|lxc|podman/i.test(cgroup)) { + return true; + } + } catch { /* ignore */ } + return false; +} /** * Copy text to the system clipboard. @@ -205,6 +227,29 @@ export async function setupCommand(): Promise { showBanner(); + // --- Container environment warning ---------------------------------- + + if (isRunningInContainer()) { + p.note( + theme.warn('Container Environment Detected') + '\n\n' + + 'Interactive CLI setup may not work properly in Docker/containers.\n' + + 'If prompts freeze or fail, cancel and run:\n\n' + + ' ' + theme.cmd('tinyclaw setup --docker') + '\n\n' + + 'Or use ' + theme.cmd('--web') + ' for browser-based setup.', + 'Docker/Container' + ); + + const continueAnyway = await p.confirm({ + message: 'Continue with interactive setup anyway?', + initialValue: false, + }); + + if (p.isCancel(continueAnyway) || !continueAnyway) { + p.outro(theme.dim('Setup cancelled. Use --docker or --web flag for container environments.')); + process.exit(0); + } + } + const secretsManager = await SecretsManager.create(); const configManager = await ConfigManager.create(); diff --git a/src/cli/src/index.ts b/src/cli/src/index.ts index 1ebcaad..d1ed62c 100644 --- a/src/cli/src/index.ts +++ b/src/cli/src/index.ts @@ -6,18 +6,54 @@ * Lightweight argument router. No framework — just process.argv. * * Usage: - * tinyclaw Show banner + help - * tinyclaw setup Interactive first-time setup wizard - * tinyclaw setup --web Start web onboarding at /setup - * tinyclaw start Boot the agent (requires setup first) - * tinyclaw purge Wipe all data for a fresh install - * tinyclaw --version Print version - * tinyclaw --help Show help + * tinyclaw Show banner + help + * tinyclaw setup Interactive first-time setup wizard + * tinyclaw setup --web Start web onboarding at /setup + * tinyclaw setup --docker Force web mode for Docker/container environments + * tinyclaw start Boot the agent (requires setup first) + * tinyclaw purge Wipe all data for a fresh install + * tinyclaw --version Print version + * tinyclaw --help Show help */ import { logger } from '@tinyclaw/logger'; import { getVersion, showBanner } from './ui/banner.js'; import { theme } from './ui/theme.js'; +import { existsSync, readFileSync } from 'fs'; + +// ── Docker Detection ─────────────────────────────────────────────────── + +/** + * Detect if running inside a Docker container or CI environment. + * Checks for common indicators: .dockerenv, cgroup, CI env vars, container-specific env vars. + */ +function detectDockerEnvironment(): boolean { + // Check for explicit CI/container environment variables + if (process.env.CI || process.env.CONTAINER || process.env.DOCKER_CONTAINER) { + return true; + } + + // Check for .dockerenv file (older Docker versions) + try { + if (existsSync('/.dockerenv')) { + return true; + } + } catch { + // Ignore errors + } + + // Check cgroup for container indicators + try { + const cgroup = readFileSync('/proc/1/cgroup', 'utf8'); + if (/docker|containerd|kubepods|lxc|podman/i.test(cgroup)) { + return true; + } + } catch { + // Ignore errors (file may not exist or be readable) + } + + return false; +} // ── Help text ────────────────────────────────────────────────────────── @@ -27,9 +63,9 @@ function showHelp(): void { console.log(` ${theme.cmd('tinyclaw')} ${theme.dim('')}`); console.log(); console.log(` ${theme.label('Commands')}`); - console.log( - ` ${theme.cmd('setup')} Interactive setup wizard (use --web for browser onboarding)`, - ); + console.log(` ${theme.cmd('setup')} Interactive setup wizard`); + console.log(` ${theme.dim('Use --web for browser onboarding')}`); + console.log(` ${theme.dim('Use --docker for Docker/container environments (auto-detected)')}`); console.log(` ${theme.cmd('start')} Start the Tiny Claw agent`); console.log(` ${theme.cmd('config')} Manage models, providers, and settings`); console.log(` ${theme.cmd('seed')} Show your Tiny Claw's soul seed`); @@ -39,6 +75,8 @@ function showHelp(): void { ); console.log(); console.log(` ${theme.label('Options')}`); + console.log(` ${theme.dim('--docker')} Force web-based setup for Docker/container environments`); + console.log(` ${theme.dim('--web')} Use web-based setup instead of CLI wizard`); console.log(` ${theme.dim('--verbose')} Show debug-level logs during start`); console.log(` ${theme.dim('--version, -v')} Show version number`); console.log(` ${theme.dim('--help, -h')} Show this help message`); @@ -53,7 +91,11 @@ async function main(): Promise { switch (command) { case 'setup': { - if (args.includes('--web')) { + // Detect Docker/container environment and auto-route to web mode + const isDocker = args.includes('--docker') || detectDockerEnvironment(); + const isWeb = args.includes('--web') || isDocker; + + if (isWeb) { // Web setup goes through supervisor so the restart mechanism works const { supervisedStart } = await import('./supervisor.js'); await supervisedStart();