From 554d031f478ebcd3417fd76d8d695bce7cea7013 Mon Sep 17 00:00:00 2001 From: Youhana Sheriff Date: Tue, 10 Mar 2026 07:35:16 +0530 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 container environment detection and --docker flag to route setup to the web-based flow, preventing CLI prompts from hanging in non-TTY container environments. Changes: - Add isRunningInContainer() in detect-container.ts (shared utility) - Auto-detect Docker/CI via env vars, .dockerenv, and cgroup - Add --docker flag as alias for --web in container contexts - Show container warning in interactive setup with escape hatch - Update help text with --docker documentation --- src/cli/src/commands/setup.ts | 29 +++++++++++++++++++++++++++++ src/cli/src/detect-container.ts | 27 +++++++++++++++++++++++++++ src/cli/src/index.ts | 30 +++++++++++++++++++++--------- 3 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 src/cli/src/detect-container.ts diff --git a/src/cli/src/commands/setup.ts b/src/cli/src/commands/setup.ts index de57bbe..40a9f12 100644 --- a/src/cli/src/commands/setup.ts +++ b/src/cli/src/commands/setup.ts @@ -51,6 +51,7 @@ import { buildProviderKeyName, SecretsManager } from '@tinyclaw/secrets'; import type { StreamCallback } from '@tinyclaw/types'; import { createWebUI } from '@tinyclaw/web'; import QRCode from 'qrcode'; +import { isRunningInContainer } from '../detect-container.js'; import { showBanner } from '../ui/banner.js'; import { theme } from '../ui/theme.js'; @@ -210,6 +211,34 @@ export async function setupCommand(): Promise { p.intro(theme.brand("Let's set up Tiny Claw")); + // --- 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); + } + } + // --- Security warning ----------------------------------------------- p.note( diff --git a/src/cli/src/detect-container.ts b/src/cli/src/detect-container.ts new file mode 100644 index 0000000..17f773f --- /dev/null +++ b/src/cli/src/detect-container.ts @@ -0,0 +1,27 @@ +import { existsSync, readFileSync } from 'node:fs'; + +/** + * Detect if running inside a Docker container or CI environment. + * Checks for common indicators: .dockerenv, cgroup, CI env vars, container-specific env vars. + */ +export 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; +} diff --git a/src/cli/src/index.ts b/src/cli/src/index.ts index e73e32c..6e5745a 100644 --- a/src/cli/src/index.ts +++ b/src/cli/src/index.ts @@ -6,16 +6,18 @@ * 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 { isRunningInContainer } from './detect-container.js'; import { getVersion, showBanner } from './ui/banner.js'; import { theme } from './ui/theme.js'; @@ -27,8 +29,10 @@ 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`); + console.log(` ${theme.dim('Use --web for browser onboarding')}`); console.log( - ` ${theme.cmd('setup')} Interactive setup wizard (use --web for browser onboarding)`, + ` ${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`); @@ -40,6 +44,10 @@ 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`); @@ -54,7 +62,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') || isRunningInContainer(); + 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();