llm-tool-cli 0.2.0__py3-none-any.whl

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 (111) hide show
  1. llm_tool_cli/__init__.py +0 -0
  2. llm_tool_cli/cli/__init__.py +1 -0
  3. llm_tool_cli/cli/application.py +31 -0
  4. llm_tool_cli/cli/commands/__init__.py +0 -0
  5. llm_tool_cli/cli/commands/initialization.py +36 -0
  6. llm_tool_cli/cli/commands/skills.py +25 -0
  7. llm_tool_cli/cli/commands/tests/__init__.py +0 -0
  8. llm_tool_cli/cli/commands/tests/test_initialization.py +130 -0
  9. llm_tool_cli/cli/commands/tests/test_skills.py +154 -0
  10. llm_tool_cli/cli/commands/tests/test_version.py +98 -0
  11. llm_tool_cli/cli/commands/version.py +17 -0
  12. llm_tool_cli/cli/context.py +39 -0
  13. llm_tool_cli/cli/entities.py +3 -0
  14. llm_tool_cli/cli/errors.py +15 -0
  15. llm_tool_cli/cli/handling.py +41 -0
  16. llm_tool_cli/cli/options.py +48 -0
  17. llm_tool_cli/cli/tests/__init__.py +0 -0
  18. llm_tool_cli/cli/tests/test_application.py +167 -0
  19. llm_tool_cli/cli/tests/test_context.py +179 -0
  20. llm_tool_cli/cli/tests/test_handling.py +230 -0
  21. llm_tool_cli/cli/tests/test_options.py +125 -0
  22. llm_tool_cli/config/__init__.py +33 -0
  23. llm_tool_cli/config/errors.py +92 -0
  24. llm_tool_cli/config/fields.py +39 -0
  25. llm_tool_cli/config/files.py +175 -0
  26. llm_tool_cli/config/project.py +87 -0
  27. llm_tool_cli/config/runtime.py +14 -0
  28. llm_tool_cli/config/tests/__init__.py +0 -0
  29. llm_tool_cli/config/tests/fixtures.py +34 -0
  30. llm_tool_cli/config/tests/test_fields.py +124 -0
  31. llm_tool_cli/config/tests/test_files.py +826 -0
  32. llm_tool_cli/config/tests/test_project.py +257 -0
  33. llm_tool_cli/core/__init__.py +1 -0
  34. llm_tool_cli/core/context.py +50 -0
  35. llm_tool_cli/core/entities.py +35 -0
  36. llm_tool_cli/core/errors.py +97 -0
  37. llm_tool_cli/core/result.py +118 -0
  38. llm_tool_cli/core/settings.py +37 -0
  39. llm_tool_cli/core/tests/__init__.py +0 -0
  40. llm_tool_cli/core/tests/fixtures.py +13 -0
  41. llm_tool_cli/core/tests/test_context.py +152 -0
  42. llm_tool_cli/core/tests/test_entities.py +65 -0
  43. llm_tool_cli/core/tests/test_errors.py +211 -0
  44. llm_tool_cli/core/tests/test_result.py +256 -0
  45. llm_tool_cli/core/tests/test_settings.py +64 -0
  46. llm_tool_cli/output/__init__.py +3 -0
  47. llm_tool_cli/output/emitter.py +108 -0
  48. llm_tool_cli/output/errors.py +21 -0
  49. llm_tool_cli/output/journal.py +66 -0
  50. llm_tool_cli/output/tests/__init__.py +0 -0
  51. llm_tool_cli/output/tests/helpers.py +29 -0
  52. llm_tool_cli/output/tests/test_emitter.py +406 -0
  53. llm_tool_cli/output/tests/test_journal.py +141 -0
  54. llm_tool_cli/paths/__init__.py +51 -0
  55. llm_tool_cli/paths/enumeration.py +72 -0
  56. llm_tool_cli/paths/errors.py +42 -0
  57. llm_tool_cli/paths/filesystem.py +164 -0
  58. llm_tool_cli/paths/globs.py +124 -0
  59. llm_tool_cli/paths/normalization.py +58 -0
  60. llm_tool_cli/paths/tests/__init__.py +0 -0
  61. llm_tool_cli/paths/tests/test_enumeration.py +229 -0
  62. llm_tool_cli/paths/tests/test_filesystem.py +778 -0
  63. llm_tool_cli/paths/tests/test_globs.py +137 -0
  64. llm_tool_cli/paths/tests/test_normalization.py +163 -0
  65. llm_tool_cli/protocol/__init__.py +4 -0
  66. llm_tool_cli/protocol/cell_shortcuts.py +39 -0
  67. llm_tool_cli/protocol/entities.py +7 -0
  68. llm_tool_cli/protocol/errors.py +21 -0
  69. llm_tool_cli/protocol/journal.py +21 -0
  70. llm_tool_cli/protocol/logic_cells/__init__.py +4 -0
  71. llm_tool_cli/protocol/logic_cells/base.py +41 -0
  72. llm_tool_cli/protocol/logic_cells/content.py +26 -0
  73. llm_tool_cli/protocol/logic_cells/environment_error.py +36 -0
  74. llm_tool_cli/protocol/logic_cells/journal.py +44 -0
  75. llm_tool_cli/protocol/logic_cells/tests/__init__.py +0 -0
  76. llm_tool_cli/protocol/logic_cells/tests/test_base.py +47 -0
  77. llm_tool_cli/protocol/logic_cells/tests/test_content.py +59 -0
  78. llm_tool_cli/protocol/logic_cells/tests/test_environment_error.py +130 -0
  79. llm_tool_cli/protocol/logic_cells/tests/test_journal.py +47 -0
  80. llm_tool_cli/protocol/logic_cells/tests/test_uniform.py +60 -0
  81. llm_tool_cli/protocol/logic_cells/tests/test_warning.py +34 -0
  82. llm_tool_cli/protocol/logic_cells/uniform.py +21 -0
  83. llm_tool_cli/protocol/logic_cells/warning.py +22 -0
  84. llm_tool_cli/protocol/output_cells/__init__.py +11 -0
  85. llm_tool_cli/protocol/output_cells/automation.py +14 -0
  86. llm_tool_cli/protocol/output_cells/base.py +65 -0
  87. llm_tool_cli/protocol/output_cells/human.py +24 -0
  88. llm_tool_cli/protocol/output_cells/journal.py +23 -0
  89. llm_tool_cli/protocol/output_cells/llm.py +25 -0
  90. llm_tool_cli/protocol/output_cells/tests/__init__.py +0 -0
  91. llm_tool_cli/protocol/output_cells/tests/make.py +15 -0
  92. llm_tool_cli/protocol/output_cells/tests/test_automation.py +58 -0
  93. llm_tool_cli/protocol/output_cells/tests/test_base.py +86 -0
  94. llm_tool_cli/protocol/output_cells/tests/test_human.py +112 -0
  95. llm_tool_cli/protocol/output_cells/tests/test_llm.py +65 -0
  96. llm_tool_cli/protocol/serialization.py +7 -0
  97. llm_tool_cli/protocol/tests/__init__.py +0 -0
  98. llm_tool_cli/protocol/tests/helpers.py +25 -0
  99. llm_tool_cli/protocol/tests/test_cell_shortcuts.py +126 -0
  100. llm_tool_cli/protocol/tests/test_journal.py +44 -0
  101. llm_tool_cli/protocol/tests/test_serialization.py +33 -0
  102. llm_tool_cli/py.typed +0 -0
  103. llm_tool_cli/skills/__init__.py +6 -0
  104. llm_tool_cli/skills/errors.py +12 -0
  105. llm_tool_cli/skills/fixtures.py +14 -0
  106. llm_tool_cli/skills/tests/__init__.py +0 -0
  107. llm_tool_cli/skills/tests/test_fixtures.py +58 -0
  108. llm_tool_cli-0.2.0.dist-info/METADATA +275 -0
  109. llm_tool_cli-0.2.0.dist-info/RECORD +111 -0
  110. llm_tool_cli-0.2.0.dist-info/WHEEL +4 -0
  111. llm_tool_cli-0.2.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,124 @@
1
+ from pathlib import Path
2
+ from typing import Annotated, Literal
3
+
4
+ import pydantic
5
+ import pytest
6
+
7
+ from llm_tool_cli.config import errors, load_config
8
+ from llm_tool_cli.config.fields import ConfigVersion, SessionDir, validate_relative_project_path
9
+ from llm_tool_cli.core.entities import BaseEntity
10
+ from llm_tool_cli.paths import RelativeProjectPath
11
+
12
+
13
+ class ExampleConfig(BaseEntity):
14
+ version: ConfigVersion[Literal[1]] = 1
15
+ session_dir: SessionDir = RelativeProjectPath(Path(".session/example"))
16
+
17
+
18
+ class TestValidateConfigVersion:
19
+ def test_accepts_supported_integer(self) -> None:
20
+ assert pydantic.TypeAdapter(ConfigVersion[Literal[1]]).validate_python(1) == 1
21
+
22
+ def test_uses_application_version_constraint(self) -> None:
23
+ adapter: pydantic.TypeAdapter[int] = pydantic.TypeAdapter(ConfigVersion[Literal[2]])
24
+
25
+ assert adapter.validate_python(2) == 2
26
+ with pytest.raises(pydantic.ValidationError) as failure:
27
+ adapter.validate_python(1)
28
+
29
+ assert "Unsupported configuration version 1" in failure.value.errors()[0]["msg"]
30
+ assert "supported version: 2" in failure.value.errors()[0]["msg"]
31
+
32
+ def test_preserves_other_schema_failures(self) -> None:
33
+ adapter: pydantic.TypeAdapter[int] = pydantic.TypeAdapter(ConfigVersion[Annotated[int, pydantic.Field(gt=0)]])
34
+
35
+ with pytest.raises(pydantic.ValidationError) as failure:
36
+ adapter.validate_python(0)
37
+
38
+ assert failure.value.errors()[0]["type"] == "greater_than"
39
+
40
+ @pytest.mark.parametrize("value", [True, False, 1.0, "1", "", None, []])
41
+ def test_rejects_wrong_types_before_conversion(self, value: object) -> None:
42
+ with pytest.raises(pydantic.ValidationError) as failure:
43
+ pydantic.TypeAdapter(ConfigVersion[Literal[1]]).validate_python(value)
44
+
45
+ detail = failure.value.errors()[0]
46
+ assert detail["type"] == "value_error"
47
+ assert "integer" in detail["msg"]
48
+ assert repr(value) in detail["msg"]
49
+
50
+ @pytest.mark.parametrize("value", [-1, 0, 2])
51
+ def test_reports_unsupported_version_and_supported_version(self, value: int) -> None:
52
+ with pytest.raises(pydantic.ValidationError) as failure:
53
+ pydantic.TypeAdapter(ConfigVersion[Literal[1]]).validate_python(value)
54
+
55
+ detail = failure.value.errors()[0]
56
+ assert detail["type"] == "value_error"
57
+ assert "Unsupported configuration version" in detail["msg"]
58
+ assert str(value) in detail["msg"]
59
+ assert "supported version: 1" in detail["msg"]
60
+
61
+
62
+ class TestValidateRelativeProjectPath:
63
+ @pytest.mark.parametrize("value", [".", "workflows", ".session/example", "~/literal"])
64
+ def test_accepts_relative_paths_without_resolution(self, value: str) -> None:
65
+ path = RelativeProjectPath(Path(value))
66
+
67
+ assert validate_relative_project_path(path) == path
68
+
69
+ @pytest.mark.parametrize("value", ["/outside", "..", "../outside", "nested/../outside"])
70
+ def test_rejects_absolute_paths_and_parent_references(self, value: str) -> None:
71
+ with pytest.raises(ValueError):
72
+ validate_relative_project_path(RelativeProjectPath(Path(value)))
73
+
74
+
75
+ class TestConfigurationFields:
76
+ @pytest.mark.parametrize("text", ["", "version = 2\n"])
77
+ def test_load_config__tool_can_select_another_version_and_default(self, tmp_path: Path, text: str) -> None:
78
+ class VersionTwoConfig(BaseEntity):
79
+ version: ConfigVersion[Literal[2]] = 2
80
+
81
+ path = tmp_path / "config.toml"
82
+ path.write_text(text, encoding="utf-8")
83
+
84
+ config = load_config(path, VersionTwoConfig).unwrap()
85
+
86
+ assert config.version == 2
87
+ assert VersionTwoConfig.from_json(config.to_json()) == config
88
+
89
+ @pytest.mark.parametrize("version_line", ["", "version = 1\n"])
90
+ def test_load_config__relative_directory_round_trip_without_creating_storage(
91
+ self, tmp_path: Path, version_line: str
92
+ ) -> None:
93
+ path = tmp_path / "config.toml"
94
+ path.write_text(version_line + 'session_dir = "./custom/./state"\n', encoding="utf-8")
95
+
96
+ config = load_config(path, ExampleConfig).unwrap()
97
+
98
+ assert config.model_dump(mode="json") == {"version": 1, "session_dir": "custom/state"}
99
+ assert ExampleConfig.from_json(config.to_json()) == config
100
+ assert not (tmp_path / "custom").exists()
101
+
102
+ @pytest.mark.parametrize(
103
+ ("text", "field"),
104
+ [
105
+ ("version = 2", "version"),
106
+ ('version = "1"', "version"),
107
+ ("version = true", "version"),
108
+ ("version = 1.0", "version"),
109
+ ('session_dir = "/outside"', "session_dir"),
110
+ ('session_dir = "nested/../outside"', "session_dir"),
111
+ ],
112
+ )
113
+ def test_load_config__invalid_fields_use_shared_diagnostic(self, tmp_path: Path, text: str, field: str) -> None:
114
+ path = tmp_path / "config.toml"
115
+ path.write_text(text, encoding="utf-8")
116
+
117
+ failure = load_config(path, ExampleConfig).unwrap_err()[0]
118
+
119
+ assert isinstance(failure, errors.ValidationFailed)
120
+ assert failure.path == path
121
+ assert isinstance(failure.cause, pydantic.ValidationError)
122
+ detail = failure.cause.errors()[0]
123
+ assert detail["loc"] == (field,)
124
+ assert detail["msg"] in failure.reason