devs-cli 0.1.2__tar.gz → 0.1.4__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.
Files changed (30) hide show
  1. {devs_cli-0.1.2/devs_cli.egg-info → devs_cli-0.1.4}/PKG-INFO +3 -3
  2. {devs_cli-0.1.2 → devs_cli-0.1.4}/README.md +2 -2
  3. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/cli.py +128 -122
  4. {devs_cli-0.1.2 → devs_cli-0.1.4/devs_cli.egg-info}/PKG-INFO +3 -3
  5. {devs_cli-0.1.2 → devs_cli-0.1.4}/pyproject.toml +2 -2
  6. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli.py +32 -23
  7. {devs_cli-0.1.2 → devs_cli-0.1.4}/LICENSE +0 -0
  8. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/__init__.py +0 -0
  9. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/config.py +0 -0
  10. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/core/__init__.py +0 -0
  11. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/core/integration.py +0 -0
  12. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/exceptions.py +0 -0
  13. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs/utils/__init__.py +0 -0
  14. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs_cli.egg-info/SOURCES.txt +0 -0
  15. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs_cli.egg-info/dependency_links.txt +0 -0
  16. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs_cli.egg-info/entry_points.txt +0 -0
  17. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs_cli.egg-info/requires.txt +0 -0
  18. {devs_cli-0.1.2 → devs_cli-0.1.4}/devs_cli.egg-info/top_level.txt +0 -0
  19. {devs_cli-0.1.2 → devs_cli-0.1.4}/setup.cfg +0 -0
  20. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli_clean.py +0 -0
  21. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli_misc.py +0 -0
  22. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli_start.py +0 -0
  23. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli_stop.py +0 -0
  24. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_cli_vscode.py +0 -0
  25. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_container_manager.py +0 -0
  26. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_e2e.py +0 -0
  27. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_integration.py +0 -0
  28. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_live_mode.py +0 -0
  29. {devs_cli-0.1.2 → devs_cli-0.1.4}/tests/test_project.py +0 -0
  30. {devs_cli-0.1.2 → devs_cli-0.1.4}/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.2
3
+ Version: 0.1.4
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-auth
84
+ devs claude --auth
85
85
  # Or with API key
86
- devs claude-auth --api-key <YOUR_API_KEY>
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-auth
43
+ devs claude --auth
44
44
  # Or with API key
45
- devs claude-auth --api-key <YOUR_API_KEY>
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,12 +412,103 @@ 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
513
  @click.argument('dev_name')
410
514
  @click.option('--reset-workspace', is_flag=True, help='Reset workspace contents before execution')
@@ -492,104 +596,6 @@ def runtests(dev_name: str, reset_workspace: bool, live: bool, env: tuple, debug
492
596
  sys.exit(1)
493
597
 
494
598
 
495
- @cli.command('claude-auth')
496
- @click.option('--api-key', help='Claude API key to authenticate with')
497
- @debug_option
498
- def claude_auth(api_key: str, debug: bool) -> None:
499
- """Set up Claude authentication for devcontainers.
500
-
501
- This configures Claude authentication that will be shared across
502
- all devcontainers for this project. The authentication is stored
503
- 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
- """
508
-
509
- try:
510
- # Ensure Claude config directory exists
511
- config.ensure_directories()
512
-
513
- console.print("🔐 Setting up Claude authentication...")
514
- console.print(f" Configuration will be saved to: {config.claude_config_dir}")
515
-
516
- if api_key:
517
- # Set API key directly using Claude CLI
518
- console.print(" Using provided API key...")
519
-
520
- # Set CLAUDE_CONFIG_DIR to our config directory and run auth with API key
521
- env = os.environ.copy()
522
- env['CLAUDE_CONFIG_DIR'] = str(config.claude_config_dir)
523
-
524
- cmd = ['claude', 'auth', '--key', api_key]
525
-
526
- if debug:
527
- console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
528
- console.print(f"[dim]CLAUDE_CONFIG_DIR: {config.claude_config_dir}[/dim]")
529
-
530
- result = subprocess.run(
531
- cmd,
532
- env=env,
533
- capture_output=True,
534
- text=True
535
- )
536
-
537
- if result.returncode != 0:
538
- error_msg = result.stderr or result.stdout or "Unknown error"
539
- raise Exception(f"Claude authentication failed: {error_msg}")
540
-
541
- else:
542
- # Interactive authentication
543
- console.print(" Starting interactive authentication...")
544
- console.print(" Follow the prompts to authenticate with Claude")
545
- console.print("")
546
-
547
- # Set CLAUDE_CONFIG_DIR to our config directory
548
- env = os.environ.copy()
549
- env['CLAUDE_CONFIG_DIR'] = str(config.claude_config_dir)
550
-
551
- cmd = ['claude', 'auth']
552
-
553
- if debug:
554
- console.print(f"[dim]Running: {' '.join(cmd)}[/dim]")
555
- console.print(f"[dim]CLAUDE_CONFIG_DIR: {config.claude_config_dir}[/dim]")
556
-
557
- # Run interactively
558
- result = subprocess.run(
559
- cmd,
560
- env=env,
561
- check=False
562
- )
563
-
564
- if result.returncode != 0:
565
- raise Exception("Claude authentication was cancelled or failed")
566
-
567
- console.print("")
568
- console.print("✅ Claude authentication configured successfully!")
569
- console.print(f" Configuration saved to: {config.claude_config_dir}")
570
- console.print(" This authentication will be shared across all devcontainers")
571
- console.print("")
572
- console.print("💡 You can now use Claude in any devcontainer:")
573
- console.print(" devs claude <dev-name> 'Your prompt here'")
574
-
575
- except FileNotFoundError:
576
- console.print("❌ Claude CLI not found on host machine")
577
- console.print("")
578
- console.print("Please install Claude CLI first:")
579
- console.print(" npm install -g @anthropic-ai/claude-cli")
580
- console.print("")
581
- console.print("Note: Claude needs to be installed on the host machine")
582
- console.print(" for authentication. It's already available in containers.")
583
- sys.exit(1)
584
-
585
- except Exception as e:
586
- console.print(f"❌ Failed to configure Claude authentication: {e}")
587
- if debug:
588
- import traceback
589
- console.print(traceback.format_exc())
590
- sys.exit(1)
591
-
592
-
593
599
  @cli.command()
594
600
  @click.option('--all-projects', is_flag=True, help='List containers for all projects')
595
601
  def list(all_projects: bool) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devs-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
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-auth
84
+ devs claude --auth
85
85
  # Or with API key
86
- devs claude-auth --api-key <YOUR_API_KEY>
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.2"
7
+ version = "0.1.4"
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 = "0.1.2"
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 test_claude_auth_command_help(self):
127
- """Test claude-auth command help."""
126
+ def test_claude_command_help(self):
127
+ """Test claude command help."""
128
128
  runner = CliRunner()
129
- result = runner.invoke(cli, ['claude-auth', '--help'])
130
-
129
+ result = runner.invoke(cli, ['claude', '--help'])
130
+
131
131
  assert result.exit_code == 0
132
- assert "Set up Claude authentication" in result.output
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-auth command with API key."""
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-auth', '--api-key', 'test-key-123'])
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,51 @@ 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-auth command in interactive mode."""
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-auth'])
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-auth when claude CLI is not installed."""
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-auth'])
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes