usecli 0.1.35__tar.gz → 0.1.37__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 (70) hide show
  1. {usecli-0.1.35 → usecli-0.1.37}/PKG-INFO +1 -1
  2. {usecli-0.1.35 → usecli-0.1.37}/pyproject.toml +1 -1
  3. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/config/colors.py +146 -8
  4. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/manager.py +167 -11
  5. {usecli-0.1.35 → usecli-0.1.37}/LICENSE +0 -0
  6. {usecli-0.1.35 → usecli-0.1.37}/README.md +0 -0
  7. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/__init__.py +0 -0
  8. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/__init__.py +0 -0
  9. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/README.md +0 -0
  10. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/__init__.py +0 -0
  11. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/custom/README.md +0 -0
  12. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/custom/__init__.py +0 -0
  13. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/__init__.py +0 -0
  14. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/__init__.py +0 -0
  15. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/about_command.py +0 -0
  16. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/help_command.py +0 -0
  17. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/inspire_command.py +0 -0
  18. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/internal/__init__.py +0 -0
  19. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/base/internal/fzf_command.py +0 -0
  20. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/core/__init__.py +0 -0
  21. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/core/utils.py +0 -0
  22. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/__init__.py +0 -0
  23. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/make_command.py +0 -0
  24. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/defaults/make/make_theme_command.py +0 -0
  25. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/commands/init_command.py +0 -0
  26. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/config/__init__.py +0 -0
  27. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/__init__.py +0 -0
  28. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/base_command.py +0 -0
  29. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/__init__.py +0 -0
  30. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/handler.py +0 -0
  31. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/error/utils.py +0 -0
  32. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/__init__.py +0 -0
  33. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/base.py +0 -0
  34. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/config.py +0 -0
  35. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/usage.py +0 -0
  36. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/exceptions/validation.py +0 -0
  37. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/skill_generator.py +0 -0
  38. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/__init__.py +0 -0
  39. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/list.py +0 -0
  40. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/ui/title.py +0 -0
  41. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/__init__.py +0 -0
  42. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/network.py +0 -0
  43. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/numeric.py +0 -0
  44. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/path.py +0 -0
  45. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/core/validators/string.py +0 -0
  46. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/services/__init__.py +0 -0
  47. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/services/command_service.py +0 -0
  48. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/command.py.j2 +0 -0
  49. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/theme.toml.j2 +0 -0
  50. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/templates/usecli.config.toml.j2 +0 -0
  51. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/ayu_dark.toml +0 -0
  52. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_frappe.toml +0 -0
  53. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_latte.toml +0 -0
  54. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_macchiato.toml +0 -0
  55. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/catppuccin_mocha.toml +0 -0
  56. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/default.toml +0 -0
  57. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/dracula.toml +0 -0
  58. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/gruvbox_dark.toml +0 -0
  59. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/nord.toml +0 -0
  60. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/themes/tokyo_night.toml +0 -0
  61. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/__init__.py +0 -0
  62. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/interactive/__init__.py +0 -0
  63. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/cli/utils/interactive/terminal_menu.py +0 -0
  64. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/menu.py +0 -0
  65. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/params.py +0 -0
  66. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/__init__.py +0 -0
  67. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/__init__.py +0 -0
  68. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/shared/config/globals.py +0 -0
  69. {usecli-0.1.35 → usecli-0.1.37}/src/usecli/ui.py +0 -0
  70. {usecli-0.1.35/src/usecli/cli → usecli-0.1.37/src/usecli}/usecli.config.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usecli
3
- Version: 0.1.35
3
+ Version: 0.1.37
4
4
  Summary: A powerful Python CLI framework for building beautiful, developer-friendly command-line tools.
5
5
  Author: Edward Boswell
6
6
  Author-email: Edward Boswell <thememium@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "usecli"
3
- version = "0.1.35"
3
+ version = "0.1.37"
4
4
  description = "A powerful Python CLI framework for building beautiful, developer-friendly command-line tools."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Edward Boswell", email = "thememium@gmail.com" }]
@@ -11,6 +11,9 @@ Usage:
11
11
 
12
12
  from __future__ import annotations
13
13
 
14
+ import importlib.metadata
15
+ import importlib.util
16
+ import os
14
17
  import sys
15
18
  from pathlib import Path
16
19
  from typing import Any, Callable, Final, final
@@ -25,6 +28,15 @@ PYPROJECT_TOML = "pyproject.toml"
25
28
  USECLI_CONFIG_TOML = "usecli.config.toml"
26
29
  DEFAULT_THEME_NAME = "default"
27
30
  THEMES_DIR = Path(__file__).resolve().parent.parent / "themes"
31
+ _SKIP_DIRS = {
32
+ ".venv",
33
+ "venv",
34
+ "site-packages",
35
+ "dist-packages",
36
+ "__pypackages__",
37
+ "pipx",
38
+ "venvs",
39
+ }
28
40
  DEFAULT_THEME_COLORS: dict[str, str] = {
29
41
  "primary": "#60D7FF",
30
42
  "secondary": "#5EFF87",
@@ -48,15 +60,19 @@ DEFAULT_THEME_COLORS: dict[str, str] = {
48
60
  }
49
61
 
50
62
 
51
- def _find_usecli_config_path(root_dir: Path, start_dir: Path) -> Path | None:
63
+ def _find_usecli_config_path(
64
+ root_dir: Path, start_dir: Path, *, skip_venv: bool
65
+ ) -> Path | None:
52
66
  if not root_dir.exists() or not root_dir.is_dir():
53
67
  return None
54
68
 
55
- candidates = [
56
- path
57
- for path in root_dir.rglob(USECLI_CONFIG_TOML)
58
- if not any(part in {".venv", "venv"} for part in path.parts)
59
- ]
69
+ candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
70
+ if skip_venv:
71
+ candidates = [
72
+ path
73
+ for path in candidates
74
+ if not any(part in _SKIP_DIRS for part in path.parts)
75
+ ]
60
76
  if not candidates:
61
77
  return None
62
78
 
@@ -83,6 +99,98 @@ def _find_usecli_config_path(root_dir: Path, start_dir: Path) -> Path | None:
83
99
  return selection[0]
84
100
 
85
101
 
102
+ def _find_usecli_config_in_package() -> Path | None:
103
+ spec = importlib.util.find_spec(_get_package_name())
104
+ if spec is None or not spec.submodule_search_locations:
105
+ return None
106
+ for location in spec.submodule_search_locations:
107
+ package_root = Path(location)
108
+ if not package_root.exists() or not package_root.is_dir():
109
+ continue
110
+ candidates = [
111
+ path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
112
+ ]
113
+ if candidates:
114
+ candidates.sort(key=lambda path: (len(path.parts), str(path)))
115
+ return candidates[0]
116
+ return None
117
+
118
+
119
+ def _find_usecli_config_in_named_package(package_name: str) -> Path | None:
120
+ if not package_name:
121
+ return None
122
+ spec = importlib.util.find_spec(package_name)
123
+ if spec is None or not spec.submodule_search_locations:
124
+ return None
125
+ for location in spec.submodule_search_locations:
126
+ package_root = Path(location)
127
+ if not package_root.exists() or not package_root.is_dir():
128
+ continue
129
+ candidates = [
130
+ path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
131
+ ]
132
+ if candidates:
133
+ candidates.sort(key=lambda path: (len(path.parts), str(path)))
134
+ return candidates[0]
135
+ return None
136
+
137
+
138
+ def _find_usecli_config_for_console_script() -> Path | None:
139
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
140
+ if not command_name:
141
+ return None
142
+ try:
143
+ distributions = importlib.metadata.distributions()
144
+ except Exception:
145
+ return None
146
+ for dist in distributions:
147
+ try:
148
+ entry_points = dist.entry_points
149
+ except Exception:
150
+ continue
151
+ for entry_point in entry_points:
152
+ if entry_point.group != "console_scripts":
153
+ continue
154
+ if entry_point.name != command_name:
155
+ continue
156
+ metadata = dist.metadata
157
+ dist_name = ""
158
+ if "Name" in metadata:
159
+ dist_name = metadata["Name"]
160
+ elif "name" in metadata:
161
+ dist_name = metadata["name"]
162
+ candidates: list[str] = []
163
+ if dist_name:
164
+ candidates.append(dist_name)
165
+ normalized = dist_name.replace("-", "_")
166
+ if normalized not in candidates:
167
+ candidates.append(normalized)
168
+ for package_name in candidates:
169
+ match = _find_usecli_config_in_named_package(package_name)
170
+ if match:
171
+ return match
172
+ return None
173
+
174
+
175
+ def _is_preferred_package_path(path: Path) -> bool:
176
+ return any(part in _SKIP_DIRS for part in path.parts)
177
+
178
+
179
+ def _is_within_usecli_package(start_dir: Path) -> bool:
180
+ spec = importlib.util.find_spec(_get_package_name())
181
+ if spec is None or not spec.submodule_search_locations:
182
+ return False
183
+ start_dir = start_dir.resolve()
184
+ for location in spec.submodule_search_locations:
185
+ package_root = Path(location)
186
+ try:
187
+ start_dir.relative_to(package_root)
188
+ return True
189
+ except ValueError:
190
+ continue
191
+ return False
192
+
193
+
86
194
  def _find_project_root(start_dir: Path | None = None) -> Path | None:
87
195
  if start_dir is None:
88
196
  start_dir = Path.cwd()
@@ -110,10 +218,18 @@ def _find_project_root(start_dir: Path | None = None) -> Path | None:
110
218
  current = parent
111
219
 
112
220
  search_root = git_root or start_dir.resolve()
113
- config_match = _find_usecli_config_path(search_root, start_dir)
221
+ config_match = _find_usecli_config_path(search_root, start_dir, skip_venv=True)
114
222
  if config_match:
115
223
  return config_match.parent
116
224
 
225
+ console_match = _find_usecli_config_for_console_script()
226
+ if console_match:
227
+ return console_match.parent
228
+
229
+ package_match = _find_usecli_config_in_package()
230
+ if package_match:
231
+ return package_match.parent
232
+
117
233
  return git_root
118
234
 
119
235
 
@@ -123,10 +239,25 @@ def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
123
239
 
124
240
  config_path = project_root / USECLI_CONFIG_TOML
125
241
  if not config_path.exists():
126
- config_path = _find_usecli_config_path(project_root, project_root)
242
+ config_path = _find_usecli_config_path(
243
+ project_root,
244
+ project_root,
245
+ skip_venv=True,
246
+ )
247
+ if not config_path or not config_path.exists():
248
+ console_match = _find_usecli_config_for_console_script()
249
+ if console_match:
250
+ return _load_usecli_config_file(console_match)
251
+ package_match = _find_usecli_config_in_package()
252
+ if package_match:
253
+ config_path = package_match
127
254
  if not config_path or not config_path.exists():
128
255
  return {}
129
256
 
257
+ return _load_usecli_config_file(config_path)
258
+
259
+
260
+ def _load_usecli_config_file(config_path: Path) -> dict[str, Any]:
130
261
  try:
131
262
  data = tomllib.loads(config_path.read_text())
132
263
  except (tomllib.TOMLDecodeError, OSError):
@@ -145,6 +276,13 @@ def _load_usecli_config(project_root: Path | None) -> dict[str, Any]:
145
276
  return {}
146
277
 
147
278
 
279
+ def _get_package_name() -> str:
280
+ package = __package__ or __name__
281
+ if not package:
282
+ return "usecli"
283
+ return package.split(".")[0]
284
+
285
+
148
286
  def _normalize_color(value: Any) -> str | None:
149
287
  if not isinstance(value, str):
150
288
  return None
@@ -5,6 +5,9 @@ Handles loading and accessing configuration from project-level files.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import importlib.metadata
9
+ import importlib.util
10
+ import os
8
11
  import sys
9
12
  from pathlib import Path
10
13
  from typing import Any
@@ -58,7 +61,15 @@ def _dedupe_items(items: list[str]) -> list[str]:
58
61
  class ConfigManager:
59
62
  """Manages useCli configuration from project-level files."""
60
63
 
61
- _SKIP_DIRS = {".venv", "venv"}
64
+ _SKIP_DIRS = {
65
+ ".venv",
66
+ "venv",
67
+ "site-packages",
68
+ "dist-packages",
69
+ "__pypackages__",
70
+ "pipx",
71
+ "venvs",
72
+ }
62
73
 
63
74
  DEFAULT_CONFIG: dict[str, Any] = {
64
75
  "title": "usecli",
@@ -107,8 +118,25 @@ class ConfigManager:
107
118
  self.usecli_config_path: Path = usecli_config_path
108
119
  self.start_dir: Path = start_dir
109
120
  detected_root = find_project_root(start_dir)
110
- if detected_root is None and self.usecli_config_path.exists():
111
- detected_root = self.usecli_config_path.parent
121
+ if self.usecli_config_path.exists():
122
+ config_parent = self.usecli_config_path.parent
123
+ if detected_root is None:
124
+ detected_root = config_parent
125
+ else:
126
+ root_config = detected_root / USECLI_CONFIG_TOML
127
+ if self.usecli_config_path.resolve() != root_config.resolve():
128
+ detected_root = config_parent
129
+ else:
130
+ try:
131
+ self.usecli_config_path.relative_to(detected_root)
132
+ except ValueError:
133
+ detected_root = config_parent
134
+ else:
135
+ if any(
136
+ part in self._SKIP_DIRS
137
+ for part in self.usecli_config_path.parts
138
+ ):
139
+ detected_root = config_parent
112
140
  self.project_root: Path = (detected_root or start_dir).resolve()
113
141
  self._config: dict[str, Any] = {}
114
142
  self._overrides: dict[str, Any] = {}
@@ -179,22 +207,39 @@ class ConfigManager:
179
207
  current = parent
180
208
 
181
209
  search_root = find_project_root(start_dir) or start_dir.resolve()
182
- recursive_match = cls._find_usecli_config_in_tree(search_root, start_dir)
210
+ recursive_match = cls._find_usecli_config_in_tree(
211
+ search_root, start_dir, skip_venv=True
212
+ )
183
213
  if recursive_match:
184
214
  return recursive_match
185
215
 
216
+ console_match = cls._find_usecli_config_for_console_script()
217
+ if console_match:
218
+ return console_match
219
+
220
+ if not cls._is_within_usecli_package(start_dir):
221
+ return None
222
+
223
+ package_match = cls._find_usecli_config_in_package()
224
+ if package_match:
225
+ return package_match
226
+
186
227
  return cls._find_usecli_config_on_sys_path()
187
228
 
188
229
  @staticmethod
189
- def _find_usecli_config_in_tree(root_dir: Path, start_dir: Path) -> Path | None:
230
+ def _find_usecli_config_in_tree(
231
+ root_dir: Path, start_dir: Path, *, skip_venv: bool
232
+ ) -> Path | None:
190
233
  if not root_dir.exists() or not root_dir.is_dir():
191
234
  return None
192
235
 
193
- candidates = [
194
- path
195
- for path in root_dir.rglob(USECLI_CONFIG_TOML)
196
- if not any(part in ConfigManager._SKIP_DIRS for part in path.parts)
197
- ]
236
+ candidates = [path for path in root_dir.rglob(USECLI_CONFIG_TOML)]
237
+ if skip_venv:
238
+ candidates = [
239
+ path
240
+ for path in candidates
241
+ if not any(part in ConfigManager._SKIP_DIRS for part in path.parts)
242
+ ]
198
243
  if not candidates:
199
244
  return None
200
245
 
@@ -220,6 +265,98 @@ class ConfigManager:
220
265
  selection.sort(key=_depth_key)
221
266
  return selection[0]
222
267
 
268
+ @staticmethod
269
+ def _find_usecli_config_in_package() -> Path | None:
270
+ spec = importlib.util.find_spec(_get_package_name())
271
+ if spec is None or not spec.submodule_search_locations:
272
+ return None
273
+ for location in spec.submodule_search_locations:
274
+ package_root = Path(location)
275
+ if not package_root.exists() or not package_root.is_dir():
276
+ continue
277
+ candidates = [
278
+ path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
279
+ ]
280
+ if candidates:
281
+ candidates.sort(key=lambda path: (len(path.parts), str(path)))
282
+ return candidates[0]
283
+ return None
284
+
285
+ @classmethod
286
+ def _find_usecli_config_in_named_package(cls, package_name: str) -> Path | None:
287
+ if not package_name:
288
+ return None
289
+ spec = importlib.util.find_spec(package_name)
290
+ if spec is None or not spec.submodule_search_locations:
291
+ return None
292
+ for location in spec.submodule_search_locations:
293
+ package_root = Path(location)
294
+ if not package_root.exists() or not package_root.is_dir():
295
+ continue
296
+ candidates = [
297
+ path for path in package_root.rglob(USECLI_CONFIG_TOML) if path.exists()
298
+ ]
299
+ if candidates:
300
+ candidates.sort(key=lambda path: (len(path.parts), str(path)))
301
+ return candidates[0]
302
+ return None
303
+
304
+ @classmethod
305
+ def _find_usecli_config_for_console_script(cls) -> Path | None:
306
+ command_name = os.path.basename(sys.argv[0]) if sys.argv else ""
307
+ if not command_name:
308
+ return None
309
+ try:
310
+ distributions = importlib.metadata.distributions()
311
+ except Exception:
312
+ return None
313
+ for dist in distributions:
314
+ try:
315
+ entry_points = dist.entry_points
316
+ except Exception:
317
+ continue
318
+ for entry_point in entry_points:
319
+ if entry_point.group != "console_scripts":
320
+ continue
321
+ if entry_point.name != command_name:
322
+ continue
323
+ metadata = dist.metadata
324
+ dist_name = ""
325
+ if "Name" in metadata:
326
+ dist_name = metadata["Name"]
327
+ elif "name" in metadata:
328
+ dist_name = metadata["name"]
329
+ candidates = []
330
+ if dist_name:
331
+ candidates.append(dist_name)
332
+ normalized = dist_name.replace("-", "_")
333
+ if normalized not in candidates:
334
+ candidates.append(normalized)
335
+ for package_name in candidates:
336
+ match = cls._find_usecli_config_in_named_package(package_name)
337
+ if match:
338
+ return match
339
+ return None
340
+
341
+ @staticmethod
342
+ def _is_preferred_package_path(path: Path) -> bool:
343
+ return any(part in ConfigManager._SKIP_DIRS for part in path.parts)
344
+
345
+ @staticmethod
346
+ def _is_within_usecli_package(start_dir: Path) -> bool:
347
+ spec = importlib.util.find_spec(_get_package_name())
348
+ if spec is None or not spec.submodule_search_locations:
349
+ return False
350
+ start_dir = start_dir.resolve()
351
+ for location in spec.submodule_search_locations:
352
+ package_root = Path(location)
353
+ try:
354
+ start_dir.relative_to(package_root)
355
+ return True
356
+ except ValueError:
357
+ continue
358
+ return False
359
+
223
360
  @staticmethod
224
361
  def _find_usecli_config_on_sys_path() -> Path | None:
225
362
  for entry in sys.path:
@@ -328,6 +465,9 @@ class ConfigManager:
328
465
 
329
466
  def reload(self) -> None:
330
467
  """Reload configuration from disk."""
468
+ self.usecli_config_path = self._find_usecli_config(self.start_dir) or (
469
+ self.start_dir / USECLI_CONFIG_TOML
470
+ )
331
471
  self._load_config()
332
472
 
333
473
  @property
@@ -399,8 +539,24 @@ def find_project_root(start_dir: Path | None = None) -> Path | None:
399
539
  current = parent
400
540
 
401
541
  search_root = git_root or start_dir.resolve()
402
- config_match = ConfigManager._find_usecli_config_in_tree(search_root, start_dir)
542
+ config_match = ConfigManager._find_usecli_config_in_tree(
543
+ search_root,
544
+ start_dir,
545
+ skip_venv=True,
546
+ )
403
547
  if config_match:
404
548
  return config_match.parent
405
549
 
550
+ if ConfigManager._is_within_usecli_package(start_dir):
551
+ package_match = ConfigManager._find_usecli_config_in_package()
552
+ if package_match:
553
+ return package_match.parent
554
+
406
555
  return git_root
556
+
557
+
558
+ def _get_package_name() -> str:
559
+ package = __package__ or __name__
560
+ if not package:
561
+ return "usecli"
562
+ return package.split(".")[0]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes