devstuff 1.14.0__tar.gz → 1.14.2__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 (54) hide show
  1. {devstuff-1.14.0 → devstuff-1.14.2}/CHANGELOG.md +12 -0
  2. {devstuff-1.14.0 → devstuff-1.14.2}/PKG-INFO +1 -1
  3. {devstuff-1.14.0 → devstuff-1.14.2}/pyproject.toml +1 -1
  4. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/help_cmd.py +1 -1
  5. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/install_cmd.py +2 -3
  6. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/skills_cmd.py +29 -5
  7. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/update_cmd.py +2 -3
  8. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/ui.py +20 -5
  9. {devstuff-1.14.0 → devstuff-1.14.2}/uv.lock +1 -1
  10. {devstuff-1.14.0 → devstuff-1.14.2}/.claude/skills/run-stuff/SKILL.md +0 -0
  11. {devstuff-1.14.0 → devstuff-1.14.2}/.claude/skills/run-stuff/smoke.sh +0 -0
  12. {devstuff-1.14.0 → devstuff-1.14.2}/.github/workflows/bump.yml +0 -0
  13. {devstuff-1.14.0 → devstuff-1.14.2}/.github/workflows/ci.yml +0 -0
  14. {devstuff-1.14.0 → devstuff-1.14.2}/.github/workflows/publish.yml +0 -0
  15. {devstuff-1.14.0 → devstuff-1.14.2}/.github/workflows/test-installs.yml +0 -0
  16. {devstuff-1.14.0 → devstuff-1.14.2}/.gitignore +0 -0
  17. {devstuff-1.14.0 → devstuff-1.14.2}/CLAUDE.md +0 -0
  18. {devstuff-1.14.0 → devstuff-1.14.2}/README.md +0 -0
  19. {devstuff-1.14.0 → devstuff-1.14.2}/dev/Dockerfile +0 -0
  20. {devstuff-1.14.0 → devstuff-1.14.2}/dev/Dockerfile.ci +0 -0
  21. {devstuff-1.14.0 → devstuff-1.14.2}/dev/Makefile +0 -0
  22. {devstuff-1.14.0 → devstuff-1.14.2}/dev/docker-compose.yml +0 -0
  23. {devstuff-1.14.0 → devstuff-1.14.2}/dev-setup +0 -0
  24. {devstuff-1.14.0 → devstuff-1.14.2}/install.sh +0 -0
  25. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/__init__.py +0 -0
  26. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/__main__.py +0 -0
  27. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/base.py +0 -0
  28. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/catalog.py +0 -0
  29. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/cli.py +0 -0
  30. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/__init__.py +0 -0
  31. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/add_cmd.py +0 -0
  32. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/catalog_cmd.py +0 -0
  33. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/delete_cmd.py +0 -0
  34. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/docs_cmd.py +0 -0
  35. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/functions_cmd.py +0 -0
  36. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/list_cmd.py +0 -0
  37. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/remove_cmd.py +0 -0
  38. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/commands/run_cmd.py +0 -0
  39. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/function_runner.py +0 -0
  40. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/functions.schema.json +0 -0
  41. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/functions.yaml +0 -0
  42. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/functions_catalog.py +0 -0
  43. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/functions_registry.py +0 -0
  44. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/generic.py +0 -0
  45. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/registry.py +0 -0
  46. {devstuff-1.14.0 → devstuff-1.14.2}/src/dev_setup/tools.yaml +0 -0
  47. {devstuff-1.14.0 → devstuff-1.14.2}/tests/__init__.py +0 -0
  48. {devstuff-1.14.0 → devstuff-1.14.2}/tests/integration/__init__.py +0 -0
  49. {devstuff-1.14.0 → devstuff-1.14.2}/tests/integration/conftest.py +0 -0
  50. {devstuff-1.14.0 → devstuff-1.14.2}/tests/integration/test_tools.py +0 -0
  51. {devstuff-1.14.0 → devstuff-1.14.2}/tests/test_catalog.py +0 -0
  52. {devstuff-1.14.0 → devstuff-1.14.2}/tests/test_functions.py +0 -0
  53. {devstuff-1.14.0 → devstuff-1.14.2}/tests/test_generic.py +0 -0
  54. {devstuff-1.14.0 → devstuff-1.14.2}/tests/test_registry.py +0 -0
@@ -1,3 +1,15 @@
1
+ ## v1.14.2 (2026-07-12)
2
+
3
+ ### Fix
4
+
5
+ - make interactive prompts abortable with Ctrl+C/Ctrl+D
6
+
7
+ ## v1.14.1 (2026-07-12)
8
+
9
+ ### Fix
10
+
11
+ - discover skills by SKILL.md marker, not just a skills/ folder
12
+
1
13
  ## v1.14.0 (2026-07-12)
2
14
 
3
15
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devstuff
3
- Version: 1.14.0
3
+ Version: 1.14.2
4
4
  Summary: Development environment setup CLI for Linux
5
5
  Project-URL: Repository, https://github.com/thesawdawg/dev-setup-py
6
6
  Author-email: Sawyer <sawyerksu@gmail.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devstuff"
3
- version = "1.14.0"
3
+ version = "1.14.2"
4
4
  description = "Development environment setup CLI for Linux"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -19,7 +19,7 @@ def print_help() -> None:
19
19
  ("docs", "<package>", "Open documentation in browser"),
20
20
  ("run", "<function> [args...]", "Run a function/script"),
21
21
  ("functions", "<list|enable|disable|path>", "Manage functions/scripts"),
22
- ("skills", "add", "Append skills from a GitHub repo to claude/codex/pi"),
22
+ ("skills", "add", "Append GitHub skills to claude/codex/pi"),
23
23
  ("version", "", "Show version"),
24
24
  ]
25
25
  for cmd, args, desc in rows:
@@ -102,12 +102,11 @@ def _install_interactive() -> None:
102
102
  disabled=disabled,
103
103
  ))
104
104
 
105
- selected = questionary.checkbox(
105
+ selected = ui.checkbox(
106
106
  "Select packages to install:",
107
107
  choices=choices,
108
108
  instruction="(Space toggle · Enter confirm · installed items are skipped)",
109
- style=ui._STYLE,
110
- ).ask()
109
+ )
111
110
 
112
111
  if not selected:
113
112
  ui.info("No packages selected.")
@@ -112,10 +112,7 @@ def _clone_repo(repo: str, dest: Path) -> str | None:
112
112
  return ssh_url
113
113
 
114
114
  if auth_method == "Personal access token":
115
- token = questionary.password("GitHub personal access token:").ask()
116
- if not token:
117
- ui.warn("No token provided — aborted.")
118
- return None
115
+ token = ui.password("GitHub personal access token:")
119
116
  owner_repo = _owner_repo(repo)
120
117
  token_url = f"https://x-access-token:{token}@github.com/{owner_repo}.git"
121
118
  with ui.spinner("Cloning with token..."):
@@ -168,8 +165,34 @@ def _to_ssh_url(repo: str) -> str:
168
165
  return f"git@github.com:{_owner_repo(repo)}.git"
169
166
 
170
167
 
168
+ def _find_skill_dirs(root: Path) -> list[Path]:
169
+ """Recursively find directories marked as skills by a SKILL.md file.
170
+
171
+ Does not descend into a directory once it's identified as a skill, since
172
+ a skill's own subdirectories (scripts/, references/) aren't skills.
173
+ """
174
+ found: list[Path] = []
175
+
176
+ def _walk(d: Path) -> None:
177
+ if d.name == ".git":
178
+ return
179
+ if (d / "SKILL.md").is_file():
180
+ found.append(d)
181
+ return
182
+ for child in d.iterdir():
183
+ if child.is_dir():
184
+ _walk(child)
185
+
186
+ _walk(root)
187
+ return sorted(found)
188
+
189
+
171
190
  def _discover_skills(repo_dir: Path, repo: str) -> list[Path]:
172
191
  """Return the list of skill directories found in a cloned repo."""
192
+ by_marker = _find_skill_dirs(repo_dir)
193
+ if by_marker:
194
+ return by_marker
195
+
173
196
  skills_root = repo_dir / "skills"
174
197
  if skills_root.is_dir():
175
198
  return sorted(
@@ -177,7 +200,8 @@ def _discover_skills(repo_dir: Path, repo: str) -> list[Path]:
177
200
  if p.is_dir() and not p.name.startswith(".")
178
201
  )
179
202
 
180
- # No skills/ subdir — if the repo root looks like a single skill, use it.
203
+ # No SKILL.md markers or skills/ subdir — if the repo root looks like a
204
+ # single skill, use it.
181
205
  has_content = any(
182
206
  p.name not in (".git",) for p in repo_dir.iterdir()
183
207
  )
@@ -124,12 +124,11 @@ def _update_interactive() -> None:
124
124
  ]
125
125
  choices.append(questionary.Choice(title=title, value=t.key, checked=status.available is True))
126
126
 
127
- selected = questionary.checkbox(
127
+ selected = ui.checkbox(
128
128
  "Select packages to update:",
129
129
  choices=choices,
130
130
  instruction="(Space toggle · Enter confirm · pre-checked items have a known update)",
131
- style=ui._STYLE,
132
- ).ask()
131
+ )
133
132
 
134
133
  if not selected:
135
134
  ui.info("No packages selected.")
@@ -75,14 +75,24 @@ def spinner(label: str) -> Generator[None, None, None]:
75
75
  yield
76
76
 
77
77
 
78
+ def _ask(question) -> object:
79
+ """Run a questionary prompt via unsafe_ask() so Ctrl+C/Ctrl+D raise
80
+ KeyboardInterrupt/EOFError instead of being swallowed into a None return
81
+ (questionary's default .ask() catches KeyboardInterrupt and retries
82
+ silently, which makes required prompts impossible to cancel). Click's
83
+ top-level command dispatch already catches both and exits cleanly with
84
+ "Aborted!", so letting them propagate is enough."""
85
+ return question.unsafe_ask()
86
+
87
+
78
88
  def confirm(prompt: str, default: bool = False) -> bool:
79
- result = questionary.confirm(prompt, default=default, style=_STYLE).ask()
89
+ result = _ask(questionary.confirm(prompt, default=default, style=_STYLE))
80
90
  return bool(result)
81
91
 
82
92
 
83
93
  def text_input(prompt: str, default: str = "", required: bool = False) -> str:
84
94
  while True:
85
- result = questionary.text(prompt, default=default, style=_STYLE).ask()
95
+ result = _ask(questionary.text(prompt, default=default, style=_STYLE))
86
96
  val = (result or "").strip()
87
97
  if val or not required:
88
98
  return val
@@ -90,15 +100,20 @@ def text_input(prompt: str, default: str = "", required: bool = False) -> str:
90
100
 
91
101
 
92
102
  def select(prompt: str, choices: list[str]) -> str:
93
- result = questionary.select(prompt, choices=choices, style=_STYLE).ask()
103
+ result = _ask(questionary.select(prompt, choices=choices, style=_STYLE))
94
104
  return result or ""
95
105
 
96
106
 
97
- def checkbox(prompt: str, choices: list) -> list:
98
- result = questionary.checkbox(prompt, choices=choices, style=_STYLE).ask()
107
+ def checkbox(prompt: str, choices: list, **kwargs) -> list:
108
+ result = _ask(questionary.checkbox(prompt, choices=choices, style=_STYLE, **kwargs))
99
109
  return result or []
100
110
 
101
111
 
112
+ def password(prompt: str) -> str:
113
+ result = _ask(questionary.password(prompt, style=_STYLE))
114
+ return result or ""
115
+
116
+
102
117
  def code_block(code: str, language: str = "bash") -> None:
103
118
  """Print a syntax-highlighted code panel."""
104
119
  from rich.syntax import Syntax
@@ -167,7 +167,7 @@ wheels = [
167
167
 
168
168
  [[package]]
169
169
  name = "devstuff"
170
- version = "1.14.0"
170
+ version = "1.14.2"
171
171
  source = { editable = "." }
172
172
  dependencies = [
173
173
  { name = "click" },
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