devs-cli 0.1.3__tar.gz → 0.1.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devs_cli-0.1.3/devs_cli.egg-info → devs_cli-0.1.5}/PKG-INFO +3 -3
- {devs_cli-0.1.3 → devs_cli-0.1.5}/README.md +2 -2
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/cli.py +310 -122
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/config.py +10 -2
- {devs_cli-0.1.3 → devs_cli-0.1.5/devs_cli.egg-info}/PKG-INFO +3 -3
- {devs_cli-0.1.3 → devs_cli-0.1.5}/pyproject.toml +2 -2
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli.py +114 -23
- {devs_cli-0.1.3 → devs_cli-0.1.5}/LICENSE +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/__init__.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/core/__init__.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/core/integration.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/exceptions.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs/utils/__init__.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs_cli.egg-info/SOURCES.txt +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs_cli.egg-info/dependency_links.txt +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs_cli.egg-info/entry_points.txt +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs_cli.egg-info/requires.txt +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/devs_cli.egg-info/top_level.txt +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/setup.cfg +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli_clean.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli_misc.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli_start.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli_stop.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_cli_vscode.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_container_manager.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_e2e.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_integration.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_live_mode.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_project.py +0 -0
- {devs_cli-0.1.3 → devs_cli-0.1.5}/tests/test_workspace_manager.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devs-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: DevContainer Management Tool - Manage multiple named devcontainers for any project
|
|
5
5
|
Author: Dan Lester
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,9 +81,9 @@ devs start frontend --env DEBUG=true --env API_URL=http://localhost:3000
|
|
|
81
81
|
devs claude frontend "Fix the tests" --env NODE_ENV=test
|
|
82
82
|
|
|
83
83
|
# Set up Claude authentication (once per host)
|
|
84
|
-
devs claude
|
|
84
|
+
devs claude --auth
|
|
85
85
|
# Or with API key
|
|
86
|
-
devs claude
|
|
86
|
+
devs claude --auth --api-key <YOUR_API_KEY>
|
|
87
87
|
|
|
88
88
|
# Clean up when done
|
|
89
89
|
devs stop frontend backend
|
|
@@ -40,9 +40,9 @@ devs start frontend --env DEBUG=true --env API_URL=http://localhost:3000
|
|
|
40
40
|
devs claude frontend "Fix the tests" --env NODE_ENV=test
|
|
41
41
|
|
|
42
42
|
# Set up Claude authentication (once per host)
|
|
43
|
-
devs claude
|
|
43
|
+
devs claude --auth
|
|
44
44
|
# Or with API key
|
|
45
|
-
devs claude
|
|
45
|
+
devs claude --auth --api-key <YOUR_API_KEY>
|
|
46
46
|
|
|
47
47
|
# Clean up when done
|
|
48
48
|
devs stop frontend backend
|
|
@@ -328,67 +328,80 @@ def shell(dev_name: str, live: bool, env: tuple, debug: bool) -> None:
|
|
|
328
328
|
|
|
329
329
|
|
|
330
330
|
@cli.command()
|
|
331
|
-
@click.argument('dev_name')
|
|
332
|
-
@click.argument('prompt')
|
|
331
|
+
@click.argument('dev_name', required=False)
|
|
332
|
+
@click.argument('prompt', required=False)
|
|
333
|
+
@click.option('--auth', is_flag=True, help='Set up Claude authentication for devcontainers')
|
|
334
|
+
@click.option('--api-key', help='Claude API key to authenticate with (use with --auth)')
|
|
333
335
|
@click.option('--reset-workspace', is_flag=True, help='Reset workspace contents before execution')
|
|
334
336
|
@click.option('--live', is_flag=True, help='Start container with current directory mounted as workspace')
|
|
335
337
|
@click.option('--env', multiple=True, help='Environment variables to pass to container (format: VAR=value)')
|
|
336
338
|
@debug_option
|
|
337
|
-
def claude(dev_name: str, prompt: str, reset_workspace: bool, live: bool, env: tuple, debug: bool) -> None:
|
|
338
|
-
"""Execute Claude CLI in devcontainer.
|
|
339
|
-
|
|
339
|
+
def claude(dev_name: str, prompt: str, auth: bool, api_key: str, reset_workspace: bool, live: bool, env: tuple, debug: bool) -> None:
|
|
340
|
+
"""Execute Claude CLI in devcontainer or set up authentication.
|
|
341
|
+
|
|
340
342
|
DEV_NAME: Development environment name
|
|
341
343
|
PROMPT: Prompt to send to Claude
|
|
342
|
-
|
|
344
|
+
|
|
343
345
|
Example: devs claude sally "Summarize this codebase"
|
|
344
346
|
Example: devs claude sally "Fix the tests" --reset-workspace
|
|
345
347
|
Example: devs claude sally "Fix the tests" --live # Run with current directory
|
|
346
348
|
Example: devs claude sally "Start the server" --env QUART_PORT=5001
|
|
349
|
+
Example: devs claude --auth # Interactive authentication
|
|
350
|
+
Example: devs claude --auth --api-key <YOUR_KEY> # API key authentication
|
|
347
351
|
"""
|
|
352
|
+
# Handle authentication mode
|
|
353
|
+
if auth:
|
|
354
|
+
_handle_claude_auth(api_key=api_key, debug=debug)
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
# Validate required arguments for execution mode
|
|
358
|
+
if not dev_name or not prompt:
|
|
359
|
+
raise click.UsageError("DEV_NAME and PROMPT are required unless using --auth")
|
|
360
|
+
|
|
348
361
|
check_dependencies()
|
|
349
362
|
project = get_project()
|
|
350
|
-
|
|
363
|
+
|
|
351
364
|
# Load environment variables from DEVS.yml and merge with CLI --env flags
|
|
352
365
|
devs_env = DevsConfigLoader.load_env_vars(dev_name, project.info.name)
|
|
353
366
|
cli_env = parse_env_vars(env) if env else {}
|
|
354
367
|
extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
|
|
355
|
-
|
|
368
|
+
|
|
356
369
|
if extra_env:
|
|
357
370
|
console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
|
|
358
|
-
|
|
371
|
+
|
|
359
372
|
container_manager = ContainerManager(project, config)
|
|
360
373
|
workspace_manager = WorkspaceManager(project, config)
|
|
361
|
-
|
|
374
|
+
|
|
362
375
|
try:
|
|
363
376
|
# Ensure workspace exists (handles live mode and reset internally)
|
|
364
377
|
workspace_dir = workspace_manager.create_workspace(dev_name, reset_contents=reset_workspace, live=live)
|
|
365
378
|
# Ensure container is running
|
|
366
379
|
container_manager.ensure_container_running(
|
|
367
|
-
dev_name=dev_name,
|
|
368
|
-
workspace_dir=workspace_dir,
|
|
369
|
-
force_rebuild=False,
|
|
370
|
-
debug=debug,
|
|
371
|
-
live=live,
|
|
380
|
+
dev_name=dev_name,
|
|
381
|
+
workspace_dir=workspace_dir,
|
|
382
|
+
force_rebuild=False,
|
|
383
|
+
debug=debug,
|
|
384
|
+
live=live,
|
|
372
385
|
extra_env=extra_env
|
|
373
386
|
)
|
|
374
|
-
|
|
387
|
+
|
|
375
388
|
# Execute Claude
|
|
376
389
|
console.print(f"🤖 Executing Claude in {dev_name}...")
|
|
377
390
|
if reset_workspace and not live:
|
|
378
391
|
console.print("🗑️ Workspace contents reset")
|
|
379
392
|
console.print(f"📝 Prompt: {prompt}")
|
|
380
393
|
console.print("")
|
|
381
|
-
|
|
394
|
+
|
|
382
395
|
success, output, error = container_manager.exec_claude(
|
|
383
396
|
dev_name=dev_name,
|
|
384
|
-
workspace_dir=workspace_dir,
|
|
385
|
-
prompt=prompt,
|
|
386
|
-
debug=debug,
|
|
387
|
-
stream=True,
|
|
388
|
-
live=live,
|
|
397
|
+
workspace_dir=workspace_dir,
|
|
398
|
+
prompt=prompt,
|
|
399
|
+
debug=debug,
|
|
400
|
+
stream=True,
|
|
401
|
+
live=live,
|
|
389
402
|
extra_env=extra_env
|
|
390
403
|
)
|
|
391
|
-
|
|
404
|
+
|
|
392
405
|
console.print("") # Add spacing after streamed output
|
|
393
406
|
if success:
|
|
394
407
|
console.print("✅ Claude execution completed")
|
|
@@ -399,197 +412,372 @@ def claude(dev_name: str, prompt: str, reset_workspace: bool, live: bool, env: t
|
|
|
399
412
|
console.print("🚫 Error:")
|
|
400
413
|
console.print(error)
|
|
401
414
|
sys.exit(1)
|
|
402
|
-
|
|
415
|
+
|
|
403
416
|
except (ContainerError, WorkspaceError) as e:
|
|
404
417
|
console.print(f"❌ Error executing Claude in {dev_name}: {e}")
|
|
405
418
|
sys.exit(1)
|
|
406
419
|
|
|
407
420
|
|
|
421
|
+
def _handle_claude_auth(api_key: str, debug: bool) -> None:
|
|
422
|
+
"""Handle Claude authentication setup.
|
|
423
|
+
|
|
424
|
+
This configures Claude authentication that will be shared across
|
|
425
|
+
all devcontainers for this project. The authentication is stored
|
|
426
|
+
on the host and bind-mounted into containers.
|
|
427
|
+
"""
|
|
428
|
+
try:
|
|
429
|
+
# Ensure Claude config directory exists
|
|
430
|
+
config.ensure_directories()
|
|
431
|
+
|
|
432
|
+
console.print("🔐 Setting up Claude authentication...")
|
|
433
|
+
console.print(f" Configuration will be saved to: {config.claude_config_dir}")
|
|
434
|
+
|
|
435
|
+
if api_key:
|
|
436
|
+
# Set API key directly using Claude CLI
|
|
437
|
+
console.print(" Using provided API key...")
|
|
438
|
+
|
|
439
|
+
# Set CLAUDE_CONFIG_DIR to our config directory and run auth with API key
|
|
440
|
+
env = os.environ.copy()
|
|
441
|
+
env['CLAUDE_CONFIG_DIR'] = str(config.claude_config_dir)
|
|
442
|
+
|
|
443
|
+
cmd = ['claude', 'auth', '--key', api_key]
|
|
444
|
+
|
|
445
|
+
if debug:
|
|
446
|
+
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
447
|
+
console.print(f"[dim]CLAUDE_CONFIG_DIR: {config.claude_config_dir}[/dim]")
|
|
448
|
+
|
|
449
|
+
result = subprocess.run(
|
|
450
|
+
cmd,
|
|
451
|
+
env=env,
|
|
452
|
+
capture_output=True,
|
|
453
|
+
text=True
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
if result.returncode != 0:
|
|
457
|
+
error_msg = result.stderr or result.stdout or "Unknown error"
|
|
458
|
+
raise Exception(f"Claude authentication failed: {error_msg}")
|
|
459
|
+
|
|
460
|
+
else:
|
|
461
|
+
# Interactive authentication
|
|
462
|
+
console.print(" Starting interactive authentication...")
|
|
463
|
+
console.print(" Follow the prompts to authenticate with Claude")
|
|
464
|
+
console.print("")
|
|
465
|
+
|
|
466
|
+
# Set CLAUDE_CONFIG_DIR to our config directory
|
|
467
|
+
env = os.environ.copy()
|
|
468
|
+
env['CLAUDE_CONFIG_DIR'] = str(config.claude_config_dir)
|
|
469
|
+
|
|
470
|
+
cmd = ['claude', 'auth']
|
|
471
|
+
|
|
472
|
+
if debug:
|
|
473
|
+
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
474
|
+
console.print(f"[dim]CLAUDE_CONFIG_DIR: {config.claude_config_dir}[/dim]")
|
|
475
|
+
|
|
476
|
+
# Run interactively
|
|
477
|
+
result = subprocess.run(
|
|
478
|
+
cmd,
|
|
479
|
+
env=env,
|
|
480
|
+
check=False
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
if result.returncode != 0:
|
|
484
|
+
raise Exception("Claude authentication was cancelled or failed")
|
|
485
|
+
|
|
486
|
+
console.print("")
|
|
487
|
+
console.print("✅ Claude authentication configured successfully!")
|
|
488
|
+
console.print(f" Configuration saved to: {config.claude_config_dir}")
|
|
489
|
+
console.print(" This authentication will be shared across all devcontainers")
|
|
490
|
+
console.print("")
|
|
491
|
+
console.print("💡 You can now use Claude in any devcontainer:")
|
|
492
|
+
console.print(" devs claude <dev-name> 'Your prompt here'")
|
|
493
|
+
|
|
494
|
+
except FileNotFoundError:
|
|
495
|
+
console.print("❌ Claude CLI not found on host machine")
|
|
496
|
+
console.print("")
|
|
497
|
+
console.print("Please install Claude CLI first:")
|
|
498
|
+
console.print(" npm install -g @anthropic-ai/claude-cli")
|
|
499
|
+
console.print("")
|
|
500
|
+
console.print("Note: Claude needs to be installed on the host machine")
|
|
501
|
+
console.print(" for authentication. It's already available in containers.")
|
|
502
|
+
sys.exit(1)
|
|
503
|
+
|
|
504
|
+
except Exception as e:
|
|
505
|
+
console.print(f"❌ Failed to configure Claude authentication: {e}")
|
|
506
|
+
if debug:
|
|
507
|
+
import traceback
|
|
508
|
+
console.print(traceback.format_exc())
|
|
509
|
+
sys.exit(1)
|
|
510
|
+
|
|
511
|
+
|
|
408
512
|
@cli.command()
|
|
409
|
-
@click.argument('dev_name')
|
|
513
|
+
@click.argument('dev_name', required=False)
|
|
514
|
+
@click.argument('prompt', required=False)
|
|
515
|
+
@click.option('--auth', is_flag=True, help='Set up Codex authentication for devcontainers')
|
|
516
|
+
@click.option('--api-key', help='OpenAI API key to authenticate with (use with --auth)')
|
|
410
517
|
@click.option('--reset-workspace', is_flag=True, help='Reset workspace contents before execution')
|
|
411
518
|
@click.option('--live', is_flag=True, help='Start container with current directory mounted as workspace')
|
|
412
519
|
@click.option('--env', multiple=True, help='Environment variables to pass to container (format: VAR=value)')
|
|
413
520
|
@debug_option
|
|
414
|
-
def
|
|
415
|
-
"""
|
|
416
|
-
|
|
521
|
+
def codex(dev_name: str, prompt: str, auth: bool, api_key: str, reset_workspace: bool, live: bool, env: tuple, debug: bool) -> None:
|
|
522
|
+
"""Execute OpenAI Codex CLI in devcontainer or set up authentication.
|
|
523
|
+
|
|
417
524
|
DEV_NAME: Development environment name
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
Example: devs
|
|
421
|
-
Example: devs
|
|
422
|
-
Example: devs
|
|
525
|
+
PROMPT: Prompt to send to Codex
|
|
526
|
+
|
|
527
|
+
Example: devs codex sally "Summarize this codebase"
|
|
528
|
+
Example: devs codex sally "Fix the tests" --reset-workspace
|
|
529
|
+
Example: devs codex sally "Fix the tests" --live # Run with current directory
|
|
530
|
+
Example: devs codex sally "Start the server" --env QUART_PORT=5001
|
|
531
|
+
Example: devs codex --auth # Interactive authentication
|
|
532
|
+
Example: devs codex --auth --api-key <YOUR_KEY> # API key authentication
|
|
423
533
|
"""
|
|
534
|
+
# Handle authentication mode
|
|
535
|
+
if auth:
|
|
536
|
+
_handle_codex_auth(api_key=api_key, debug=debug)
|
|
537
|
+
return
|
|
538
|
+
|
|
539
|
+
# Validate required arguments for execution mode
|
|
540
|
+
if not dev_name or not prompt:
|
|
541
|
+
raise click.UsageError("DEV_NAME and PROMPT are required unless using --auth")
|
|
542
|
+
|
|
424
543
|
check_dependencies()
|
|
425
544
|
project = get_project()
|
|
426
|
-
|
|
427
|
-
# Load full DEVS configuration
|
|
428
|
-
try:
|
|
429
|
-
project_name = project.info.name
|
|
430
|
-
except Exception:
|
|
431
|
-
project_name = None
|
|
432
|
-
|
|
433
|
-
devs_config = DevsConfigLoader.load(project_name)
|
|
434
|
-
|
|
435
|
-
# Get test command from config
|
|
436
|
-
command = devs_config.ci_test_command
|
|
437
|
-
|
|
545
|
+
|
|
438
546
|
# Load environment variables from DEVS.yml and merge with CLI --env flags
|
|
439
|
-
devs_env =
|
|
547
|
+
devs_env = DevsConfigLoader.load_env_vars(dev_name, project.info.name)
|
|
440
548
|
cli_env = parse_env_vars(env) if env else {}
|
|
441
549
|
extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
|
|
442
|
-
|
|
550
|
+
|
|
443
551
|
if extra_env:
|
|
444
552
|
console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
|
|
445
|
-
|
|
553
|
+
|
|
446
554
|
container_manager = ContainerManager(project, config)
|
|
447
555
|
workspace_manager = WorkspaceManager(project, config)
|
|
448
|
-
|
|
556
|
+
|
|
449
557
|
try:
|
|
450
558
|
# Ensure workspace exists (handles live mode and reset internally)
|
|
451
559
|
workspace_dir = workspace_manager.create_workspace(dev_name, reset_contents=reset_workspace, live=live)
|
|
452
560
|
# Ensure container is running
|
|
453
561
|
container_manager.ensure_container_running(
|
|
454
|
-
dev_name=dev_name,
|
|
455
|
-
workspace_dir=workspace_dir,
|
|
456
|
-
force_rebuild=False,
|
|
457
|
-
debug=debug,
|
|
458
|
-
live=live,
|
|
562
|
+
dev_name=dev_name,
|
|
563
|
+
workspace_dir=workspace_dir,
|
|
564
|
+
force_rebuild=False,
|
|
565
|
+
debug=debug,
|
|
566
|
+
live=live,
|
|
459
567
|
extra_env=extra_env
|
|
460
568
|
)
|
|
461
|
-
|
|
462
|
-
# Execute
|
|
463
|
-
console.print(f"
|
|
569
|
+
|
|
570
|
+
# Execute Codex
|
|
571
|
+
console.print(f"🤖 Executing Codex in {dev_name}...")
|
|
464
572
|
if reset_workspace and not live:
|
|
465
573
|
console.print("🗑️ Workspace contents reset")
|
|
466
|
-
console.print(f"
|
|
574
|
+
console.print(f"📝 Prompt: {prompt}")
|
|
467
575
|
console.print("")
|
|
468
|
-
|
|
469
|
-
success, output, error = container_manager.
|
|
470
|
-
dev_name=dev_name,
|
|
471
|
-
workspace_dir=workspace_dir,
|
|
472
|
-
|
|
473
|
-
debug=debug,
|
|
474
|
-
stream=True,
|
|
475
|
-
live=live,
|
|
576
|
+
|
|
577
|
+
success, output, error = container_manager.exec_codex(
|
|
578
|
+
dev_name=dev_name,
|
|
579
|
+
workspace_dir=workspace_dir,
|
|
580
|
+
prompt=prompt,
|
|
581
|
+
debug=debug,
|
|
582
|
+
stream=True,
|
|
583
|
+
live=live,
|
|
476
584
|
extra_env=extra_env
|
|
477
585
|
)
|
|
478
|
-
|
|
586
|
+
|
|
479
587
|
console.print("") # Add spacing after streamed output
|
|
480
588
|
if success:
|
|
481
|
-
console.print("✅
|
|
589
|
+
console.print("✅ Codex execution completed")
|
|
482
590
|
else:
|
|
483
|
-
console.print("❌
|
|
591
|
+
console.print("❌ Codex execution failed")
|
|
484
592
|
if error:
|
|
485
593
|
console.print("")
|
|
486
594
|
console.print("🚫 Error:")
|
|
487
595
|
console.print(error)
|
|
488
596
|
sys.exit(1)
|
|
489
|
-
|
|
597
|
+
|
|
490
598
|
except (ContainerError, WorkspaceError) as e:
|
|
491
|
-
console.print(f"❌ Error
|
|
599
|
+
console.print(f"❌ Error executing Codex in {dev_name}: {e}")
|
|
492
600
|
sys.exit(1)
|
|
493
601
|
|
|
494
602
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
"""Set up Claude authentication for devcontainers.
|
|
500
|
-
|
|
501
|
-
This configures Claude authentication that will be shared across
|
|
603
|
+
def _handle_codex_auth(api_key: str, debug: bool) -> None:
|
|
604
|
+
"""Handle Codex authentication setup.
|
|
605
|
+
|
|
606
|
+
This configures Codex authentication that will be shared across
|
|
502
607
|
all devcontainers for this project. The authentication is stored
|
|
503
608
|
on the host and bind-mounted into containers.
|
|
504
|
-
|
|
505
|
-
Example: devs claude-auth
|
|
506
|
-
Example: devs claude-auth --api-key <YOUR_API_KEY>
|
|
507
609
|
"""
|
|
508
|
-
|
|
509
610
|
try:
|
|
510
|
-
# Ensure
|
|
611
|
+
# Ensure Codex config directory exists
|
|
511
612
|
config.ensure_directories()
|
|
512
|
-
|
|
513
|
-
console.print("🔐 Setting up
|
|
514
|
-
console.print(f" Configuration will be saved to: {config.
|
|
515
|
-
|
|
613
|
+
|
|
614
|
+
console.print("🔐 Setting up Codex authentication...")
|
|
615
|
+
console.print(f" Configuration will be saved to: {config.codex_config_dir}")
|
|
616
|
+
|
|
516
617
|
if api_key:
|
|
517
|
-
# Set API key directly using
|
|
618
|
+
# Set API key directly using Codex CLI
|
|
518
619
|
console.print(" Using provided API key...")
|
|
519
|
-
|
|
520
|
-
# Set
|
|
620
|
+
|
|
621
|
+
# Set CODEX_CONFIG_HOME to our config directory and run auth with API key
|
|
521
622
|
env = os.environ.copy()
|
|
522
|
-
env['
|
|
523
|
-
|
|
524
|
-
cmd = ['
|
|
525
|
-
|
|
623
|
+
env['CODEX_CONFIG_HOME'] = str(config.codex_config_dir)
|
|
624
|
+
|
|
625
|
+
cmd = ['codex', 'auth', '--api-key', api_key]
|
|
626
|
+
|
|
526
627
|
if debug:
|
|
527
628
|
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
528
|
-
console.print(f"[dim]
|
|
529
|
-
|
|
629
|
+
console.print(f"[dim]CODEX_CONFIG_HOME: {config.codex_config_dir}[/dim]")
|
|
630
|
+
|
|
530
631
|
result = subprocess.run(
|
|
531
632
|
cmd,
|
|
532
633
|
env=env,
|
|
533
634
|
capture_output=True,
|
|
534
635
|
text=True
|
|
535
636
|
)
|
|
536
|
-
|
|
637
|
+
|
|
537
638
|
if result.returncode != 0:
|
|
538
639
|
error_msg = result.stderr or result.stdout or "Unknown error"
|
|
539
|
-
raise Exception(f"
|
|
540
|
-
|
|
640
|
+
raise Exception(f"Codex authentication failed: {error_msg}")
|
|
641
|
+
|
|
541
642
|
else:
|
|
542
643
|
# Interactive authentication
|
|
543
644
|
console.print(" Starting interactive authentication...")
|
|
544
|
-
console.print(" Follow the prompts to authenticate with
|
|
645
|
+
console.print(" Follow the prompts to authenticate with Codex")
|
|
545
646
|
console.print("")
|
|
546
|
-
|
|
547
|
-
# Set
|
|
647
|
+
|
|
648
|
+
# Set CODEX_CONFIG_HOME to our config directory
|
|
548
649
|
env = os.environ.copy()
|
|
549
|
-
env['
|
|
550
|
-
|
|
551
|
-
cmd = ['
|
|
552
|
-
|
|
650
|
+
env['CODEX_CONFIG_HOME'] = str(config.codex_config_dir)
|
|
651
|
+
|
|
652
|
+
cmd = ['codex', 'auth']
|
|
653
|
+
|
|
553
654
|
if debug:
|
|
554
655
|
console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
|
|
555
|
-
console.print(f"[dim]
|
|
556
|
-
|
|
656
|
+
console.print(f"[dim]CODEX_CONFIG_HOME: {config.codex_config_dir}[/dim]")
|
|
657
|
+
|
|
557
658
|
# Run interactively
|
|
558
659
|
result = subprocess.run(
|
|
559
660
|
cmd,
|
|
560
661
|
env=env,
|
|
561
662
|
check=False
|
|
562
663
|
)
|
|
563
|
-
|
|
664
|
+
|
|
564
665
|
if result.returncode != 0:
|
|
565
|
-
raise Exception("
|
|
566
|
-
|
|
666
|
+
raise Exception("Codex authentication was cancelled or failed")
|
|
667
|
+
|
|
567
668
|
console.print("")
|
|
568
|
-
console.print("✅
|
|
569
|
-
console.print(f" Configuration saved to: {config.
|
|
669
|
+
console.print("✅ Codex authentication configured successfully!")
|
|
670
|
+
console.print(f" Configuration saved to: {config.codex_config_dir}")
|
|
570
671
|
console.print(" This authentication will be shared across all devcontainers")
|
|
571
672
|
console.print("")
|
|
572
|
-
console.print("💡 You can now use
|
|
573
|
-
console.print(" devs
|
|
574
|
-
|
|
673
|
+
console.print("💡 You can now use Codex in any devcontainer:")
|
|
674
|
+
console.print(" devs codex <dev-name> 'Your prompt here'")
|
|
675
|
+
|
|
575
676
|
except FileNotFoundError:
|
|
576
|
-
console.print("❌
|
|
677
|
+
console.print("❌ Codex CLI not found on host machine")
|
|
577
678
|
console.print("")
|
|
578
|
-
console.print("Please install
|
|
579
|
-
console.print(" npm install -g @
|
|
679
|
+
console.print("Please install Codex CLI first:")
|
|
680
|
+
console.print(" npm install -g @openai/codex")
|
|
580
681
|
console.print("")
|
|
581
|
-
console.print("Note:
|
|
682
|
+
console.print("Note: Codex needs to be installed on the host machine")
|
|
582
683
|
console.print(" for authentication. It's already available in containers.")
|
|
583
684
|
sys.exit(1)
|
|
584
|
-
|
|
685
|
+
|
|
585
686
|
except Exception as e:
|
|
586
|
-
console.print(f"❌ Failed to configure
|
|
687
|
+
console.print(f"❌ Failed to configure Codex authentication: {e}")
|
|
587
688
|
if debug:
|
|
588
689
|
import traceback
|
|
589
690
|
console.print(traceback.format_exc())
|
|
590
691
|
sys.exit(1)
|
|
591
692
|
|
|
592
693
|
|
|
694
|
+
@cli.command()
|
|
695
|
+
@click.argument('dev_name')
|
|
696
|
+
@click.option('--reset-workspace', is_flag=True, help='Reset workspace contents before execution')
|
|
697
|
+
@click.option('--live', is_flag=True, help='Start container with current directory mounted as workspace')
|
|
698
|
+
@click.option('--env', multiple=True, help='Environment variables to pass to container (format: VAR=value)')
|
|
699
|
+
@debug_option
|
|
700
|
+
def runtests(dev_name: str, reset_workspace: bool, live: bool, env: tuple, debug: bool) -> None:
|
|
701
|
+
"""Run tests in devcontainer.
|
|
702
|
+
|
|
703
|
+
DEV_NAME: Development environment name
|
|
704
|
+
|
|
705
|
+
Example: devs runtests sally
|
|
706
|
+
Example: devs runtests sally --reset-workspace
|
|
707
|
+
Example: devs runtests sally --live # Run with current directory
|
|
708
|
+
Example: devs runtests sally --env NODE_ENV=test
|
|
709
|
+
"""
|
|
710
|
+
check_dependencies()
|
|
711
|
+
project = get_project()
|
|
712
|
+
|
|
713
|
+
# Load full DEVS configuration
|
|
714
|
+
try:
|
|
715
|
+
project_name = project.info.name
|
|
716
|
+
except Exception:
|
|
717
|
+
project_name = None
|
|
718
|
+
|
|
719
|
+
devs_config = DevsConfigLoader.load(project_name)
|
|
720
|
+
|
|
721
|
+
# Get test command from config
|
|
722
|
+
command = devs_config.ci_test_command
|
|
723
|
+
|
|
724
|
+
# Load environment variables from DEVS.yml and merge with CLI --env flags
|
|
725
|
+
devs_env = devs_config.get_env_vars(dev_name)
|
|
726
|
+
cli_env = parse_env_vars(env) if env else {}
|
|
727
|
+
extra_env = merge_env_vars(devs_env, cli_env) if devs_env or cli_env else None
|
|
728
|
+
|
|
729
|
+
if extra_env:
|
|
730
|
+
console.print(f"🔧 Environment variables: {', '.join(f'{k}={v}' for k, v in extra_env.items())}")
|
|
731
|
+
|
|
732
|
+
container_manager = ContainerManager(project, config)
|
|
733
|
+
workspace_manager = WorkspaceManager(project, config)
|
|
734
|
+
|
|
735
|
+
try:
|
|
736
|
+
# Ensure workspace exists (handles live mode and reset internally)
|
|
737
|
+
workspace_dir = workspace_manager.create_workspace(dev_name, reset_contents=reset_workspace, live=live)
|
|
738
|
+
# Ensure container is running
|
|
739
|
+
container_manager.ensure_container_running(
|
|
740
|
+
dev_name=dev_name,
|
|
741
|
+
workspace_dir=workspace_dir,
|
|
742
|
+
force_rebuild=False,
|
|
743
|
+
debug=debug,
|
|
744
|
+
live=live,
|
|
745
|
+
extra_env=extra_env
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
# Execute test command
|
|
749
|
+
console.print(f"🧪 Running tests in {dev_name}...")
|
|
750
|
+
if reset_workspace and not live:
|
|
751
|
+
console.print("🗑️ Workspace contents reset")
|
|
752
|
+
console.print(f"🔧 Command: {command}")
|
|
753
|
+
console.print("")
|
|
754
|
+
|
|
755
|
+
success, output, error = container_manager.exec_command(
|
|
756
|
+
dev_name=dev_name,
|
|
757
|
+
workspace_dir=workspace_dir,
|
|
758
|
+
command=command,
|
|
759
|
+
debug=debug,
|
|
760
|
+
stream=True,
|
|
761
|
+
live=live,
|
|
762
|
+
extra_env=extra_env
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
console.print("") # Add spacing after streamed output
|
|
766
|
+
if success:
|
|
767
|
+
console.print("✅ Tests completed successfully")
|
|
768
|
+
else:
|
|
769
|
+
console.print("❌ Tests failed")
|
|
770
|
+
if error:
|
|
771
|
+
console.print("")
|
|
772
|
+
console.print("🚫 Error:")
|
|
773
|
+
console.print(error)
|
|
774
|
+
sys.exit(1)
|
|
775
|
+
|
|
776
|
+
except (ContainerError, WorkspaceError) as e:
|
|
777
|
+
console.print(f"❌ Error running tests in {dev_name}: {e}")
|
|
778
|
+
sys.exit(1)
|
|
779
|
+
|
|
780
|
+
|
|
593
781
|
@cli.command()
|
|
594
782
|
@click.option('--all-projects', is_flag=True, help='List containers for all projects')
|
|
595
783
|
def list(all_projects: bool) -> None:
|
|
@@ -15,17 +15,24 @@ class Config(BaseConfig):
|
|
|
15
15
|
WORKSPACES_DIR = Path.home() / ".devs" / "workspaces"
|
|
16
16
|
BRIDGE_DIR = Path.home() / ".devs" / "bridge"
|
|
17
17
|
CLAUDE_CONFIG_DIR = Path.home() / ".devs" / "claudeconfig"
|
|
18
|
-
|
|
18
|
+
CODEX_CONFIG_DIR = Path.home() / ".devs" / "codexconfig"
|
|
19
|
+
|
|
19
20
|
def __init__(self) -> None:
|
|
20
21
|
"""Initialize configuration with environment variable overrides."""
|
|
21
22
|
super().__init__()
|
|
22
|
-
|
|
23
|
+
|
|
23
24
|
# CLI-specific configuration
|
|
24
25
|
claude_config_env = os.getenv("DEVS_CLAUDE_CONFIG_DIR")
|
|
25
26
|
if claude_config_env:
|
|
26
27
|
self.claude_config_dir = Path(claude_config_env)
|
|
27
28
|
else:
|
|
28
29
|
self.claude_config_dir = self.CLAUDE_CONFIG_DIR
|
|
30
|
+
|
|
31
|
+
codex_config_env = os.getenv("DEVS_CODEX_CONFIG_DIR")
|
|
32
|
+
if codex_config_env:
|
|
33
|
+
self.codex_config_dir = Path(codex_config_env)
|
|
34
|
+
else:
|
|
35
|
+
self.codex_config_dir = self.CODEX_CONFIG_DIR
|
|
29
36
|
|
|
30
37
|
def get_default_workspaces_dir(self) -> Path:
|
|
31
38
|
"""Get default workspaces directory for CLI package."""
|
|
@@ -43,6 +50,7 @@ class Config(BaseConfig):
|
|
|
43
50
|
"""Ensure required directories exist."""
|
|
44
51
|
super().ensure_directories()
|
|
45
52
|
self.claude_config_dir.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
self.codex_config_dir.mkdir(parents=True, exist_ok=True)
|
|
46
54
|
|
|
47
55
|
|
|
48
56
|
# Global config instance
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devs-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: DevContainer Management Tool - Manage multiple named devcontainers for any project
|
|
5
5
|
Author: Dan Lester
|
|
6
6
|
License-Expression: MIT
|
|
@@ -81,9 +81,9 @@ devs start frontend --env DEBUG=true --env API_URL=http://localhost:3000
|
|
|
81
81
|
devs claude frontend "Fix the tests" --env NODE_ENV=test
|
|
82
82
|
|
|
83
83
|
# Set up Claude authentication (once per host)
|
|
84
|
-
devs claude
|
|
84
|
+
devs claude --auth
|
|
85
85
|
# Or with API key
|
|
86
|
-
devs claude
|
|
86
|
+
devs claude --auth --api-key <YOUR_API_KEY>
|
|
87
87
|
|
|
88
88
|
# Clean up when done
|
|
89
89
|
devs stop frontend backend
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devs-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "DevContainer Management Tool - Manage multiple named devcontainers for any project"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -65,7 +65,7 @@ line-length = 88
|
|
|
65
65
|
target-version = ['py38']
|
|
66
66
|
|
|
67
67
|
[tool.mypy]
|
|
68
|
-
python_version = "
|
|
68
|
+
python_version = "3.8"
|
|
69
69
|
warn_return_any = true
|
|
70
70
|
warn_unused_configs = true
|
|
71
71
|
disallow_untyped_defs = true
|
|
@@ -123,31 +123,32 @@ class TestCLI:
|
|
|
123
123
|
assert result.exit_code != 0
|
|
124
124
|
assert "Missing argument" in result.output
|
|
125
125
|
|
|
126
|
-
def
|
|
127
|
-
"""Test claude
|
|
126
|
+
def test_claude_command_help(self):
|
|
127
|
+
"""Test claude command help."""
|
|
128
128
|
runner = CliRunner()
|
|
129
|
-
result = runner.invoke(cli, ['claude
|
|
130
|
-
|
|
129
|
+
result = runner.invoke(cli, ['claude', '--help'])
|
|
130
|
+
|
|
131
131
|
assert result.exit_code == 0
|
|
132
|
-
assert "
|
|
132
|
+
assert "Execute Claude CLI in devcontainer or set up authentication" in result.output
|
|
133
|
+
assert "--auth" in result.output
|
|
133
134
|
assert "--api-key" in result.output
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
@patch('devs.cli.subprocess.run')
|
|
136
137
|
@patch('devs.cli.config')
|
|
137
138
|
def test_claude_auth_with_api_key(self, mock_config, mock_subprocess):
|
|
138
|
-
"""Test claude
|
|
139
|
+
"""Test claude --auth command with API key."""
|
|
139
140
|
# Setup mocks
|
|
140
141
|
mock_config.claude_config_dir = '/tmp/test-claude-config'
|
|
141
142
|
mock_config.ensure_directories = Mock()
|
|
142
143
|
mock_subprocess.return_value.returncode = 0
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
runner = CliRunner()
|
|
145
|
-
result = runner.invoke(cli, ['claude
|
|
146
|
-
|
|
146
|
+
result = runner.invoke(cli, ['claude', '--auth', '--api-key', 'test-key-123'])
|
|
147
|
+
|
|
147
148
|
assert result.exit_code == 0
|
|
148
149
|
assert "Setting up Claude authentication" in result.output
|
|
149
150
|
assert "Claude authentication configured successfully" in result.output
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
# Verify subprocess was called with correct arguments
|
|
152
153
|
mock_subprocess.assert_called_once()
|
|
153
154
|
call_args = mock_subprocess.call_args
|
|
@@ -155,43 +156,133 @@ class TestCLI:
|
|
|
155
156
|
assert 'auth' in call_args[0][0]
|
|
156
157
|
assert '--key' in call_args[0][0]
|
|
157
158
|
assert 'test-key-123' in call_args[0][0]
|
|
158
|
-
|
|
159
|
+
|
|
159
160
|
@patch('devs.cli.subprocess.run')
|
|
160
161
|
@patch('devs.cli.config')
|
|
161
162
|
def test_claude_auth_interactive(self, mock_config, mock_subprocess):
|
|
162
|
-
"""Test claude
|
|
163
|
+
"""Test claude --auth command in interactive mode."""
|
|
163
164
|
# Setup mocks
|
|
164
165
|
mock_config.claude_config_dir = '/tmp/test-claude-config'
|
|
165
166
|
mock_config.ensure_directories = Mock()
|
|
166
167
|
mock_subprocess.return_value.returncode = 0
|
|
167
|
-
|
|
168
|
+
|
|
168
169
|
runner = CliRunner()
|
|
169
|
-
result = runner.invoke(cli, ['claude
|
|
170
|
-
|
|
170
|
+
result = runner.invoke(cli, ['claude', '--auth'])
|
|
171
|
+
|
|
171
172
|
assert result.exit_code == 0
|
|
172
173
|
assert "Setting up Claude authentication" in result.output
|
|
173
174
|
assert "Starting interactive authentication" in result.output
|
|
174
175
|
assert "Claude authentication configured successfully" in result.output
|
|
175
|
-
|
|
176
|
+
|
|
176
177
|
# Verify subprocess was called for interactive auth
|
|
177
178
|
mock_subprocess.assert_called_once()
|
|
178
179
|
call_args = mock_subprocess.call_args
|
|
179
180
|
assert 'claude' in call_args[0][0]
|
|
180
181
|
assert 'auth' in call_args[0][0]
|
|
181
182
|
assert '--key' not in call_args[0][0]
|
|
182
|
-
|
|
183
|
+
|
|
183
184
|
@patch('devs.cli.subprocess.run')
|
|
184
185
|
@patch('devs.cli.config')
|
|
185
186
|
def test_claude_auth_command_not_found(self, mock_config, mock_subprocess):
|
|
186
|
-
"""Test claude
|
|
187
|
+
"""Test claude --auth when claude CLI is not installed."""
|
|
187
188
|
# Setup mocks
|
|
188
189
|
mock_config.claude_config_dir = '/tmp/test-claude-config'
|
|
189
190
|
mock_config.ensure_directories = Mock()
|
|
190
191
|
mock_subprocess.side_effect = FileNotFoundError()
|
|
191
|
-
|
|
192
|
+
|
|
192
193
|
runner = CliRunner()
|
|
193
|
-
result = runner.invoke(cli, ['claude
|
|
194
|
-
|
|
194
|
+
result = runner.invoke(cli, ['claude', '--auth'])
|
|
195
|
+
|
|
195
196
|
assert result.exit_code == 1
|
|
196
197
|
assert "Claude CLI not found" in result.output
|
|
197
|
-
assert "npm install -g @anthropic-ai/claude-cli" in result.output
|
|
198
|
+
assert "npm install -g @anthropic-ai/claude-cli" in result.output
|
|
199
|
+
|
|
200
|
+
def test_claude_missing_args(self):
|
|
201
|
+
"""Test claude command without required args (not using --auth)."""
|
|
202
|
+
runner = CliRunner()
|
|
203
|
+
result = runner.invoke(cli, ['claude'])
|
|
204
|
+
|
|
205
|
+
assert result.exit_code != 0
|
|
206
|
+
assert "DEV_NAME and PROMPT are required unless using --auth" in result.output
|
|
207
|
+
|
|
208
|
+
def test_codex_command_help(self):
|
|
209
|
+
"""Test codex command help."""
|
|
210
|
+
runner = CliRunner()
|
|
211
|
+
result = runner.invoke(cli, ['codex', '--help'])
|
|
212
|
+
|
|
213
|
+
assert result.exit_code == 0
|
|
214
|
+
assert "Execute OpenAI Codex CLI in devcontainer or set up authentication" in result.output
|
|
215
|
+
assert "--auth" in result.output
|
|
216
|
+
assert "--api-key" in result.output
|
|
217
|
+
|
|
218
|
+
@patch('devs.cli.subprocess.run')
|
|
219
|
+
@patch('devs.cli.config')
|
|
220
|
+
def test_codex_auth_with_api_key(self, mock_config, mock_subprocess):
|
|
221
|
+
"""Test codex --auth command with API key."""
|
|
222
|
+
# Setup mocks
|
|
223
|
+
mock_config.codex_config_dir = '/tmp/test-codex-config'
|
|
224
|
+
mock_config.ensure_directories = Mock()
|
|
225
|
+
mock_subprocess.return_value.returncode = 0
|
|
226
|
+
|
|
227
|
+
runner = CliRunner()
|
|
228
|
+
result = runner.invoke(cli, ['codex', '--auth', '--api-key', 'test-key-123'])
|
|
229
|
+
|
|
230
|
+
assert result.exit_code == 0
|
|
231
|
+
assert "Setting up Codex authentication" in result.output
|
|
232
|
+
assert "Codex authentication configured successfully" in result.output
|
|
233
|
+
|
|
234
|
+
# Verify subprocess was called with correct arguments
|
|
235
|
+
mock_subprocess.assert_called_once()
|
|
236
|
+
call_args = mock_subprocess.call_args
|
|
237
|
+
assert 'codex' in call_args[0][0]
|
|
238
|
+
assert 'auth' in call_args[0][0]
|
|
239
|
+
assert '--api-key' in call_args[0][0]
|
|
240
|
+
assert 'test-key-123' in call_args[0][0]
|
|
241
|
+
|
|
242
|
+
@patch('devs.cli.subprocess.run')
|
|
243
|
+
@patch('devs.cli.config')
|
|
244
|
+
def test_codex_auth_interactive(self, mock_config, mock_subprocess):
|
|
245
|
+
"""Test codex --auth command in interactive mode."""
|
|
246
|
+
# Setup mocks
|
|
247
|
+
mock_config.codex_config_dir = '/tmp/test-codex-config'
|
|
248
|
+
mock_config.ensure_directories = Mock()
|
|
249
|
+
mock_subprocess.return_value.returncode = 0
|
|
250
|
+
|
|
251
|
+
runner = CliRunner()
|
|
252
|
+
result = runner.invoke(cli, ['codex', '--auth'])
|
|
253
|
+
|
|
254
|
+
assert result.exit_code == 0
|
|
255
|
+
assert "Setting up Codex authentication" in result.output
|
|
256
|
+
assert "Starting interactive authentication" in result.output
|
|
257
|
+
assert "Codex authentication configured successfully" in result.output
|
|
258
|
+
|
|
259
|
+
# Verify subprocess was called for interactive auth
|
|
260
|
+
mock_subprocess.assert_called_once()
|
|
261
|
+
call_args = mock_subprocess.call_args
|
|
262
|
+
assert 'codex' in call_args[0][0]
|
|
263
|
+
assert 'auth' in call_args[0][0]
|
|
264
|
+
assert '--api-key' not in call_args[0][0]
|
|
265
|
+
|
|
266
|
+
@patch('devs.cli.subprocess.run')
|
|
267
|
+
@patch('devs.cli.config')
|
|
268
|
+
def test_codex_auth_command_not_found(self, mock_config, mock_subprocess):
|
|
269
|
+
"""Test codex --auth when codex CLI is not installed."""
|
|
270
|
+
# Setup mocks
|
|
271
|
+
mock_config.codex_config_dir = '/tmp/test-codex-config'
|
|
272
|
+
mock_config.ensure_directories = Mock()
|
|
273
|
+
mock_subprocess.side_effect = FileNotFoundError()
|
|
274
|
+
|
|
275
|
+
runner = CliRunner()
|
|
276
|
+
result = runner.invoke(cli, ['codex', '--auth'])
|
|
277
|
+
|
|
278
|
+
assert result.exit_code == 1
|
|
279
|
+
assert "Codex CLI not found" in result.output
|
|
280
|
+
assert "npm install -g @openai/codex" in result.output
|
|
281
|
+
|
|
282
|
+
def test_codex_missing_args(self):
|
|
283
|
+
"""Test codex command without required args (not using --auth)."""
|
|
284
|
+
runner = CliRunner()
|
|
285
|
+
result = runner.invoke(cli, ['codex'])
|
|
286
|
+
|
|
287
|
+
assert result.exit_code != 0
|
|
288
|
+
assert "DEV_NAME and PROMPT are required unless using --auth" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|