dktcli 1.0.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.
- dktcli-1.0.0/LICENSE +21 -0
- dktcli-1.0.0/PKG-INFO +148 -0
- dktcli-1.0.0/README.md +129 -0
- dktcli-1.0.0/pyproject.toml +33 -0
- dktcli-1.0.0/setup.cfg +4 -0
- dktcli-1.0.0/src/devkittools/__init__.py +3 -0
- dktcli-1.0.0/src/devkittools/__main__.py +6 -0
- dktcli-1.0.0/src/devkittools/cli.py +179 -0
- dktcli-1.0.0/src/devkittools/tools/__init__.py +1 -0
- dktcli-1.0.0/src/devkittools/tools/bench.py +73 -0
- dktcli-1.0.0/src/devkittools/tools/changeloggen.py +181 -0
- dktcli-1.0.0/src/devkittools/tools/clean.py +110 -0
- dktcli-1.0.0/src/devkittools/tools/colortool.py +126 -0
- dktcli-1.0.0/src/devkittools/tools/commitwriter.py +199 -0
- dktcli-1.0.0/src/devkittools/tools/contextpacker.py +226 -0
- dktcli-1.0.0/src/devkittools/tools/cron.py +152 -0
- dktcli-1.0.0/src/devkittools/tools/doctor.py +74 -0
- dktcli-1.0.0/src/devkittools/tools/du.py +72 -0
- dktcli-1.0.0/src/devkittools/tools/enc.py +131 -0
- dktcli-1.0.0/src/devkittools/tools/envtool.py +141 -0
- dktcli-1.0.0/src/devkittools/tools/findtool.py +119 -0
- dktcli-1.0.0/src/devkittools/tools/gen.py +182 -0
- dktcli-1.0.0/src/devkittools/tools/gx.py +184 -0
- dktcli-1.0.0/src/devkittools/tools/hashtool.py +93 -0
- dktcli-1.0.0/src/devkittools/tools/httptool.py +131 -0
- dktcli-1.0.0/src/devkittools/tools/ignore.py +213 -0
- dktcli-1.0.0/src/devkittools/tools/initproj.py +166 -0
- dktcli-1.0.0/src/devkittools/tools/jsontool.py +200 -0
- dktcli-1.0.0/src/devkittools/tools/net.py +114 -0
- dktcli-1.0.0/src/devkittools/tools/portkiller.py +246 -0
- dktcli-1.0.0/src/devkittools/tools/regextool.py +84 -0
- dktcli-1.0.0/src/devkittools/tools/repostats.py +197 -0
- dktcli-1.0.0/src/devkittools/tools/secrets_scan.py +108 -0
- dktcli-1.0.0/src/devkittools/tools/serve.py +118 -0
- dktcli-1.0.0/src/devkittools/tools/snip.py +94 -0
- dktcli-1.0.0/src/devkittools/tools/system.py +157 -0
- dktcli-1.0.0/src/devkittools/tools/timetool.py +130 -0
- dktcli-1.0.0/src/devkittools/tools/todo.py +69 -0
- dktcli-1.0.0/src/devkittools/tools/tree.py +86 -0
- dktcli-1.0.0/src/devkittools/tools/watch.py +82 -0
- dktcli-1.0.0/src/devkittools/utils.py +561 -0
- dktcli-1.0.0/src/dktcli.egg-info/PKG-INFO +148 -0
- dktcli-1.0.0/src/dktcli.egg-info/SOURCES.txt +46 -0
- dktcli-1.0.0/src/dktcli.egg-info/dependency_links.txt +1 -0
- dktcli-1.0.0/src/dktcli.egg-info/entry_points.txt +3 -0
- dktcli-1.0.0/src/dktcli.egg-info/top_level.txt +1 -0
- dktcli-1.0.0/tests/test_dkt.py +64 -0
- dktcli-1.0.0/tests/test_tools.py +193 -0
dktcli-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 <your name>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
dktcli-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dktcli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: devkittools: the developer's swiss army knife - 35+ zero-dependency CLI tools (ports, serve, json, git helpers, secrets scan, cron, and more) under one command: dkt
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: cli,developer-tools,devtools,git,changelog,ollama,productivity,utilities,toolbox
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# dkt - the developer's swiss army knife
|
|
21
|
+
|
|
22
|
+
**35+ small, sharp CLI tools under one command: `dkt`.**
|
|
23
|
+
Pure Python, **zero dependencies**, Windows / macOS / Linux. Colored output, spinners, aligned tables,
|
|
24
|
+
typo suggestions ("did you mean..."), tab-completion, and an interactive menu.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
____ _ _______
|
|
28
|
+
| _ \| |/ /_ _| devkittools
|
|
29
|
+
| | | | ' / | | the developer's swiss army knife
|
|
30
|
+
| |_| | . \ | |
|
|
31
|
+
|____/|_|\_\ |_|
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install dktcli
|
|
38
|
+
dkt # see every tool
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
No PyPI? From this folder: `pip install .` - or on Windows just double-click **`install.bat`**
|
|
42
|
+
(it also fixes your PATH). If `dkt` isn't found, `python -m devkittools` always works.
|
|
43
|
+
|
|
44
|
+
**Update:** `dkt update` (PyPI installs) or double-click **`update.bat`** (does `git pull` if this is a clone).
|
|
45
|
+
**Uninstall:** `uninstall.bat` or `pip uninstall dktcli`.
|
|
46
|
+
|
|
47
|
+
## The tools
|
|
48
|
+
|
|
49
|
+
Run `dkt` for the list, `dkt <tool> -h` for examples of any tool, or `dkt menu` for an interactive launcher.
|
|
50
|
+
|
|
51
|
+
### Servers & Network
|
|
52
|
+
| Command | Alias | What it does |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `dkt portkiller` | `pk` | what's using port 3000? kill it |
|
|
55
|
+
| `dkt serve` | `sv` | static server with CORS, SPA mode, LAN URL, `--open` |
|
|
56
|
+
| `dkt http` | `req` | HTTP requests with pretty JSON, timing, headers, downloads |
|
|
57
|
+
| `dkt net` | | `ip` (local/public), `dns`, `check host:port` |
|
|
58
|
+
|
|
59
|
+
### Project & Code
|
|
60
|
+
| Command | Alias | What it does |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `dkt repostats` | `rs` | lines per language, biggest files, git activity, health check |
|
|
63
|
+
| `dkt tree` | `tr` | pretty tree, respects .gitignore, `--sizes`, `--copy` |
|
|
64
|
+
| `dkt find` | `fd` | project-wide search (regex, context lines) + find files + safe find & replace |
|
|
65
|
+
| `dkt todo` | `td` | every TODO / FIXME / HACK with file:line |
|
|
66
|
+
| `dkt secrets` | `sec` | scan for leaked API keys / tokens / private keys (CI-friendly exit code) |
|
|
67
|
+
| `dkt clean` | `cl` | delete node_modules, `__pycache__`, caches with sizes + confirmation |
|
|
68
|
+
| `dkt du` | | what's eating my disk, with bars |
|
|
69
|
+
| `dkt watch` | `w` | re-run any command when files change |
|
|
70
|
+
| `dkt bench` | `bn` | benchmark / compare commands |
|
|
71
|
+
| `dkt env` | | env vars (secrets masked), PATH doctor, `.env` vs `.env.example` |
|
|
72
|
+
| `dkt ignore` | `gi` | .gitignore generator (python, node, unity, minecraft, godot...) |
|
|
73
|
+
| `dkt license` | `lic` | LICENSE file (MIT, ISC, BSD, Unlicense) |
|
|
74
|
+
| `dkt init` | `new` | scaffold python / node / web projects with git + license |
|
|
75
|
+
|
|
76
|
+
### Git
|
|
77
|
+
| Command | What it does |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `dkt gx log` | compact colored graph |
|
|
80
|
+
| `dkt gx branches [--prune]` | branch overview, delete merged branches |
|
|
81
|
+
| `dkt gx undo` / `wip` / `amend` / `sync` / `who` | everyday shortcuts |
|
|
82
|
+
| `dkt changeloggen` (`clg`) | CHANGELOG.md from conventional commits |
|
|
83
|
+
|
|
84
|
+
### AI helpers
|
|
85
|
+
| Command | What it does |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `dkt projectcontextpacker` (`pcp`) | pack a project into one AI-prompt file, with token counts |
|
|
88
|
+
| `dkt commitwriter` (`cw`) | commit messages from your diff using local Ollama |
|
|
89
|
+
|
|
90
|
+
### Generate & Convert
|
|
91
|
+
| Command | Alias | What it does |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `dkt gen` | `g` | `uuid` (v4/v7), `ulid`, `password`, `token`, `id`, `lorem`, `slug` |
|
|
94
|
+
| `dkt enc` | `e` | base64 / url / hex / html encode & decode |
|
|
95
|
+
| `dkt jwt` | | decode a JWT, shows expiry |
|
|
96
|
+
| `dkt hash` | `h` | md5/sha*/blake2, files, `--verify`, `--hmac` |
|
|
97
|
+
| `dkt time` | `tm` | epoch <-> date, date math, differences |
|
|
98
|
+
| `dkt color` | `col` | hex/rgb/hsl, live swatches, contrast ratio, palettes |
|
|
99
|
+
|
|
100
|
+
### Data & Text
|
|
101
|
+
| Command | Alias | What it does |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `dkt json` | `js` | `fmt`, `min`, `get a.b[0]`, `keys`, `flat`, `csv`, `validate` (forgives comments / trailing commas) |
|
|
104
|
+
| `dkt regex` | `rx` | highlighted matches, named groups, `--replace` |
|
|
105
|
+
| `dkt cron` | | explain a cron expression + next runs |
|
|
106
|
+
| `dkt snip` | | personal snippet notebook (`add` / `get --copy` / `ls` / `rm`) |
|
|
107
|
+
|
|
108
|
+
### System & dkt itself
|
|
109
|
+
`dkt doctor` (system info + which dev tools you have), `dkt update`, `dkt config`, `dkt completion`, `dkt menu`, `dkt about`.
|
|
110
|
+
|
|
111
|
+
## Handy patterns
|
|
112
|
+
```bash
|
|
113
|
+
dkt js fmt --paste --copy # clean up JSON from your clipboard
|
|
114
|
+
cat resp.json | dkt js get data.items[0].id
|
|
115
|
+
dkt sv --open --spa # serve the folder, SPA fallback, open browser
|
|
116
|
+
dkt w -c "pytest -q" --clear # tests on every save
|
|
117
|
+
dkt sec --json || echo "secrets found" # CI gate
|
|
118
|
+
dkt tr --copy # paste a project tree into docs / AI chats
|
|
119
|
+
dkt new python mytool # new project, ready to go
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Tab completion
|
|
123
|
+
```powershell
|
|
124
|
+
dkt completion powershell | Out-String | Add-Content $PROFILE # PowerShell
|
|
125
|
+
```
|
|
126
|
+
```bash
|
|
127
|
+
eval "$(dkt completion bash)" # add to ~/.bashrc (zsh: dkt completion zsh)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Settings
|
|
131
|
+
`dkt config set author "Your Name"` (used by `license` / `init`), `dkt config set model qwen2.5-coder:7b` (commit writer).
|
|
132
|
+
Env vars `DKT_<KEY>` override the file. `NO_COLOR=1` disables colors, `DKT_ASCII=1` disables unicode symbols.
|
|
133
|
+
|
|
134
|
+
## Publishing to PyPI (maintainers)
|
|
135
|
+
1. The PyPI package is called **`dktcli`** (set in `pyproject.toml`); the command you type is still `dkt`.
|
|
136
|
+
2. Run **`publish.bat`** (builds, checks, uploads with your API token) - or use the included GitHub Action
|
|
137
|
+
(`.github/workflows/publish.yml`, trusted publishing) and just create a Release.
|
|
138
|
+
3. Bump `__version__` in `src/devkittools/__init__.py` for every release.
|
|
139
|
+
|
|
140
|
+
## Development
|
|
141
|
+
```bash
|
|
142
|
+
pip install -e .
|
|
143
|
+
python -m unittest discover -s tests
|
|
144
|
+
```
|
|
145
|
+
Adding a tool = one file in `src/devkittools/tools/` with a `META` tuple and a `register()` function, then add it to `MODULES` in `cli.py`.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
MIT
|
dktcli-1.0.0/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# dkt - the developer's swiss army knife
|
|
2
|
+
|
|
3
|
+
**35+ small, sharp CLI tools under one command: `dkt`.**
|
|
4
|
+
Pure Python, **zero dependencies**, Windows / macOS / Linux. Colored output, spinners, aligned tables,
|
|
5
|
+
typo suggestions ("did you mean..."), tab-completion, and an interactive menu.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
____ _ _______
|
|
9
|
+
| _ \| |/ /_ _| devkittools
|
|
10
|
+
| | | | ' / | | the developer's swiss army knife
|
|
11
|
+
| |_| | . \ | |
|
|
12
|
+
|____/|_|\_\ |_|
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install dktcli
|
|
19
|
+
dkt # see every tool
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
No PyPI? From this folder: `pip install .` - or on Windows just double-click **`install.bat`**
|
|
23
|
+
(it also fixes your PATH). If `dkt` isn't found, `python -m devkittools` always works.
|
|
24
|
+
|
|
25
|
+
**Update:** `dkt update` (PyPI installs) or double-click **`update.bat`** (does `git pull` if this is a clone).
|
|
26
|
+
**Uninstall:** `uninstall.bat` or `pip uninstall dktcli`.
|
|
27
|
+
|
|
28
|
+
## The tools
|
|
29
|
+
|
|
30
|
+
Run `dkt` for the list, `dkt <tool> -h` for examples of any tool, or `dkt menu` for an interactive launcher.
|
|
31
|
+
|
|
32
|
+
### Servers & Network
|
|
33
|
+
| Command | Alias | What it does |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `dkt portkiller` | `pk` | what's using port 3000? kill it |
|
|
36
|
+
| `dkt serve` | `sv` | static server with CORS, SPA mode, LAN URL, `--open` |
|
|
37
|
+
| `dkt http` | `req` | HTTP requests with pretty JSON, timing, headers, downloads |
|
|
38
|
+
| `dkt net` | | `ip` (local/public), `dns`, `check host:port` |
|
|
39
|
+
|
|
40
|
+
### Project & Code
|
|
41
|
+
| Command | Alias | What it does |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `dkt repostats` | `rs` | lines per language, biggest files, git activity, health check |
|
|
44
|
+
| `dkt tree` | `tr` | pretty tree, respects .gitignore, `--sizes`, `--copy` |
|
|
45
|
+
| `dkt find` | `fd` | project-wide search (regex, context lines) + find files + safe find & replace |
|
|
46
|
+
| `dkt todo` | `td` | every TODO / FIXME / HACK with file:line |
|
|
47
|
+
| `dkt secrets` | `sec` | scan for leaked API keys / tokens / private keys (CI-friendly exit code) |
|
|
48
|
+
| `dkt clean` | `cl` | delete node_modules, `__pycache__`, caches with sizes + confirmation |
|
|
49
|
+
| `dkt du` | | what's eating my disk, with bars |
|
|
50
|
+
| `dkt watch` | `w` | re-run any command when files change |
|
|
51
|
+
| `dkt bench` | `bn` | benchmark / compare commands |
|
|
52
|
+
| `dkt env` | | env vars (secrets masked), PATH doctor, `.env` vs `.env.example` |
|
|
53
|
+
| `dkt ignore` | `gi` | .gitignore generator (python, node, unity, minecraft, godot...) |
|
|
54
|
+
| `dkt license` | `lic` | LICENSE file (MIT, ISC, BSD, Unlicense) |
|
|
55
|
+
| `dkt init` | `new` | scaffold python / node / web projects with git + license |
|
|
56
|
+
|
|
57
|
+
### Git
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `dkt gx log` | compact colored graph |
|
|
61
|
+
| `dkt gx branches [--prune]` | branch overview, delete merged branches |
|
|
62
|
+
| `dkt gx undo` / `wip` / `amend` / `sync` / `who` | everyday shortcuts |
|
|
63
|
+
| `dkt changeloggen` (`clg`) | CHANGELOG.md from conventional commits |
|
|
64
|
+
|
|
65
|
+
### AI helpers
|
|
66
|
+
| Command | What it does |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `dkt projectcontextpacker` (`pcp`) | pack a project into one AI-prompt file, with token counts |
|
|
69
|
+
| `dkt commitwriter` (`cw`) | commit messages from your diff using local Ollama |
|
|
70
|
+
|
|
71
|
+
### Generate & Convert
|
|
72
|
+
| Command | Alias | What it does |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `dkt gen` | `g` | `uuid` (v4/v7), `ulid`, `password`, `token`, `id`, `lorem`, `slug` |
|
|
75
|
+
| `dkt enc` | `e` | base64 / url / hex / html encode & decode |
|
|
76
|
+
| `dkt jwt` | | decode a JWT, shows expiry |
|
|
77
|
+
| `dkt hash` | `h` | md5/sha*/blake2, files, `--verify`, `--hmac` |
|
|
78
|
+
| `dkt time` | `tm` | epoch <-> date, date math, differences |
|
|
79
|
+
| `dkt color` | `col` | hex/rgb/hsl, live swatches, contrast ratio, palettes |
|
|
80
|
+
|
|
81
|
+
### Data & Text
|
|
82
|
+
| Command | Alias | What it does |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| `dkt json` | `js` | `fmt`, `min`, `get a.b[0]`, `keys`, `flat`, `csv`, `validate` (forgives comments / trailing commas) |
|
|
85
|
+
| `dkt regex` | `rx` | highlighted matches, named groups, `--replace` |
|
|
86
|
+
| `dkt cron` | | explain a cron expression + next runs |
|
|
87
|
+
| `dkt snip` | | personal snippet notebook (`add` / `get --copy` / `ls` / `rm`) |
|
|
88
|
+
|
|
89
|
+
### System & dkt itself
|
|
90
|
+
`dkt doctor` (system info + which dev tools you have), `dkt update`, `dkt config`, `dkt completion`, `dkt menu`, `dkt about`.
|
|
91
|
+
|
|
92
|
+
## Handy patterns
|
|
93
|
+
```bash
|
|
94
|
+
dkt js fmt --paste --copy # clean up JSON from your clipboard
|
|
95
|
+
cat resp.json | dkt js get data.items[0].id
|
|
96
|
+
dkt sv --open --spa # serve the folder, SPA fallback, open browser
|
|
97
|
+
dkt w -c "pytest -q" --clear # tests on every save
|
|
98
|
+
dkt sec --json || echo "secrets found" # CI gate
|
|
99
|
+
dkt tr --copy # paste a project tree into docs / AI chats
|
|
100
|
+
dkt new python mytool # new project, ready to go
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Tab completion
|
|
104
|
+
```powershell
|
|
105
|
+
dkt completion powershell | Out-String | Add-Content $PROFILE # PowerShell
|
|
106
|
+
```
|
|
107
|
+
```bash
|
|
108
|
+
eval "$(dkt completion bash)" # add to ~/.bashrc (zsh: dkt completion zsh)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Settings
|
|
112
|
+
`dkt config set author "Your Name"` (used by `license` / `init`), `dkt config set model qwen2.5-coder:7b` (commit writer).
|
|
113
|
+
Env vars `DKT_<KEY>` override the file. `NO_COLOR=1` disables colors, `DKT_ASCII=1` disables unicode symbols.
|
|
114
|
+
|
|
115
|
+
## Publishing to PyPI (maintainers)
|
|
116
|
+
1. The PyPI package is called **`dktcli`** (set in `pyproject.toml`); the command you type is still `dkt`.
|
|
117
|
+
2. Run **`publish.bat`** (builds, checks, uploads with your API token) - or use the included GitHub Action
|
|
118
|
+
(`.github/workflows/publish.yml`, trusted publishing) and just create a Release.
|
|
119
|
+
3. Bump `__version__` in `src/devkittools/__init__.py` for every release.
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
```bash
|
|
123
|
+
pip install -e .
|
|
124
|
+
python -m unittest discover -s tests
|
|
125
|
+
```
|
|
126
|
+
Adding a tool = one file in `src/devkittools/tools/` with a `META` tuple and a `register()` function, then add it to `MODULES` in `cli.py`.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "dktcli"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "devkittools: the developer's swiss army knife - 35+ zero-dependency CLI tools (ports, serve, json, git helpers, secrets scan, cron, and more) under one command: dkt"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["cli", "developer-tools", "devtools", "git", "changelog", "ollama", "productivity", "utilities", "toolbox"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Topic :: Software Development :: Build Tools",
|
|
21
|
+
"Topic :: Utilities",
|
|
22
|
+
]
|
|
23
|
+
dependencies = []
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
dkt = "devkittools.cli:main"
|
|
27
|
+
devkittools = "devkittools.cli:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.dynamic]
|
|
30
|
+
version = { attr = "devkittools.__version__" }
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
dktcli-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Entry point for the `dkt` command."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import difflib
|
|
6
|
+
import re
|
|
7
|
+
import shlex
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .tools import (bench, changeloggen, clean, colortool, commitwriter, contextpacker, cron, doctor, du, enc,
|
|
13
|
+
envtool, findtool, gen, gx, hashtool, httptool, ignore, initproj, jsontool, net, portkiller,
|
|
14
|
+
regextool, repostats, secrets_scan, serve, snip, system, timetool, todo, tree, watch)
|
|
15
|
+
from .utils import DktError, banner, c, is_interactive, ask, setup_console, sym, term_width
|
|
16
|
+
|
|
17
|
+
VERSION_STR = "devkittools %s" % __version__
|
|
18
|
+
|
|
19
|
+
# Order here = order in the help screen.
|
|
20
|
+
MODULES = [
|
|
21
|
+
portkiller, serve, httptool, net,
|
|
22
|
+
repostats, tree, findtool, todo, secrets_scan, clean, du, watch, bench, envtool, ignore, initproj,
|
|
23
|
+
gx, changeloggen,
|
|
24
|
+
contextpacker, commitwriter,
|
|
25
|
+
gen, enc, hashtool, timetool, colortool,
|
|
26
|
+
jsontool, regextool, cron, snip,
|
|
27
|
+
doctor,
|
|
28
|
+
]
|
|
29
|
+
CATEGORY_ORDER = ["Servers & Network", "Project & Code", "Git", "AI Helpers", "Generate & Convert",
|
|
30
|
+
"Data & Text", "System"]
|
|
31
|
+
EXTRA = [ # (category, name, alias, summary) for commands without a META of their own
|
|
32
|
+
("Generate & Convert", "jwt", "", "decode JSON Web Tokens"),
|
|
33
|
+
("Project & Code", "license", "lic", "write a LICENSE file"),
|
|
34
|
+
("dkt itself", "update", "", "upgrade dkt to the newest version"),
|
|
35
|
+
("dkt itself", "config", "", "view / change settings"),
|
|
36
|
+
("dkt itself", "completion", "", "shell tab-completion script"),
|
|
37
|
+
("dkt itself", "menu", "", "interactive launcher"),
|
|
38
|
+
("dkt itself", "about", "", "logo, version and tips"),
|
|
39
|
+
]
|
|
40
|
+
CATEGORY_ORDER.append("dkt itself")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def all_tools():
|
|
44
|
+
rows = [m.META for m in MODULES] + EXTRA
|
|
45
|
+
return rows
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def grouped_help() -> str:
|
|
49
|
+
lines = []
|
|
50
|
+
cats = {}
|
|
51
|
+
for cat, name, alias, summary in all_tools():
|
|
52
|
+
cats.setdefault(cat, []).append((name, alias, summary))
|
|
53
|
+
for cat in CATEGORY_ORDER:
|
|
54
|
+
items = cats.get(cat)
|
|
55
|
+
if not items:
|
|
56
|
+
continue
|
|
57
|
+
lines.append("")
|
|
58
|
+
lines.append(" " + c(cat, "bold bmagenta"))
|
|
59
|
+
for name, alias, summary in items:
|
|
60
|
+
if len(name) > 12 and alias:
|
|
61
|
+
name, alias = alias, name
|
|
62
|
+
short = ("(%s)" % alias) if alias else ""
|
|
63
|
+
lines.append(" %s %s %s" % (c(name.ljust(10), "bold bcyan"), c(short.ljust(6), "gray"), summary))
|
|
64
|
+
return "\n".join(lines)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def print_overview() -> None:
|
|
68
|
+
banner()
|
|
69
|
+
print(grouped_help())
|
|
70
|
+
print()
|
|
71
|
+
print(" " + c("usage:", "bold") + " dkt <tool> [options] " + c("dkt <tool> -h for examples", "gray"))
|
|
72
|
+
print(" " + c("tip:", "bold") + " dkt menu for an interactive launcher, dkt completion for tab-complete")
|
|
73
|
+
print()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class OverviewAction(argparse.Action):
|
|
77
|
+
def __init__(self, option_strings, dest, **kw):
|
|
78
|
+
super().__init__(option_strings, dest, nargs=0, **kw)
|
|
79
|
+
|
|
80
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
81
|
+
print_overview()
|
|
82
|
+
parser.exit(0)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DktParser(argparse.ArgumentParser):
|
|
86
|
+
tool_names: List[str] = []
|
|
87
|
+
|
|
88
|
+
def error(self, message):
|
|
89
|
+
m = re.search(r"invalid choice: '([^']*)'", message)
|
|
90
|
+
if m and self.prog == "dkt":
|
|
91
|
+
bad = m.group(1)
|
|
92
|
+
guess = difflib.get_close_matches(bad, self.tool_names, n=3, cutoff=0.5)
|
|
93
|
+
sys.stderr.write("%s unknown tool '%s'\n" % (c("error:", "red"), bad))
|
|
94
|
+
if guess:
|
|
95
|
+
sys.stderr.write(" did you mean: %s ?\n" % ", ".join(c(g, "bold bcyan") for g in guess))
|
|
96
|
+
sys.stderr.write(" run 'dkt' to see all tools\n")
|
|
97
|
+
self.exit(2)
|
|
98
|
+
super().error(message)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def build_parser():
|
|
102
|
+
parser = DktParser(prog="dkt", description="devkittools - the developer's swiss army knife.",
|
|
103
|
+
add_help=False, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
104
|
+
parser.add_argument("-h", "--help", action=OverviewAction, help="show all tools")
|
|
105
|
+
parser.add_argument("-v", "-V", "--version", action="version", version=VERSION_STR, help="show version")
|
|
106
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
107
|
+
common.add_argument("-v", "-V", "--version", action="version", version=VERSION_STR, help="show version and exit")
|
|
108
|
+
sub = parser.add_subparsers(dest="command", metavar="<tool>", title="tools")
|
|
109
|
+
for m in MODULES:
|
|
110
|
+
m.register(sub, common)
|
|
111
|
+
system.register(sub, common)
|
|
112
|
+
menu = sub.add_parser("menu", aliases=["m"], parents=[common], help="interactive launcher")
|
|
113
|
+
menu.set_defaults(func=lambda a: run_menu())
|
|
114
|
+
DktParser.tool_names = sorted(sub.choices)
|
|
115
|
+
return parser
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def run_menu() -> int:
|
|
119
|
+
if not is_interactive():
|
|
120
|
+
print_overview()
|
|
121
|
+
return 0
|
|
122
|
+
banner()
|
|
123
|
+
tools = []
|
|
124
|
+
cats = {}
|
|
125
|
+
for cat, name, alias, summary in all_tools():
|
|
126
|
+
if name in ("menu",):
|
|
127
|
+
continue
|
|
128
|
+
cats.setdefault(cat, []).append((name, alias, summary))
|
|
129
|
+
n = 0
|
|
130
|
+
for cat in CATEGORY_ORDER:
|
|
131
|
+
if cat not in cats:
|
|
132
|
+
continue
|
|
133
|
+
print(" " + c(cat, "bold bmagenta"))
|
|
134
|
+
for name, alias, summary in cats[cat]:
|
|
135
|
+
n += 1
|
|
136
|
+
tools.append(name)
|
|
137
|
+
print(" %s %s %s" % (c(str(n).rjust(2), "gray"), c(name.ljust(10), "bold bcyan"), summary))
|
|
138
|
+
print()
|
|
139
|
+
while True:
|
|
140
|
+
pick = ask("pick a tool by number or name (q to quit)")
|
|
141
|
+
if pick.lower() in ("", "q", "quit", "exit"):
|
|
142
|
+
return 0
|
|
143
|
+
name = tools[int(pick) - 1] if pick.isdigit() and 0 < int(pick) <= len(tools) else pick
|
|
144
|
+
print()
|
|
145
|
+
main([name, "--help"] if True else [])
|
|
146
|
+
line = ask("arguments (blank = run with none, 'b' = back)")
|
|
147
|
+
if line.lower() == "b":
|
|
148
|
+
continue
|
|
149
|
+
try:
|
|
150
|
+
argv = [name] + shlex.split(line)
|
|
151
|
+
except ValueError as e:
|
|
152
|
+
print(c("bad quoting: %s" % e, "red"))
|
|
153
|
+
continue
|
|
154
|
+
print()
|
|
155
|
+
main(argv)
|
|
156
|
+
print()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
160
|
+
setup_console()
|
|
161
|
+
parser = build_parser()
|
|
162
|
+
args = parser.parse_args(argv)
|
|
163
|
+
if not getattr(args, "func", None):
|
|
164
|
+
print_overview()
|
|
165
|
+
return 0
|
|
166
|
+
try:
|
|
167
|
+
return int(args.func(args) or 0)
|
|
168
|
+
except DktError as e:
|
|
169
|
+
print(c("error:", "red"), e, file=sys.stderr)
|
|
170
|
+
return 1
|
|
171
|
+
except KeyboardInterrupt:
|
|
172
|
+
print(file=sys.stderr)
|
|
173
|
+
return 130
|
|
174
|
+
except BrokenPipeError:
|
|
175
|
+
return 0
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
sys.exit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Individual dkt tools. Each module exposes register(subparsers, common) and run(args)."""
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""dkt bench (bn) - time a command over several runs."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import statistics
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from ..utils import DktError, Spinner, bar, c, kv
|
|
10
|
+
|
|
11
|
+
META = ("Project & Code", "bench", "bn", "benchmark any command: min/mean/max over N runs")
|
|
12
|
+
|
|
13
|
+
EPILOG = """\
|
|
14
|
+
examples:
|
|
15
|
+
dkt bn "python script.py"
|
|
16
|
+
dkt bn "node a.js" "node b.js" -n 20 compare two commands
|
|
17
|
+
dkt bn "make build" --warmup 2 --runs 5
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def fmt(s: float) -> str:
|
|
22
|
+
return "%.0f ms" % (s * 1000) if s < 1 else "%.2f s" % s
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def time_cmd(cmd: str, runs: int, warmup: int):
|
|
26
|
+
times = []
|
|
27
|
+
fails = 0
|
|
28
|
+
for i in range(warmup + runs):
|
|
29
|
+
t0 = time.perf_counter()
|
|
30
|
+
rc = subprocess.call(cmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
31
|
+
dt = time.perf_counter() - t0
|
|
32
|
+
if i >= warmup:
|
|
33
|
+
times.append(dt)
|
|
34
|
+
fails += rc != 0
|
|
35
|
+
return times, fails
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def run(args) -> int:
|
|
39
|
+
if not args.commands:
|
|
40
|
+
raise DktError('Give at least one command, e.g. dkt bn "python script.py"')
|
|
41
|
+
results = []
|
|
42
|
+
for cmd in args.commands:
|
|
43
|
+
with Spinner("benchmarking: %s" % cmd):
|
|
44
|
+
times, fails = time_cmd(cmd, args.runs, args.warmup)
|
|
45
|
+
results.append((cmd, times, fails))
|
|
46
|
+
best = min(statistics.mean(t) for _, t, _ in results)
|
|
47
|
+
for cmd, times, fails in results:
|
|
48
|
+
mean = statistics.mean(times)
|
|
49
|
+
print("\n " + c(cmd, "bold bcyan"))
|
|
50
|
+
rows = [("mean", c(fmt(mean), "bold") + (" " + c("± " + fmt(statistics.stdev(times)), "gray") if len(times) > 1 else "")),
|
|
51
|
+
("range", "%s … %s" % (fmt(min(times)), fmt(max(times)))),
|
|
52
|
+
("runs", "%d%s" % (len(times), c(" (%d failed)" % fails, "red") if fails else ""))]
|
|
53
|
+
if len(results) > 1:
|
|
54
|
+
rows.append(("vs best", "%.2fx" % (mean / best) if mean > best else c("fastest", "bgreen")))
|
|
55
|
+
kv(rows)
|
|
56
|
+
if len(results) > 1:
|
|
57
|
+
print()
|
|
58
|
+
mx = max(statistics.mean(t) for _, t, _ in results)
|
|
59
|
+
for cmd, times, _ in results:
|
|
60
|
+
m = statistics.mean(times)
|
|
61
|
+
print(" %s %s %s" % (c(bar(m, mx, 24), "bmagenta"), fmt(m).rjust(8), cmd))
|
|
62
|
+
print()
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def register(sub, common) -> None:
|
|
67
|
+
p = sub.add_parser("bench", aliases=["bn"], parents=[common], help=META[3],
|
|
68
|
+
description="Benchmark shell commands.", epilog=EPILOG,
|
|
69
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
70
|
+
p.add_argument("commands", nargs="*", metavar="CMD")
|
|
71
|
+
p.add_argument("-n", "--runs", type=int, default=10)
|
|
72
|
+
p.add_argument("--warmup", type=int, default=1)
|
|
73
|
+
p.set_defaults(func=run)
|