ostruct-cli 0.8.2__tar.gz → 0.8.3__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 (71) hide show
  1. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/PKG-INFO +88 -2
  2. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/README.md +85 -1
  3. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/pyproject.toml +10 -4
  4. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/click_options.py +111 -8
  5. ostruct_cli-0.8.3/src/ostruct/cli/code_interpreter.py +431 -0
  6. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/run.py +56 -0
  7. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/config.py +20 -1
  8. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/errors.py +2 -30
  9. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_info.py +55 -20
  10. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_utils.py +19 -3
  11. ostruct_cli-0.8.3/src/ostruct/cli/json_extract.py +75 -0
  12. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/model_creation.py +1 -1
  13. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/runner.py +461 -180
  14. ostruct_cli-0.8.3/src/ostruct/cli/sentinel.py +29 -0
  15. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_optimizer.py +11 -7
  16. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_processor.py +243 -115
  17. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_rendering.py +41 -1
  18. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_validation.py +41 -3
  19. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/types.py +14 -1
  20. ostruct_cli-0.8.2/src/ostruct/cli/code_interpreter.py +0 -238
  21. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/LICENSE +0 -0
  22. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/__init__.py +0 -0
  23. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/__init__.py +0 -0
  24. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/base_errors.py +0 -0
  25. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cache_manager.py +0 -0
  26. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cli.py +0 -0
  27. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/__init__.py +0 -0
  28. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/list_models.py +0 -0
  29. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/quick_ref.py +0 -0
  30. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/commands/update_registry.py +0 -0
  31. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/cost_estimation.py +0 -0
  32. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/exit_codes.py +0 -0
  33. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/explicit_file_processor.py +0 -0
  34. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/field_utils.py +0 -0
  35. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_list.py +0 -0
  36. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/file_search.py +0 -0
  37. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/mcp_integration.py +0 -0
  38. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/model_validation.py +0 -0
  39. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/path_utils.py +0 -0
  40. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/progress.py +0 -0
  41. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/progress_reporting.py +0 -0
  42. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/registry_updates.py +0 -0
  43. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/schema_utils.py +0 -0
  44. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/schema_validation.py +0 -0
  45. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/__init__.py +0 -0
  46. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/allowed_checker.py +0 -0
  47. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/base.py +0 -0
  48. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/case_manager.py +0 -0
  49. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/errors.py +0 -0
  50. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/normalization.py +0 -0
  51. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/safe_joiner.py +0 -0
  52. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/security_manager.py +0 -0
  53. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/symlink_resolver.py +0 -0
  54. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/types.py +0 -0
  55. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/security/windows_paths.py +0 -0
  56. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/serialization.py +0 -0
  57. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/services.py +0 -0
  58. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_debug.py +0 -0
  59. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_debug_help.py +0 -0
  60. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_env.py +0 -0
  61. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_extensions.py +0 -0
  62. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_filters.py +0 -0
  63. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_io.py +0 -0
  64. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_schema.py +0 -0
  65. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/template_utils.py +0 -0
  66. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/token_utils.py +0 -0
  67. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/token_validation.py +0 -0
  68. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/unattended_operation.py +0 -0
  69. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/utils.py +0 -0
  70. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/cli/validators.py +0 -0
  71. {ostruct_cli-0.8.2 → ostruct_cli-0.8.3}/src/ostruct/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ostruct-cli
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: CLI for OpenAI Structured Output with Multi-Tool Integration
5
5
  Author: Yaniv Golan
6
6
  Author-email: yaniv@golan.name
@@ -22,6 +22,7 @@ Requires-Dist: chardet (>=5.0.0,<6.0)
22
22
  Requires-Dist: click (>=8.1.7,<9.0)
23
23
  Requires-Dist: flake8 (>=6.0,<7.0) ; extra == "dev"
24
24
  Requires-Dist: flake8-pyproject (>=1.2.3,<2.0) ; extra == "dev"
25
+ Requires-Dist: hypothesis (>=6.0.0,<7.0) ; extra == "dev"
25
26
  Requires-Dist: ijson (>=3.2.3,<4.0)
26
27
  Requires-Dist: jinja2 (>=3.1.2,<4.0)
27
28
  Requires-Dist: jsonschema (>=4.23.0,<5.0)
@@ -37,6 +38,7 @@ Requires-Dist: pygments (>=2.15.0,<3.0)
37
38
  Requires-Dist: pytest (>=8.3.4,<9.0) ; extra == "dev"
38
39
  Requires-Dist: pytest-asyncio (>=0.25.2,<1.0) ; extra == "dev"
39
40
  Requires-Dist: pytest-mock (>=3.14.0,<4.0) ; extra == "dev"
41
+ Requires-Dist: pytest-rerunfailures (>=12.0,<13.0) ; extra == "dev"
40
42
  Requires-Dist: python-dotenv (>=1.0.1,<2.0) ; extra == "dev"
41
43
  Requires-Dist: pyyaml (>=6.0.2,<7.0)
42
44
  Requires-Dist: sphinx (>=7.0,<8.0) ; extra == "dev"
@@ -302,7 +304,7 @@ ostruct run analysis.j2 schema.json -fc data.csv
302
304
  ostruct run search.j2 schema.json -fs documentation.pdf
303
305
 
304
306
  # Web Search (real-time information)
305
- ostruct run research.j2 schema.json --web-search -V topic="latest AI developments"
307
+ ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"
306
308
 
307
309
  # Multiple tools with one file
308
310
  ostruct run template.j2 schema.json --file-for code-interpreter shared.json --file-for file-search shared.json
@@ -370,6 +372,7 @@ tools:
370
372
  code_interpreter:
371
373
  auto_download: true
372
374
  output_directory: "./output"
375
+ download_strategy: "two_pass_sentinel" # Enable reliable file downloads
373
376
 
374
377
  mcp:
375
378
  custom_server: "https://my-mcp-server.com"
@@ -384,6 +387,35 @@ Load custom configuration:
384
387
  ostruct --config my-config.yaml run template.j2 schema.json
385
388
  ```
386
389
 
390
+ ### Code Interpreter File Downloads
391
+
392
+ **Important**: If you're using Code Interpreter with structured output (JSON schemas), you may need to enable the two-pass download strategy to ensure files are downloaded reliably.
393
+
394
+ #### Option 1: CLI Flags (Recommended for one-off usage)
395
+
396
+ ```bash
397
+ # Enable reliable file downloads for this run
398
+ ostruct run template.j2 schema.json -fc data.csv --enable-feature ci-download-hack
399
+
400
+ # Force single-pass mode (override config)
401
+ ostruct run template.j2 schema.json -fc data.csv --disable-feature ci-download-hack
402
+ ```
403
+
404
+ #### Option 2: Configuration File (Recommended for persistent settings)
405
+
406
+ ```yaml
407
+ # ostruct.yaml
408
+ tools:
409
+ code_interpreter:
410
+ download_strategy: "two_pass_sentinel" # Enables reliable file downloads
411
+ auto_download: true
412
+ output_directory: "./downloads"
413
+ ```
414
+
415
+ **Why this is needed**: OpenAI's structured output mode can prevent file download annotations from being generated. The two-pass strategy works around this by making two API calls: one to generate files (without structured output), then another to ensure schema compliance. For detailed technical information, see [docs/known-issues/2025-06-responses-ci-file-output.md](docs/known-issues/2025-06-responses-ci-file-output.md).
416
+
417
+ **Performance**: The two-pass strategy approximately doubles token usage but ensures reliable file downloads when using structured output with Code Interpreter.
418
+
387
419
  ## Get Started Quickly
388
420
 
389
421
  🚀 **New to ostruct?** Follow our [step-by-step quickstart guide](https://ostruct.readthedocs.io/en/latest/user-guide/quickstart.html) featuring Juno the beagle for a hands-on introduction.
@@ -628,6 +660,60 @@ The registry file is stored at `~/.openai_structured/config/models.yml` and is a
628
660
 
629
661
  The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
630
662
 
663
+ # Testing
664
+
665
+ ## Running Tests
666
+
667
+ The test suite is divided into two categories:
668
+
669
+ ### Regular Tests (Default)
670
+
671
+ ```bash
672
+ # Run all tests (skips live tests by default)
673
+ pytest
674
+
675
+ # Run specific test file
676
+ pytest tests/test_config.py
677
+
678
+ # Run with verbose output
679
+ pytest -v
680
+ ```
681
+
682
+ ### Live Tests
683
+
684
+ Live tests make real API calls to OpenAI and require a valid API key. They are skipped by default.
685
+
686
+ ```bash
687
+ # Run only live tests (requires OPENAI_API_KEY)
688
+ pytest -m live
689
+
690
+ # Run all tests including live tests
691
+ pytest -m "live or not live"
692
+
693
+ # Run specific live test
694
+ pytest tests/test_responses_annotations.py -m live
695
+ ```
696
+
697
+ **Live tests include:**
698
+
699
+ - Tests that make actual OpenAI API calls
700
+ - Tests that run `ostruct` commands via subprocess
701
+ - Tests that verify real API behavior and file downloads
702
+
703
+ **Requirements for live tests:**
704
+
705
+ - Valid `OPENAI_API_KEY` environment variable
706
+ - Internet connection
707
+ - May incur API costs
708
+
709
+ ## Test Markers
710
+
711
+ - `@pytest.mark.live` - Tests that make real API calls or run actual commands
712
+ - `@pytest.mark.no_fs` - Tests that need real filesystem (not pyfakefs)
713
+ - `@pytest.mark.slow` - Performance/stress tests
714
+ - `@pytest.mark.flaky` - Tests that may need reruns
715
+ - `@pytest.mark.mock_openai` - Tests using mocked OpenAI client
716
+
631
717
  <!--
632
718
  MAINTAINER NOTE: After editing this README, please test GitHub rendering by:
633
719
  1. Creating a draft PR or pushing to a test branch
@@ -241,7 +241,7 @@ ostruct run analysis.j2 schema.json -fc data.csv
241
241
  ostruct run search.j2 schema.json -fs documentation.pdf
242
242
 
243
243
  # Web Search (real-time information)
244
- ostruct run research.j2 schema.json --web-search -V topic="latest AI developments"
244
+ ostruct run research.j2 schema.json --enable-tool web-search -V topic="latest AI developments"
245
245
 
246
246
  # Multiple tools with one file
247
247
  ostruct run template.j2 schema.json --file-for code-interpreter shared.json --file-for file-search shared.json
@@ -309,6 +309,7 @@ tools:
309
309
  code_interpreter:
310
310
  auto_download: true
311
311
  output_directory: "./output"
312
+ download_strategy: "two_pass_sentinel" # Enable reliable file downloads
312
313
 
313
314
  mcp:
314
315
  custom_server: "https://my-mcp-server.com"
@@ -323,6 +324,35 @@ Load custom configuration:
323
324
  ostruct --config my-config.yaml run template.j2 schema.json
324
325
  ```
325
326
 
327
+ ### Code Interpreter File Downloads
328
+
329
+ **Important**: If you're using Code Interpreter with structured output (JSON schemas), you may need to enable the two-pass download strategy to ensure files are downloaded reliably.
330
+
331
+ #### Option 1: CLI Flags (Recommended for one-off usage)
332
+
333
+ ```bash
334
+ # Enable reliable file downloads for this run
335
+ ostruct run template.j2 schema.json -fc data.csv --enable-feature ci-download-hack
336
+
337
+ # Force single-pass mode (override config)
338
+ ostruct run template.j2 schema.json -fc data.csv --disable-feature ci-download-hack
339
+ ```
340
+
341
+ #### Option 2: Configuration File (Recommended for persistent settings)
342
+
343
+ ```yaml
344
+ # ostruct.yaml
345
+ tools:
346
+ code_interpreter:
347
+ download_strategy: "two_pass_sentinel" # Enables reliable file downloads
348
+ auto_download: true
349
+ output_directory: "./downloads"
350
+ ```
351
+
352
+ **Why this is needed**: OpenAI's structured output mode can prevent file download annotations from being generated. The two-pass strategy works around this by making two API calls: one to generate files (without structured output), then another to ensure schema compliance. For detailed technical information, see [docs/known-issues/2025-06-responses-ci-file-output.md](docs/known-issues/2025-06-responses-ci-file-output.md).
353
+
354
+ **Performance**: The two-pass strategy approximately doubles token usage but ensures reliable file downloads when using structured output with Code Interpreter.
355
+
326
356
  ## Get Started Quickly
327
357
 
328
358
  🚀 **New to ostruct?** Follow our [step-by-step quickstart guide](https://ostruct.readthedocs.io/en/latest/user-guide/quickstart.html) featuring Juno the beagle for a hands-on introduction.
@@ -567,6 +597,60 @@ The registry file is stored at `~/.openai_structured/config/models.yml` and is a
567
597
 
568
598
  The update command uses HTTP conditional requests (If-Modified-Since headers) to check if the remote registry has changed before downloading, ensuring efficient updates.
569
599
 
600
+ # Testing
601
+
602
+ ## Running Tests
603
+
604
+ The test suite is divided into two categories:
605
+
606
+ ### Regular Tests (Default)
607
+
608
+ ```bash
609
+ # Run all tests (skips live tests by default)
610
+ pytest
611
+
612
+ # Run specific test file
613
+ pytest tests/test_config.py
614
+
615
+ # Run with verbose output
616
+ pytest -v
617
+ ```
618
+
619
+ ### Live Tests
620
+
621
+ Live tests make real API calls to OpenAI and require a valid API key. They are skipped by default.
622
+
623
+ ```bash
624
+ # Run only live tests (requires OPENAI_API_KEY)
625
+ pytest -m live
626
+
627
+ # Run all tests including live tests
628
+ pytest -m "live or not live"
629
+
630
+ # Run specific live test
631
+ pytest tests/test_responses_annotations.py -m live
632
+ ```
633
+
634
+ **Live tests include:**
635
+
636
+ - Tests that make actual OpenAI API calls
637
+ - Tests that run `ostruct` commands via subprocess
638
+ - Tests that verify real API behavior and file downloads
639
+
640
+ **Requirements for live tests:**
641
+
642
+ - Valid `OPENAI_API_KEY` environment variable
643
+ - Internet connection
644
+ - May incur API costs
645
+
646
+ ## Test Markers
647
+
648
+ - `@pytest.mark.live` - Tests that make real API calls or run actual commands
649
+ - `@pytest.mark.no_fs` - Tests that need real filesystem (not pyfakefs)
650
+ - `@pytest.mark.slow` - Performance/stress tests
651
+ - `@pytest.mark.flaky` - Tests that may need reruns
652
+ - `@pytest.mark.mock_openai` - Tests using mocked OpenAI client
653
+
570
654
  <!--
571
655
  MAINTAINER NOTE: After editing this README, please test GitHub rendering by:
572
656
  1. Creating a draft PR or pushing to a test branch
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "ostruct-cli"
7
- version = "0.8.2"
7
+ version = "0.8.3"
8
8
  description = "CLI for OpenAI Structured Output with Multi-Tool Integration"
9
9
  authors = [{name = "Yaniv Golan", email = "yaniv@golan.name"}]
10
10
  readme = "README.md"
@@ -33,6 +33,7 @@ ostruct = "ostruct.cli.cli:main"
33
33
  [project.optional-dependencies]
34
34
  dev = [
35
35
  "pytest>=8.3.4,<9.0",
36
+ "pytest-rerunfailures>=12.0,<13.0",
36
37
  "flake8>=6.0,<7.0",
37
38
  "flake8-pyproject>=1.2.3,<2.0",
38
39
  "black==24.8.0",
@@ -54,6 +55,7 @@ dev = [
54
55
  "types-requests>=2.32.0.20241016",
55
56
  "pre-commit>=4.1.0,<5.0",
56
57
  "psutil>=7.0.0,<8.0",
58
+ "hypothesis>=6.0.0,<7.0",
57
59
  ]
58
60
  docs = [
59
61
  "sphinx>=7.0,<8.0",
@@ -111,12 +113,16 @@ include = ["py.typed"]
111
113
  testpaths = ["tests"]
112
114
  python_files = ["test_*.py"]
113
115
  markers = [
114
- "live: mark test as a live test that should use real API key",
116
+ "live: mark test as a live test that makes real API calls or runs actual ostruct commands",
115
117
  "asyncio: mark test as requiring async loop",
116
- "no_pyfakefs: mark test to disable pyfakefs",
117
- "slow: mark test as slow performance/stress test"
118
+ "no_fs: mark test to disable pyfakefs and use real filesystem",
119
+ "slow: mark test as slow performance/stress test",
120
+ "flaky: mark test as flaky (may need reruns)",
121
+ "mock_openai: mark test to use mock OpenAI client"
118
122
  ]
119
123
  asyncio_default_fixture_loop_scope = "function"
124
+ # By default, skip live tests unless explicitly requested
125
+ addopts = "-m 'not live'"
120
126
 
121
127
  [tool.ruff]
122
128
  target-version = "py310"
@@ -29,6 +29,56 @@ CommandDecorator = Callable[[F], Command]
29
29
  DecoratedCommand = Union[Command, Callable[..., Any]]
30
30
 
31
31
 
32
+ def parse_feature_flags(
33
+ enabled_features: tuple[str, ...], disabled_features: tuple[str, ...]
34
+ ) -> dict[str, str]:
35
+ """Parse feature flags from CLI arguments.
36
+
37
+ Args:
38
+ enabled_features: Tuple of feature names to enable
39
+ disabled_features: Tuple of feature names to disable
40
+
41
+ Returns:
42
+ Dictionary mapping feature names to "on" or "off"
43
+
44
+ Raises:
45
+ click.BadParameter: If flag format is invalid or conflicts exist
46
+ """
47
+ parsed = {}
48
+
49
+ # Process enabled features
50
+ for feature in enabled_features:
51
+ feature = feature.strip()
52
+ if not feature:
53
+ raise click.BadParameter("Feature name cannot be empty")
54
+
55
+ # Validate known feature flags
56
+ if feature == "ci-download-hack":
57
+ parsed[feature] = "on"
58
+ else:
59
+ raise click.BadParameter(f"Unknown feature: {feature}")
60
+
61
+ # Process disabled features
62
+ for feature in disabled_features:
63
+ feature = feature.strip()
64
+ if not feature:
65
+ raise click.BadParameter("Feature name cannot be empty")
66
+
67
+ # Check for conflicts
68
+ if feature in parsed:
69
+ raise click.BadParameter(
70
+ f"Feature '{feature}' cannot be both enabled and disabled"
71
+ )
72
+
73
+ # Validate known feature flags
74
+ if feature == "ci-download-hack":
75
+ parsed[feature] = "off"
76
+ else:
77
+ raise click.BadParameter(f"Unknown feature: {feature}")
78
+
79
+ return parsed
80
+
81
+
32
82
  def debug_options(f: Union[Command, Callable[..., Any]]) -> Command:
33
83
  """Add debug-related CLI options."""
34
84
  # Initial conversion to Command if needed
@@ -573,6 +623,31 @@ def code_interpreter_options(f: Union[Command, Callable[..., Any]]) -> Command:
573
623
  help="""Clean up uploaded files after execution to save storage quota.""",
574
624
  )(cmd)
575
625
 
626
+ # Feature flags for experimental features
627
+ cmd = click.option(
628
+ "--enable-feature",
629
+ "enabled_features",
630
+ multiple=True,
631
+ metavar="<FEATURE>",
632
+ help="""🔧 [EXPERIMENTAL] Enable experimental features.
633
+ Available features:
634
+ • ci-download-hack - Enable two-pass sentinel mode for reliable Code Interpreter
635
+ file downloads with structured output. Overrides config file setting.
636
+ Example: --enable-feature ci-download-hack""",
637
+ )(cmd)
638
+
639
+ cmd = click.option(
640
+ "--disable-feature",
641
+ "disabled_features",
642
+ multiple=True,
643
+ metavar="<FEATURE>",
644
+ help="""🔧 [EXPERIMENTAL] Disable experimental features.
645
+ Available features:
646
+ • ci-download-hack - Force single-pass mode for Code Interpreter downloads.
647
+ Overrides config file setting.
648
+ Example: --disable-feature ci-download-hack""",
649
+ )(cmd)
650
+
576
651
  return cast(Command, cmd)
577
652
 
578
653
 
@@ -685,13 +760,17 @@ def web_search_options(f: Union[Command, Callable[..., Any]]) -> Command:
685
760
  is_flag=True,
686
761
  help="""🌐 [WEB SEARCH] Enable OpenAI web search tool for up-to-date information.
687
762
  Allows the model to search the web for current events, recent updates, and real-time data.
688
- Note: Search queries may be sent to external services via OpenAI.""",
763
+ Note: Search queries may be sent to external services via OpenAI.
764
+
765
+ ⚠️ DEPRECATED: Use --enable-tool web-search instead. Will be removed in v0.9.0.""",
689
766
  )(cmd)
690
767
 
691
768
  cmd = click.option(
692
769
  "--no-web-search",
693
770
  is_flag=True,
694
- help="""Explicitly disable web search even if enabled by default in configuration.""",
771
+ help="""Explicitly disable web search even if enabled by default in configuration.
772
+
773
+ ⚠️ DEPRECATED: Use --disable-tool web-search instead. Will be removed in v0.9.0.""",
695
774
  )(cmd)
696
775
 
697
776
  cmd = click.option(
@@ -725,6 +804,35 @@ def web_search_options(f: Union[Command, Callable[..., Any]]) -> Command:
725
804
  return cast(Command, cmd)
726
805
 
727
806
 
807
+ def tool_toggle_options(f: Union[Command, Callable[..., Any]]) -> Command:
808
+ """Add universal tool toggle CLI options."""
809
+ cmd: Any = f if isinstance(f, Command) else f
810
+
811
+ cmd = click.option(
812
+ "--enable-tool",
813
+ "enabled_tools",
814
+ multiple=True,
815
+ metavar="<TOOL>",
816
+ help="""🔧 [TOOL TOGGLES] Enable a tool for this run (repeatable).
817
+ Overrides configuration file and implicit activation.
818
+ Available tools: code-interpreter, file-search, web-search, mcp
819
+ Example: --enable-tool code-interpreter --enable-tool web-search""",
820
+ )(cmd)
821
+
822
+ cmd = click.option(
823
+ "--disable-tool",
824
+ "disabled_tools",
825
+ multiple=True,
826
+ metavar="<TOOL>",
827
+ help="""🔧 [TOOL TOGGLES] Disable a tool for this run (repeatable).
828
+ Overrides configuration file and implicit activation.
829
+ Available tools: code-interpreter, file-search, web-search, mcp
830
+ Example: --disable-tool web-search --disable-tool mcp""",
831
+ )(cmd)
832
+
833
+ return cast(Command, cmd)
834
+
835
+
728
836
  def debug_progress_options(f: Union[Command, Callable[..., Any]]) -> Command:
729
837
  """Add debugging and progress CLI options."""
730
838
  cmd: Any = f if isinstance(f, Command) else f
@@ -746,12 +854,6 @@ def debug_progress_options(f: Union[Command, Callable[..., Any]]) -> Command:
746
854
  "--verbose", is_flag=True, help="Enable verbose logging"
747
855
  )(cmd)
748
856
 
749
- cmd = click.option(
750
- "--debug-openai-stream",
751
- is_flag=True,
752
- help="Debug OpenAI streaming process",
753
- )(cmd)
754
-
755
857
  cmd = click.option(
756
858
  "--timeout",
757
859
  type=int,
@@ -777,6 +879,7 @@ def all_options(f: Union[Command, Callable[..., Any]]) -> Command:
777
879
  cmd = code_interpreter_options(cmd)
778
880
  cmd = file_search_options(cmd)
779
881
  cmd = web_search_options(cmd)
882
+ cmd = tool_toggle_options(cmd)
780
883
  cmd = debug_options(cmd)
781
884
  cmd = debug_progress_options(cmd)
782
885