skit-cli 0.0.1__tar.gz → 0.2.0__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.
- skit_cli-0.0.1/README.md → skit_cli-0.2.0/PKG-INFO +63 -1
- skit_cli-0.0.1/PKG-INFO → skit_cli-0.2.0/README.md +33 -31
- {skit_cli-0.0.1 → skit_cli-0.2.0}/pyproject.toml +20 -5
- skit_cli-0.2.0/src/skit/__init__.py +20 -0
- skit_cli-0.2.0/src/skit/agentskill.py +99 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/atomic.py +25 -1
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/cli.py +410 -29
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/config.py +22 -2
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/flows.py +12 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/inlineform.py +6 -1
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/locales/skit.pot +521 -347
- skit_cli-0.2.0/src/skit/locales/zh_CN/LC_MESSAGES/skit.mo +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/locales/zh_CN/LC_MESSAGES/skit.po +787 -420
- skit_cli-0.2.0/src/skit/locales/zh_TW/LC_MESSAGES/skit.mo +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/locales/zh_TW/LC_MESSAGES/skit.po +787 -420
- skit_cli-0.2.0/src/skit/skills/skit/SKILL.md +143 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui.py +195 -30
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui_add.py +130 -25
- skit_cli-0.2.0/src/skit/tui_footer.py +129 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui_form.py +53 -16
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui_health.py +12 -9
- skit_cli-0.2.0/src/skit/tui_layout.py +50 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui_prefs.py +46 -13
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tui_settings.py +18 -11
- skit_cli-0.0.1/src/skit/__init__.py +0 -3
- skit_cli-0.0.1/src/skit/locales/zh_CN/LC_MESSAGES/skit.mo +0 -0
- skit_cli-0.0.1/src/skit/locales/zh_TW/LC_MESSAGES/skit.mo +0 -0
- skit_cli-0.0.1/src/skit/tui_footer.py +0 -38
- {skit_cli-0.0.1 → skit_cli-0.2.0}/LICENSE +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/analyzer.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/argspec.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/argstate.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/editor.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/i18n.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/launcher.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/metawriter.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/models.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/paths.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/pep723.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/promptform.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/reconcile.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/shim.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/store.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/theme.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/tokens.py +0 -0
- {skit_cli-0.0.1 → skit_cli-0.2.0}/src/skit/uvman.py +0 -0
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: skit-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Script launcher and parameter manager: all your scripts in one menu, their flags, input() calls, and constants auto-extracted into a run form — no more opening an IDE just to tweak a value.
|
|
5
|
+
Keywords: script,launcher,uv,tui,pep723,script-manager,script-runner,automation,ai-agent,agent-skills
|
|
6
|
+
Author: Yi-Ting Chiu
|
|
7
|
+
Author-email: Yi-Ting Chiu <mytim710@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Environment :: Console :: Curses
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Dist: typer>=0.26,<1
|
|
22
|
+
Requires-Dist: platformdirs>=4.10,<5
|
|
23
|
+
Requires-Dist: tomli-w>=1.2,<2
|
|
24
|
+
Requires-Dist: textual>=8.2,<9
|
|
25
|
+
Requires-Dist: rich>=15,<16
|
|
26
|
+
Requires-Python: >=3.12
|
|
27
|
+
Project-URL: Repository, https://github.com/t41372/skit
|
|
28
|
+
Project-URL: Issues, https://github.com/t41372/skit/issues
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
1
31
|

|
|
2
32
|
|
|
3
33
|
[](https://github.com/t41372/skit/actions/workflows/ci.yml)
|
|
@@ -17,7 +47,11 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
17
47
|
|
|
18
48
|
**AI writes the scripts. skit gives them a home.**
|
|
19
49
|
|
|
20
|
-
|
|
50
|
+
And it's a home you share with your agent: the library you drive from a menu, AI agents
|
|
51
|
+
drive through a deterministic CLI — checking it before writing yet another one-off
|
|
52
|
+
script, and (with your OK) saving the good ones back, so they outlive the chat.
|
|
53
|
+
|
|
54
|
+
<video src="https://github.com/user-attachments/assets/d27121fe-5855-4270-91b0-b0ee9e5d11ee" controls></video>
|
|
21
55
|
|
|
22
56
|
## What it does
|
|
23
57
|
|
|
@@ -27,6 +61,7 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
27
61
|
- **No environment mess.** skit declares each script's dependencies in the script itself (PEP 723) and runs it through uv in an isolated, cached environment — no venvs to manage, nothing installed globally.
|
|
28
62
|
- **Mouse or keyboard.** Plain `skit` opens the full TUI; every key hint on screen is also a clickable button.
|
|
29
63
|
- **Automation-ready.** Every TUI action is also a CLI command with `--json` output and meaningful exit codes — for shell scripts, CI, and AI agents.
|
|
64
|
+
- **Your agent's script library too.** The official [Agent Skill](https://agentskills.io) teaches Claude Code, Codex, Cursor, Gemini CLI, and friends the whole drill: discover scripts with `skit list`, read a parameter schema with `skit show`, run with `skit run --set … --no-input`. One `skit agent install` away — see [Works with your AI agent](#works-with-your-ai-agent).
|
|
30
65
|
- **Speaks your language.** English, 繁體中文, and 简体中文, with more to come. See [Languages](#languages).
|
|
31
66
|
|
|
32
67
|
| Problem | What skit does |
|
|
@@ -34,6 +69,7 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
34
69
|
| Scripts scattered all over the place | One central menu, with search |
|
|
35
70
|
| Scripts with weird external dependencies | An isolated environment per script — dependencies declared in the file (PEP 723), resolved by uv |
|
|
36
71
|
| CLI flags you forget ten minutes later, `input()` prompts, hard-coded constants meant to be edited by hand | Static analysis extracts them all into an interactive form — no code changes. Last-used values come prefilled; favorites save as presets. |
|
|
72
|
+
| The weird script an AI wrote for you dies with the chat session | Agents check the library first, reuse what's there, and save the keepers — one-off scripts become permanent, parameterized tools |
|
|
37
73
|
|
|
38
74
|
Nothing to set up per script — no refactoring, no config to maintain. The script an AI wrote last week and the one you barely remember from last year launch the same way.
|
|
39
75
|
|
|
@@ -72,6 +108,15 @@ uv tool install git+https://github.com/t41372/skit # latest development
|
|
|
72
108
|
uvx --from git+https://github.com/t41372/skit skit --help # try it without installing
|
|
73
109
|
```
|
|
74
110
|
|
|
111
|
+
## Update
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv tool upgrade skit-cli # update to the latest release — also how you "check": it says up to date if you are
|
|
115
|
+
skit --version # the version you're on
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`uv tool upgrade` follows whatever source you installed from: PyPI installs track PyPI releases, `git+…` installs re-fetch the main branch.
|
|
119
|
+
|
|
75
120
|
## Uninstall
|
|
76
121
|
|
|
77
122
|
```bash
|
|
@@ -119,12 +164,29 @@ The rest of the CLI exists for automation and AI agents — every TUI action, sc
|
|
|
119
164
|
```bash
|
|
120
165
|
skit run my_script -p fast # run with a saved preset
|
|
121
166
|
skit run my_script --dry-run # print the exact command, don't run it
|
|
167
|
+
skit run my_script --set width=800 --no-input # set values explicitly, never prompt
|
|
168
|
+
skit show my_script --json # one script's full parameter schema, machine-readable
|
|
122
169
|
skit params my_script # show managed parameters and last-used values
|
|
123
170
|
skit list --json # machine-readable listing
|
|
124
171
|
skit config # settings: language, editor, mirror, form style
|
|
125
172
|
skit --help # everything else
|
|
126
173
|
```
|
|
127
174
|
|
|
175
|
+
## Works with your AI agent
|
|
176
|
+
|
|
177
|
+
skit is a script repository for humans *and* AI agents: one library — you get the
|
|
178
|
+
forms, agents get a deterministic CLI. The official [Agent Skill](https://agentskills.io)
|
|
179
|
+
teaches compatible agents (Claude Code, Codex, Cursor, Gemini CLI, and many more) to
|
|
180
|
+
check your library before writing yet another one-off script, to inspect and run what's
|
|
181
|
+
already there, and to offer to save the useful scripts they write — so they outlive the
|
|
182
|
+
session that created them.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
skit agent install # pick one of the agent directories found on your machine
|
|
186
|
+
skit agent install claude # or name it: claude / codex / agents (--project for this repo only)
|
|
187
|
+
npx skills add t41372/skit # or install through skills.sh into 70+ agents
|
|
188
|
+
```
|
|
189
|
+
|
|
128
190
|
## Languages
|
|
129
191
|
|
|
130
192
|
| Language | Status |
|
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: skit-cli
|
|
3
|
-
Version: 0.0.1
|
|
4
|
-
Summary: Script launcher and parameter manager: pick a script, fill in a form, run — no more opening an IDE just to tweak constants.
|
|
5
|
-
Keywords: script,launcher,uv,tui,pep723,script-manager
|
|
6
|
-
Author: Yi-Ting Chiu
|
|
7
|
-
Author-email: Yi-Ting Chiu <mytim710@gmail.com>
|
|
8
|
-
License-Expression: MIT
|
|
9
|
-
License-File: LICENSE
|
|
10
|
-
Classifier: Development Status :: 3 - Alpha
|
|
11
|
-
Classifier: Environment :: Console
|
|
12
|
-
Classifier: Environment :: Console :: Curses
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
20
|
-
Classifier: Topic :: Utilities
|
|
21
|
-
Requires-Dist: typer>=0.26,<1
|
|
22
|
-
Requires-Dist: platformdirs>=4.10,<5
|
|
23
|
-
Requires-Dist: tomli-w>=1.2,<2
|
|
24
|
-
Requires-Dist: textual>=8.2,<9
|
|
25
|
-
Requires-Dist: rich>=15,<16
|
|
26
|
-
Requires-Python: >=3.12
|
|
27
|
-
Project-URL: Repository, https://github.com/t41372/skit
|
|
28
|
-
Project-URL: Issues, https://github.com/t41372/skit/issues
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
|
|
31
1
|

|
|
32
2
|
|
|
33
3
|
[](https://github.com/t41372/skit/actions/workflows/ci.yml)
|
|
@@ -47,7 +17,11 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
47
17
|
|
|
48
18
|
**AI writes the scripts. skit gives them a home.**
|
|
49
19
|
|
|
50
|
-
|
|
20
|
+
And it's a home you share with your agent: the library you drive from a menu, AI agents
|
|
21
|
+
drive through a deterministic CLI — checking it before writing yet another one-off
|
|
22
|
+
script, and (with your OK) saving the good ones back, so they outlive the chat.
|
|
23
|
+
|
|
24
|
+
<video src="https://github.com/user-attachments/assets/d27121fe-5855-4270-91b0-b0ee9e5d11ee" controls></video>
|
|
51
25
|
|
|
52
26
|
## What it does
|
|
53
27
|
|
|
@@ -57,6 +31,7 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
57
31
|
- **No environment mess.** skit declares each script's dependencies in the script itself (PEP 723) and runs it through uv in an isolated, cached environment — no venvs to manage, nothing installed globally.
|
|
58
32
|
- **Mouse or keyboard.** Plain `skit` opens the full TUI; every key hint on screen is also a clickable button.
|
|
59
33
|
- **Automation-ready.** Every TUI action is also a CLI command with `--json` output and meaningful exit codes — for shell scripts, CI, and AI agents.
|
|
34
|
+
- **Your agent's script library too.** The official [Agent Skill](https://agentskills.io) teaches Claude Code, Codex, Cursor, Gemini CLI, and friends the whole drill: discover scripts with `skit list`, read a parameter schema with `skit show`, run with `skit run --set … --no-input`. One `skit agent install` away — see [Works with your AI agent](#works-with-your-ai-agent).
|
|
60
35
|
- **Speaks your language.** English, 繁體中文, and 简体中文, with more to come. See [Languages](#languages).
|
|
61
36
|
|
|
62
37
|
| Problem | What skit does |
|
|
@@ -64,6 +39,7 @@ skit stores your Python scripts in one place and makes them painless to launch.
|
|
|
64
39
|
| Scripts scattered all over the place | One central menu, with search |
|
|
65
40
|
| Scripts with weird external dependencies | An isolated environment per script — dependencies declared in the file (PEP 723), resolved by uv |
|
|
66
41
|
| CLI flags you forget ten minutes later, `input()` prompts, hard-coded constants meant to be edited by hand | Static analysis extracts them all into an interactive form — no code changes. Last-used values come prefilled; favorites save as presets. |
|
|
42
|
+
| The weird script an AI wrote for you dies with the chat session | Agents check the library first, reuse what's there, and save the keepers — one-off scripts become permanent, parameterized tools |
|
|
67
43
|
|
|
68
44
|
Nothing to set up per script — no refactoring, no config to maintain. The script an AI wrote last week and the one you barely remember from last year launch the same way.
|
|
69
45
|
|
|
@@ -102,6 +78,15 @@ uv tool install git+https://github.com/t41372/skit # latest development
|
|
|
102
78
|
uvx --from git+https://github.com/t41372/skit skit --help # try it without installing
|
|
103
79
|
```
|
|
104
80
|
|
|
81
|
+
## Update
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv tool upgrade skit-cli # update to the latest release — also how you "check": it says up to date if you are
|
|
85
|
+
skit --version # the version you're on
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`uv tool upgrade` follows whatever source you installed from: PyPI installs track PyPI releases, `git+…` installs re-fetch the main branch.
|
|
89
|
+
|
|
105
90
|
## Uninstall
|
|
106
91
|
|
|
107
92
|
```bash
|
|
@@ -149,12 +134,29 @@ The rest of the CLI exists for automation and AI agents — every TUI action, sc
|
|
|
149
134
|
```bash
|
|
150
135
|
skit run my_script -p fast # run with a saved preset
|
|
151
136
|
skit run my_script --dry-run # print the exact command, don't run it
|
|
137
|
+
skit run my_script --set width=800 --no-input # set values explicitly, never prompt
|
|
138
|
+
skit show my_script --json # one script's full parameter schema, machine-readable
|
|
152
139
|
skit params my_script # show managed parameters and last-used values
|
|
153
140
|
skit list --json # machine-readable listing
|
|
154
141
|
skit config # settings: language, editor, mirror, form style
|
|
155
142
|
skit --help # everything else
|
|
156
143
|
```
|
|
157
144
|
|
|
145
|
+
## Works with your AI agent
|
|
146
|
+
|
|
147
|
+
skit is a script repository for humans *and* AI agents: one library — you get the
|
|
148
|
+
forms, agents get a deterministic CLI. The official [Agent Skill](https://agentskills.io)
|
|
149
|
+
teaches compatible agents (Claude Code, Codex, Cursor, Gemini CLI, and many more) to
|
|
150
|
+
check your library before writing yet another one-off script, to inspect and run what's
|
|
151
|
+
already there, and to offer to save the useful scripts they write — so they outlive the
|
|
152
|
+
session that created them.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
skit agent install # pick one of the agent directories found on your machine
|
|
156
|
+
skit agent install claude # or name it: claude / codex / agents (--project for this repo only)
|
|
157
|
+
npx skills add t41372/skit # or install through skills.sh into 70+ agents
|
|
158
|
+
```
|
|
159
|
+
|
|
158
160
|
## Languages
|
|
159
161
|
|
|
160
162
|
| Language | Status |
|
|
@@ -2,14 +2,25 @@
|
|
|
2
2
|
# Distribution name only ("skit" is admin-prohibited on PyPI); the import package,
|
|
3
3
|
# CLI command, and config dirs all stay "skit".
|
|
4
4
|
name = "skit-cli"
|
|
5
|
-
version = "0.0
|
|
6
|
-
description = "Script launcher and parameter manager:
|
|
5
|
+
version = "0.2.0"
|
|
6
|
+
description = "Script launcher and parameter manager: all your scripts in one menu, their flags, input() calls, and constants auto-extracted into a run form — no more opening an IDE just to tweak a value."
|
|
7
7
|
readme = "README.md"
|
|
8
8
|
requires-python = ">=3.12"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
license-files = ["LICENSE"]
|
|
11
11
|
authors = [{ name = "Yi-Ting Chiu", email = "mytim710@gmail.com" }]
|
|
12
|
-
keywords = [
|
|
12
|
+
keywords = [
|
|
13
|
+
"script",
|
|
14
|
+
"launcher",
|
|
15
|
+
"uv",
|
|
16
|
+
"tui",
|
|
17
|
+
"pep723",
|
|
18
|
+
"script-manager",
|
|
19
|
+
"script-runner",
|
|
20
|
+
"automation",
|
|
21
|
+
"ai-agent",
|
|
22
|
+
"agent-skills",
|
|
23
|
+
]
|
|
13
24
|
classifiers = [
|
|
14
25
|
"Development Status :: 3 - Alpha",
|
|
15
26
|
"Environment :: Console",
|
|
@@ -174,5 +185,9 @@ ignore = [
|
|
|
174
185
|
"tests/corpus/**" = ["ALL"] # corpus files are verbatim analyzer inputs, not code style subjects
|
|
175
186
|
|
|
176
187
|
[tool.mutmut]
|
|
177
|
-
paths_to_mutate
|
|
178
|
-
|
|
188
|
+
# Modern key names (paths_to_mutate / tests_dir print deprecation warnings on 3.6).
|
|
189
|
+
source_paths = ["src/skit/"]
|
|
190
|
+
pytest_add_cli_args_test_selection = ["tests/"]
|
|
191
|
+
# The skill-sync test (tests/test_agent_skill.py) compares the repo-root skill against
|
|
192
|
+
# the packaged copy; mutmut's mutants/ tree only carries src/ + tests/ by default.
|
|
193
|
+
also_copy = ["skills/"]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""skit — a launcher and parameter manager for your scripts."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
5
|
+
|
|
6
|
+
# Textual ≥ 8.2.7 enables the kitty keyboard protocol's "report all keys" mode, which
|
|
7
|
+
# iTerm2 (3.6.x) implements in a way that fights the macOS IME: candidate-selection
|
|
8
|
+
# digits and Enter reach the app as raw key events and the composed CJK text is never
|
|
9
|
+
# delivered at all (iTerm2 issue 12906) — Chinese/Japanese/Korean typing breaks. No
|
|
10
|
+
# skit binding needs the protocol, so opt out before the first textual import
|
|
11
|
+
# (textual.constants reads this at import time). setdefault keeps an explicit user
|
|
12
|
+
# override (=0 re-enables) winning.
|
|
13
|
+
os.environ.setdefault("TEXTUAL_DISABLE_KITTY_KEY", "1")
|
|
14
|
+
|
|
15
|
+
# pyproject.toml is the single source of the version; installed distributions carry it
|
|
16
|
+
# as metadata (a wheel doesn't ship pyproject.toml), so read it from there.
|
|
17
|
+
try:
|
|
18
|
+
__version__ = version("skit-cli")
|
|
19
|
+
except PackageNotFoundError: # a bare checkout on sys.path, no installed dist
|
|
20
|
+
__version__ = "0.0.0+unknown"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""The bundled Agent Skill and where it can be installed (headless; the CLI renders).
|
|
2
|
+
|
|
3
|
+
skit ships an official Agent Skill (skills/skit/SKILL.md, agentskills.io format) that
|
|
4
|
+
teaches AI coding agents to drive the library through the CLI. This module owns the
|
|
5
|
+
headless half of `skit agent install`: locating the bundled copy inside the installed
|
|
6
|
+
package and computing which agent skill directories exist on this machine.
|
|
7
|
+
|
|
8
|
+
Non-invasive by design (AGENTS.md principle #6): detection only reports directories
|
|
9
|
+
whose parent tool is already present (`~/.claude` exists, `./.codex` exists, …) — skit
|
|
10
|
+
never conjures another tool's config tree uninvited. Writing anywhere requires either
|
|
11
|
+
an explicit target/--to (consent by flag) or an interactive confirmation.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from importlib import resources
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
SKILL_DIR_NAME = "skit"
|
|
21
|
+
SKILL_FILE_NAME = "SKILL.md"
|
|
22
|
+
|
|
23
|
+
# Known agent conventions: tool marker directory -> skills subdirectory lives inside it.
|
|
24
|
+
# claude: Claude Code (user ~/.claude/skills, project .claude/skills)
|
|
25
|
+
# codex: OpenAI Codex CLI (user ~/.codex/skills, project .codex/skills)
|
|
26
|
+
# agents: the cross-agent project convention (.agents/skills), project-only.
|
|
27
|
+
USER_TARGETS = {"claude": ".claude", "codex": ".codex"}
|
|
28
|
+
PROJECT_TARGETS = {"claude": ".claude", "codex": ".codex", "agents": ".agents"}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Target:
|
|
33
|
+
"""One place the skill can be installed: `<base>/skills/skit/SKILL.md`."""
|
|
34
|
+
|
|
35
|
+
name: str # "claude" | "codex" | "agents"
|
|
36
|
+
scope: str # "user" | "project"
|
|
37
|
+
base: Path # the tool's marker directory (e.g. ~/.claude)
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def skills_dir(self) -> Path:
|
|
41
|
+
return self.base / "skills"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def default_roots() -> tuple[Path, Path]:
|
|
45
|
+
"""(home, cwd) for target resolution — one seam for the CLI and for tests. Tests
|
|
46
|
+
redirect this instead of monkeypatching os.getcwd, which mutmut's stats
|
|
47
|
+
bookkeeping also relies on mid-test."""
|
|
48
|
+
return Path.home(), Path.cwd()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def skill_text() -> str:
|
|
52
|
+
"""The bundled SKILL.md, read from the installed package (single source: the repo's
|
|
53
|
+
skills/skit/SKILL.md is a test-enforced byte-identical copy of this file)."""
|
|
54
|
+
res = resources.files("skit").joinpath("skills", SKILL_DIR_NAME, SKILL_FILE_NAME)
|
|
55
|
+
# I/O kwarg mutants ("utf-8"→"UTF-8"/None) are equivalent aliases here; content is
|
|
56
|
+
# pinned by test_skill_text_is_the_bundled_skill (see docs/mutation-ledger.md).
|
|
57
|
+
return res.read_text(encoding="utf-8") # pragma: no mutate
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def named_target(name: str, *, project: bool, home: Path, cwd: Path) -> Target | None:
|
|
61
|
+
"""Resolve an explicit target name ("claude" / "codex" / "agents") to a Target.
|
|
62
|
+
None for an unknown name. `agents` is a project-level convention, so it resolves
|
|
63
|
+
to the project scope regardless of the --project flag."""
|
|
64
|
+
if name == "agents":
|
|
65
|
+
return Target(name=name, scope="project", base=cwd / PROJECT_TARGETS[name])
|
|
66
|
+
if project:
|
|
67
|
+
marker = PROJECT_TARGETS.get(name)
|
|
68
|
+
return Target(name=name, scope="project", base=cwd / marker) if marker else None
|
|
69
|
+
marker = USER_TARGETS.get(name)
|
|
70
|
+
return Target(name=name, scope="user", base=home / marker) if marker else None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def detect_targets(*, home: Path, cwd: Path) -> list[Target]:
|
|
74
|
+
"""Every known target whose marker directory already exists — the tool is in use,
|
|
75
|
+
so offering to drop a skill inside it is not an intrusion. User scope first."""
|
|
76
|
+
found: list[Target] = []
|
|
77
|
+
for name, marker in USER_TARGETS.items():
|
|
78
|
+
base = home / marker
|
|
79
|
+
if base.is_dir():
|
|
80
|
+
found.append(Target(name=name, scope="user", base=base))
|
|
81
|
+
for name, marker in PROJECT_TARGETS.items():
|
|
82
|
+
base = cwd / marker
|
|
83
|
+
if base.is_dir():
|
|
84
|
+
found.append(Target(name=name, scope="project", base=base))
|
|
85
|
+
return found
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def install_into(skills_dir: Path, text: str) -> Path:
|
|
89
|
+
"""Write `text` as `<skills_dir>/skit/SKILL.md` (idempotent: rewriting is how an
|
|
90
|
+
upgrade lands). Returns the written file's path. Deliberately write-only — the
|
|
91
|
+
caller resolves the bundled copy via skill_text() first, so a broken installation
|
|
92
|
+
fails loudly there instead of masquerading as a destination write error here."""
|
|
93
|
+
dest = skills_dir / SKILL_DIR_NAME
|
|
94
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
95
|
+
out = dest / SKILL_FILE_NAME
|
|
96
|
+
# Same I/O kwarg equivalence; written content is pinned byte-for-byte by
|
|
97
|
+
# test_install_into_writes_and_upgrades (see docs/mutation-ledger.md).
|
|
98
|
+
out.write_text(text, encoding="utf-8") # pragma: no mutate
|
|
99
|
+
return out
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import shutil
|
|
8
8
|
import sys
|
|
9
9
|
import tempfile
|
|
10
|
+
import time
|
|
10
11
|
import tomllib
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from pathlib import Path
|
|
@@ -14,6 +15,29 @@ from typing import Any
|
|
|
14
15
|
|
|
15
16
|
import tomli_w
|
|
16
17
|
|
|
18
|
+
# Windows can't replace a file that another handle has open (sharing violation →
|
|
19
|
+
# PermissionError); concurrent readers of registry.toml hold it for microseconds, so a
|
|
20
|
+
# bounded exponential backoff is the standard idiom (total worst-case wait ≈ 1.3 s).
|
|
21
|
+
# POSIX replaces open files freely, so this path never fires there.
|
|
22
|
+
_REPLACE_RETRIES = 7 # sleeps: 0.01 · 0.02 · 0.04 · 0.08 · 0.16 · 0.32 · 0.64 s
|
|
23
|
+
_REPLACE_BACKOFF_START = 0.01
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _replace_with_retry(src: str, dst: Path) -> None:
|
|
27
|
+
"""os.replace that rides out transient Windows sharing violations. After the
|
|
28
|
+
retries are exhausted, the final attempt's PermissionError propagates — a target
|
|
29
|
+
held open indefinitely (antivirus, an actual leak) must stay loud."""
|
|
30
|
+
delay = _REPLACE_BACKOFF_START
|
|
31
|
+
for _ in range(_REPLACE_RETRIES):
|
|
32
|
+
try:
|
|
33
|
+
os.replace(src, dst)
|
|
34
|
+
except PermissionError:
|
|
35
|
+
time.sleep(delay)
|
|
36
|
+
delay *= 2
|
|
37
|
+
else:
|
|
38
|
+
return
|
|
39
|
+
os.replace(src, dst)
|
|
40
|
+
|
|
17
41
|
|
|
18
42
|
def _fsync_dir(dir_path: Path) -> None:
|
|
19
43
|
"""Fsync a directory fd so a prior os.replace()'s rename entry is durable on stable storage.
|
|
@@ -39,7 +63,7 @@ def atomic_write_bytes(path: Path, data: bytes) -> None:
|
|
|
39
63
|
f.write(data)
|
|
40
64
|
f.flush()
|
|
41
65
|
os.fsync(f.fileno()) # durable on disk BEFORE the rename, not just before this returns
|
|
42
|
-
|
|
66
|
+
_replace_with_retry(tmp, path)
|
|
43
67
|
if sys.platform != "win32": # os.open can't open a directory on Windows
|
|
44
68
|
with contextlib.suppress(OSError):
|
|
45
69
|
_fsync_dir(path.parent) # best-effort: persist the rename's directory entry too
|