devkit-tools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ """devkit — 面向开发者的命令行工具包。"""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
dev_toolkit/cli.py ADDED
@@ -0,0 +1,19 @@
1
+ """devkit 主 CLI 组。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from . import __version__
8
+ from .convert import convert_cmd
9
+ from .envcheck import envcheck_cmd
10
+
11
+
12
+ @click.group()
13
+ @click.version_option(__version__, prog_name="devkit")
14
+ def cli() -> None:
15
+ """devkit — 面向开发者的命令行工具包。"""
16
+
17
+
18
+ cli.add_command(convert_cmd)
19
+ cli.add_command(envcheck_cmd)
@@ -0,0 +1,6 @@
1
+ """convert 模块:JSON / YAML / TOML / CSV 互转。"""
2
+
3
+ from .cli import convert_cmd
4
+ from .core import convert
5
+
6
+ __all__ = ["convert", "convert_cmd"]
@@ -0,0 +1,55 @@
1
+ """convert 子命令的 Click 入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from .core import convert
11
+ from .handlers import FORMATS, ConvertError
12
+
13
+ # JSON/YAML/TOML 整树加载,设输入大小上限(requirements.md NF-001)
14
+ MAX_INPUT_BYTES = 50 * 1024 * 1024
15
+
16
+
17
+ @click.command("convert")
18
+ @click.argument("input", type=click.Path(dir_okay=False, path_type=Path), required=False)
19
+ @click.argument("output", type=click.Path(dir_okay=False, path_type=Path), required=False)
20
+ @click.option("--from", "from_fmt", type=click.Choice(FORMATS), help="输入格式(默认自动检测)")
21
+ @click.option("--to", "to_fmt", type=click.Choice(FORMATS), help="输出格式(默认按 output 扩展名)")
22
+ @click.option("--pretty", is_flag=True, help="美化输出")
23
+ def convert_cmd(input: Path | None, output: Path | None,
24
+ from_fmt: str | None, to_fmt: str | None, pretty: bool) -> None:
25
+ """在 JSON / YAML / TOML / CSV 之间转换。
26
+
27
+ INPUT 省略时从 stdin 读取;OUTPUT 省略时写到 stdout。
28
+ """
29
+ filename = None
30
+ if input is None:
31
+ text = sys.stdin.read()
32
+ else:
33
+ raw = input.read_bytes()
34
+ if len(raw) > MAX_INPUT_BYTES:
35
+ raise click.ClickException(
36
+ f"输入超过上限 {MAX_INPUT_BYTES} 字节;大文件转换暂不支持"
37
+ )
38
+ text = raw.decode("utf-8")
39
+ filename = input.name
40
+
41
+ if to_fmt is None and output is not None:
42
+ from .core import detect_format
43
+ to_fmt = detect_format("", output.name)
44
+ if to_fmt is None:
45
+ raise click.ClickException("无法确定输出格式,请用 --to 指定")
46
+
47
+ try:
48
+ result = convert(text, to_fmt, from_fmt=from_fmt, filename=filename, pretty=pretty)
49
+ except ConvertError as exc:
50
+ raise click.ClickException(str(exc)) from exc
51
+
52
+ if output is None:
53
+ click.echo(result, nl=False)
54
+ else:
55
+ output.write_text(result, encoding="utf-8")
@@ -0,0 +1,40 @@
1
+ """convert 统一转换接口:格式检测 + 读 → 写。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .handlers import FORMATS, ConvertError, dump, load
6
+
7
+ _EXT = {
8
+ ".json": "json",
9
+ ".yaml": "yaml",
10
+ ".yml": "yaml",
11
+ ".toml": "toml",
12
+ ".csv": "csv",
13
+ }
14
+
15
+ # ponytail: 内容嗅探只覆盖无歧义的首字符;拿不准就让用户用 --from。
16
+ def detect_format(text: str, filename: str | None = None) -> str | None:
17
+ """从扩展名优先、内容嗅探兜底地推断格式。无法确定返回 None。"""
18
+ if filename:
19
+ for ext, fmt in _EXT.items():
20
+ if filename.lower().endswith(ext):
21
+ return fmt
22
+ stripped = text.lstrip()
23
+ if not stripped:
24
+ return None
25
+ if stripped[0] in "{[":
26
+ return "json"
27
+ return None
28
+
29
+
30
+ def convert(text: str, to_fmt: str, from_fmt: str | None = None,
31
+ filename: str | None = None, pretty: bool = False) -> str:
32
+ if from_fmt is None:
33
+ from_fmt = detect_format(text, filename)
34
+ if from_fmt is None:
35
+ raise ConvertError(
36
+ "无法自动检测输入格式,请用 --from 指定"
37
+ f"(支持:{', '.join(FORMATS)})"
38
+ )
39
+ data = load(text, from_fmt)
40
+ return dump(data, to_fmt, pretty=pretty)
@@ -0,0 +1,125 @@
1
+ """各数据格式的读写处理器。
2
+
3
+ 读 → 统一的 Python dict/list/scalar 数据模型 → 写。
4
+ CSV 仅支持二维表(表头 + 行记录),见 requirements.md 2.2.1 转换边界。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import csv
10
+ import io
11
+ import json
12
+ import sys
13
+ from typing import Any
14
+
15
+ import yaml
16
+
17
+ # TOML 读:tomllib(3.11+) / tomli(3.10 回退);写:tomli-w
18
+ if sys.version_info >= (3, 11):
19
+ import tomllib as _toml_read
20
+ else: # pragma: no cover - 仅 3.10 走这里
21
+ import tomli as _toml_read
22
+
23
+ import tomli_w
24
+
25
+ FORMATS = ("json", "yaml", "toml", "csv")
26
+
27
+
28
+ class ConvertError(ValueError):
29
+ """转换边界相关的可预期错误(格式不支持、CSV 结构不符等)。"""
30
+
31
+
32
+ # ---- 读:bytes/str → 数据模型 ----
33
+
34
+ def _load_json(text: str) -> Any:
35
+ return json.loads(text)
36
+
37
+
38
+ def _load_yaml(text: str) -> Any:
39
+ return yaml.safe_load(text)
40
+
41
+
42
+ def _load_toml(text: str) -> Any:
43
+ return _toml_read.loads(text)
44
+
45
+
46
+ def _load_csv(text: str) -> list[dict[str, str]]:
47
+ return list(csv.DictReader(io.StringIO(text)))
48
+
49
+
50
+ _LOADERS = {
51
+ "json": _load_json,
52
+ "yaml": _load_yaml,
53
+ "toml": _load_toml,
54
+ "csv": _load_csv,
55
+ }
56
+
57
+
58
+ # ---- 写:数据模型 → str ----
59
+
60
+ def _dump_json(data: Any, pretty: bool) -> str:
61
+ return json.dumps(data, ensure_ascii=False, indent=2 if pretty else None)
62
+
63
+
64
+ def _dump_yaml(data: Any, pretty: bool) -> str:
65
+ # pretty 对 YAML 无额外含义;safe_dump 已是可读多行格式
66
+ return yaml.safe_dump(data, allow_unicode=True, sort_keys=False)
67
+
68
+
69
+ def _dump_toml(data: Any, pretty: bool) -> str:
70
+ if not isinstance(data, dict):
71
+ raise ConvertError("TOML 顶层必须是表(dict),无法写入列表或标量")
72
+ return tomli_w.dumps(data)
73
+
74
+
75
+ def _dump_csv(data: Any, pretty: bool) -> str:
76
+ rows = _as_table(data)
77
+ fieldnames: list[str] = []
78
+ for row in rows:
79
+ for key in row:
80
+ if key not in fieldnames:
81
+ fieldnames.append(key)
82
+ buf = io.StringIO()
83
+ writer = csv.DictWriter(buf, fieldnames=fieldnames)
84
+ writer.writeheader()
85
+ writer.writerows(rows)
86
+ return buf.getvalue()
87
+
88
+
89
+ def _as_table(data: Any) -> list[dict[str, Any]]:
90
+ """把数据模型规约为 CSV 二维表(list[dict[扁平标量]]),否则显式失败。"""
91
+ if isinstance(data, dict):
92
+ data = [data]
93
+ if not isinstance(data, list):
94
+ raise ConvertError("CSV 仅支持二维表:需要对象列表或单个对象")
95
+ rows: list[dict[str, Any]] = []
96
+ for item in data:
97
+ if not isinstance(item, dict):
98
+ raise ConvertError("CSV 行必须是对象(dict)")
99
+ for key, value in item.items():
100
+ if isinstance(value, (dict, list)):
101
+ raise ConvertError(
102
+ f"CSV 不支持嵌套字段 {key!r};请先显式展开为扁平字段"
103
+ )
104
+ rows.append(item)
105
+ return rows
106
+
107
+
108
+ _DUMPERS = {
109
+ "json": _dump_json,
110
+ "yaml": _dump_yaml,
111
+ "toml": _dump_toml,
112
+ "csv": _dump_csv,
113
+ }
114
+
115
+
116
+ def load(text: str, fmt: str) -> Any:
117
+ if fmt not in _LOADERS:
118
+ raise ConvertError(f"不支持的输入格式:{fmt}(支持:{', '.join(FORMATS)})")
119
+ return _LOADERS[fmt](text)
120
+
121
+
122
+ def dump(data: Any, fmt: str, pretty: bool = False) -> str:
123
+ if fmt not in _DUMPERS:
124
+ raise ConvertError(f"不支持的输出格式:{fmt}(支持:{', '.join(FORMATS)})")
125
+ return _DUMPERS[fmt](data, pretty)
@@ -0,0 +1,5 @@
1
+ """envcheck 模块:开发环境只读诊断。"""
2
+
3
+ from .cli import envcheck_cmd
4
+
5
+ __all__ = ["envcheck_cmd"]
@@ -0,0 +1,73 @@
1
+ """单个工具检查的实现:定位可执行文件、取版本、比对最低版本。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ from dataclasses import dataclass
9
+
10
+ from packaging.version import InvalidVersion, Version
11
+
12
+ _VERSION_RE = re.compile(r"(\d+\.\d+(?:\.\d+)?)")
13
+
14
+
15
+ @dataclass
16
+ class CheckSpec:
17
+ """一条检查规格(来自 YAML 配置)。"""
18
+
19
+ name: str
20
+ command: str
21
+ version_arg: str = "--version"
22
+ min_version: str | None = None
23
+
24
+
25
+ @dataclass
26
+ class CheckResult:
27
+ name: str
28
+ found: bool
29
+ path: str | None
30
+ version: str | None
31
+ ok: bool
32
+ detail: str
33
+
34
+ def suggestion(self) -> str | None:
35
+ if not self.found:
36
+ return f"未找到 {self.name},请安装并确保其在 PATH 中"
37
+ if not self.ok:
38
+ return f"{self.name} 版本 {self.version} 低于要求,请升级"
39
+ return None
40
+
41
+
42
+ def _query_version(command: str, version_arg: str) -> str | None:
43
+ try:
44
+ proc = subprocess.run(
45
+ [command, version_arg],
46
+ capture_output=True, text=True, timeout=10,
47
+ )
48
+ except (OSError, subprocess.SubprocessError):
49
+ return None
50
+ output = (proc.stdout or "") + (proc.stderr or "")
51
+ match = _VERSION_RE.search(output)
52
+ return match.group(1) if match else None
53
+
54
+
55
+ def run_check(spec: CheckSpec) -> CheckResult:
56
+ path = shutil.which(spec.command)
57
+ if path is None:
58
+ return CheckResult(spec.name, False, None, None, False, "未安装或不在 PATH")
59
+
60
+ version = _query_version(spec.command, spec.version_arg)
61
+ if spec.min_version is None:
62
+ return CheckResult(spec.name, True, path, version, True, "已安装")
63
+
64
+ if version is None:
65
+ return CheckResult(spec.name, True, path, None, False, "无法解析版本")
66
+
67
+ try:
68
+ ok = Version(version) >= Version(spec.min_version)
69
+ except InvalidVersion:
70
+ return CheckResult(spec.name, True, path, version, False, "版本号格式异常")
71
+
72
+ detail = "满足要求" if ok else f"需要 >= {spec.min_version}"
73
+ return CheckResult(spec.name, True, path, version, ok, detail)
@@ -0,0 +1,27 @@
1
+ """envcheck 子命令的 Click 入口。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from .core import load_specs, run_all
11
+ from .reporters import render
12
+
13
+
14
+ @click.command("envcheck")
15
+ @click.option("--config", type=click.Path(exists=True, dir_okay=False, path_type=Path),
16
+ help="YAML 检查项配置(默认使用预置检查项)")
17
+ @click.option("--format", "fmt", type=click.Choice(["table", "json"]), default="table",
18
+ show_default=True, help="报告格式(html 属 v0.2+)")
19
+ @click.option("--suggest-fix", is_flag=True, help="输出修复建议(不修改系统)")
20
+ def envcheck_cmd(config: Path | None, fmt: str, suggest_fix: bool) -> None:
21
+ """诊断开发环境:检查必备工具的安装与版本。"""
22
+ specs = load_specs(config)
23
+ results = run_all(specs)
24
+ click.echo(render(results, fmt, suggest_fix))
25
+ # 任一检查未通过 → 非零退出码(requirements.md NF-003)
26
+ if any(not r.ok for r in results):
27
+ sys.exit(1)
@@ -0,0 +1,41 @@
1
+ """envcheck 引擎:加载配置 → 并行跑检查 → 汇总结果。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from pathlib import Path
7
+
8
+ import yaml
9
+
10
+ from .checks import CheckResult, CheckSpec, run_check
11
+
12
+ # 无配置文件时的预置检查项
13
+ DEFAULT_CHECKS = [
14
+ CheckSpec("Python", "python", "--version", "3.10"),
15
+ CheckSpec("Git", "git", "--version"),
16
+ CheckSpec("Node.js", "node", "--version"),
17
+ CheckSpec("Docker", "docker", "--version"),
18
+ CheckSpec("Go", "go", "version"),
19
+ CheckSpec("Rust", "rustc", "--version"),
20
+ ]
21
+
22
+
23
+ def load_specs(config: Path | None) -> list[CheckSpec]:
24
+ if config is None:
25
+ return DEFAULT_CHECKS
26
+ data = yaml.safe_load(config.read_text(encoding="utf-8")) or {}
27
+ specs = []
28
+ for item in data.get("checks", []):
29
+ specs.append(CheckSpec(
30
+ name=item["name"],
31
+ command=item["command"],
32
+ version_arg=item.get("version_arg", "--version"),
33
+ min_version=item.get("min_version"),
34
+ ))
35
+ return specs
36
+
37
+
38
+ def run_all(specs: list[CheckSpec]) -> list[CheckResult]:
39
+ # 检查项可并行执行(requirements.md 2.4 #5);I/O 密集用线程足够
40
+ with ThreadPoolExecutor(max_workers=8) as pool:
41
+ return list(pool.map(run_check, specs))
@@ -0,0 +1,49 @@
1
+ """envcheck 报告输出:table / json。
2
+
3
+ HTML 报告属 v0.2+ 路线图(requirements.md 2.6 Later),v0.1 不实现。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import json
9
+
10
+ from .checks import CheckResult
11
+
12
+
13
+ def report_table(results: list[CheckResult], suggest_fix: bool) -> str:
14
+ lines = []
15
+ width = max((len(r.name) for r in results), default=4)
16
+ for r in results:
17
+ mark = "OK " if r.ok else "FAIL"
18
+ ver = r.version or "-"
19
+ lines.append(f"[{mark}] {r.name:<{width}} {ver:<12} {r.detail}")
20
+ if suggest_fix:
21
+ fixes = [s for r in results if (s := r.suggestion())]
22
+ if fixes:
23
+ lines.append("")
24
+ lines.append("修复建议:")
25
+ lines.extend(f" - {f}" for f in fixes)
26
+ return "\n".join(lines)
27
+
28
+
29
+ def report_json(results: list[CheckResult], suggest_fix: bool) -> str:
30
+ payload = []
31
+ for r in results:
32
+ entry = {
33
+ "name": r.name,
34
+ "found": r.found,
35
+ "path": r.path,
36
+ "version": r.version,
37
+ "ok": r.ok,
38
+ "detail": r.detail,
39
+ }
40
+ if suggest_fix:
41
+ entry["suggestion"] = r.suggestion()
42
+ payload.append(entry)
43
+ return json.dumps(payload, ensure_ascii=False, indent=2)
44
+
45
+
46
+ def render(results: list[CheckResult], fmt: str, suggest_fix: bool) -> str:
47
+ if fmt == "json":
48
+ return report_json(results, suggest_fix)
49
+ return report_table(results, suggest_fix)
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: devkit-tools
3
+ Version: 0.1.0
4
+ Summary: 面向开发者的命令行工具包:数据格式转换 + 环境诊断
5
+ Project-URL: Homepage, https://github.com/yukinpost/devkit-cli
6
+ Project-URL: Issues, https://github.com/yukinpost/devkit-cli/issues
7
+ Author-email: yukinpost <jasonliaojx98@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: cli,convert,envcheck,json,toml,yaml
11
+ Classifier: Environment :: Console
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: click>=8.1
20
+ Requires-Dist: packaging>=23.0
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: tomli-w>=1.0
23
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
24
+ Provides-Extra: dev
25
+ Requires-Dist: mypy>=1.10; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
27
+ Requires-Dist: pytest>=7.4; extra == 'dev'
28
+ Requires-Dist: ruff>=0.5; extra == 'dev'
29
+ Requires-Dist: types-pyyaml; extra == 'dev'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # devkit-tools
33
+
34
+ [![CI](https://github.com/yukinpost/devkit-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/yukinpost/devkit-cli/actions/workflows/ci.yml)
35
+
36
+ 面向开发者的命令行工具包。v0.1 提供两个开箱即用的子命令:
37
+
38
+ - **`convert`** — 在 JSON / YAML / TOML / CSV 之间互相转换
39
+ - **`envcheck`** — 诊断开发环境中必备工具的安装与版本
40
+
41
+ > 路线图模块(`templater` / `watch` / `gitstats`)见 [docs/requirements.md](docs/requirements.md),将在 v0.2+ 实现。
42
+
43
+ ## 安装
44
+
45
+ ```bash
46
+ pip install devkit-tools
47
+ ```
48
+
49
+ 需要 Python 3.10+。
50
+
51
+ ## convert
52
+
53
+ ```bash
54
+ # 文件互转(输出格式由扩展名推断)
55
+ devkit convert config.json config.yaml
56
+
57
+ # 显式指定格式
58
+ devkit convert data.txt --from json --to toml
59
+
60
+ # 管道模式
61
+ cat data.json | devkit convert --to yaml
62
+
63
+ # 美化输出
64
+ devkit convert data.json --to json --pretty
65
+ ```
66
+
67
+ 支持矩阵与边界(如 CSV 仅支持二维表)见 [docs/requirements.md](docs/requirements.md) 2.2.1。
68
+
69
+ ## envcheck
70
+
71
+ ```bash
72
+ # 用预置检查项诊断环境
73
+ devkit envcheck
74
+
75
+ # 自定义检查项 + 输出修复建议
76
+ devkit envcheck --config examples/config/dev-toolkit.yml --suggest-fix
77
+
78
+ # JSON 报告(便于 CI 消费)
79
+ devkit envcheck --format json
80
+ ```
81
+
82
+ 任一检查未通过时返回非零退出码。`--suggest-fix` 只输出建议,**不会修改你的系统**。
83
+
84
+ ## 开发
85
+
86
+ ```bash
87
+ pip install -e ".[dev]"
88
+ pytest # 测试 + 覆盖率
89
+ ruff check . # lint
90
+ mypy # 类型检查
91
+ ```
92
+
93
+ ## License
94
+
95
+ [MIT](LICENSE)
@@ -0,0 +1,17 @@
1
+ dev_toolkit/__init__.py,sha256=KGDCUdaySLJ91l-9PXsq40jJxBg6OCf3GdZdIWGEbgc,80
2
+ dev_toolkit/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
3
+ dev_toolkit/cli.py,sha256=myujRTrg5as8eL4i6DNifRjQDZHVrRVKKCAeKofSVw8,386
4
+ dev_toolkit/convert/__init__.py,sha256=Ji1m1-_F737VfIOW8Z2h5XAgJMOGUXPl_1bMcKzG3pA,152
5
+ dev_toolkit/convert/cli.py,sha256=HppjOsQEGF2kxPeHJJ1UTHSu7zXixEHm6p9uQ2XwSco,2002
6
+ dev_toolkit/convert/core.py,sha256=C3XQFdmeg-kwNl0abWq-1-JIyeWvggW6mEER3AY_CBU,1268
7
+ dev_toolkit/convert/handlers.py,sha256=o0TK-7PQik5wWd1_Piuc4y7FkMXSE23_ApTuEand5U8,3445
8
+ dev_toolkit/envcheck/__init__.py,sha256=mrhO5oQtI41nE6BDGpvi-i9yQsQT8fs35xo3zQSp-Uo,111
9
+ dev_toolkit/envcheck/checks.py,sha256=zVEIpgaf25cuh2ibzmfuo_hm3frt5l6xxKMYQwICF0w,2137
10
+ dev_toolkit/envcheck/cli.py,sha256=Rn7mwmodFjI0UMlM3d4Idfypz0v2HDtfa1LYaN5-HWw,1040
11
+ dev_toolkit/envcheck/core.py,sha256=0N-p9EotwnarUpNFDjW3NKQ4nWB_e1zzHCuIWi0XBlY,1314
12
+ dev_toolkit/envcheck/reporters.py,sha256=AHY-cGd_yNe3WsZdx9eKP_vhNpPsLBAWqPiP4x95PYE,1457
13
+ devkit_tools-0.1.0.dist-info/METADATA,sha256=NhxPCD78d-gt6DxPL2DTKQQ_ICzumYmnXaYJk-9dzCM,2761
14
+ devkit_tools-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
15
+ devkit_tools-0.1.0.dist-info/entry_points.txt,sha256=NMEr1Y1mN_WFDDEdPrUxhsByqtJOCHYVm6ZC7-NsVRg,47
16
+ devkit_tools-0.1.0.dist-info/licenses/LICENSE,sha256=6vA3xWebNN8urkgFj8MH5DLC3mGwtMg8PPD1MRtw_kY,1067
17
+ devkit_tools-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devkit = dev_toolkit.cli:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jason Liao
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.