devit-cli 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.
Files changed (52) hide show
  1. _devkit_entry.py +59 -0
  2. devit_cli-0.1.0.dist-info/METADATA +273 -0
  3. devit_cli-0.1.0.dist-info/RECORD +52 -0
  4. devit_cli-0.1.0.dist-info/WHEEL +5 -0
  5. devit_cli-0.1.0.dist-info/entry_points.txt +2 -0
  6. devit_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  7. devit_cli-0.1.0.dist-info/top_level.txt +2 -0
  8. devkit_cli/__init__.py +4 -0
  9. devkit_cli/commands/__init__.py +0 -0
  10. devkit_cli/commands/archive.py +166 -0
  11. devkit_cli/commands/clean.py +130 -0
  12. devkit_cli/commands/env.py +156 -0
  13. devkit_cli/commands/find.py +122 -0
  14. devkit_cli/commands/info.py +119 -0
  15. devkit_cli/commands/init.py +451 -0
  16. devkit_cli/commands/run.py +236 -0
  17. devkit_cli/main.py +89 -0
  18. devkit_cli/templates/aws/.gitignore +43 -0
  19. devkit_cli/templates/aws/README.md +23 -0
  20. devkit_cli/templates/aws/requirements.txt +2 -0
  21. devkit_cli/templates/aws/scripts/__init__.py +0 -0
  22. devkit_cli/templates/aws/scripts/ec2.py +9 -0
  23. devkit_cli/templates/aws/scripts/main.py +25 -0
  24. devkit_cli/templates/aws/scripts/s3.py +10 -0
  25. devkit_cli/templates/aws/tests/test_scripts.py +6 -0
  26. devkit_cli/templates/django/.gitignore +43 -0
  27. devkit_cli/templates/django/README.md +15 -0
  28. devkit_cli/templates/django/apps/__init__.py +0 -0
  29. devkit_cli/templates/django/apps/core/__init__.py +0 -0
  30. devkit_cli/templates/django/apps/core/apps.py +6 -0
  31. devkit_cli/templates/django/apps/core/urls.py +6 -0
  32. devkit_cli/templates/django/apps/core/views.py +7 -0
  33. devkit_cli/templates/django/manage.py +20 -0
  34. devkit_cli/templates/django/requirements.txt +1 -0
  35. devkit_cli/templates/django/{{module_name}}/__init__.py +0 -0
  36. devkit_cli/templates/django/{{module_name}}/settings.py +61 -0
  37. devkit_cli/templates/django/{{module_name}}/urls.py +9 -0
  38. devkit_cli/templates/django/{{module_name}}/wsgi.py +7 -0
  39. devkit_cli/templates/fastapi/.gitignore +43 -0
  40. devkit_cli/templates/fastapi/README.md +21 -0
  41. devkit_cli/templates/fastapi/app/__init__.py +0 -0
  42. devkit_cli/templates/fastapi/app/routers/__init__.py +0 -0
  43. devkit_cli/templates/fastapi/app/routers/health.py +10 -0
  44. devkit_cli/templates/fastapi/main.py +13 -0
  45. devkit_cli/templates/fastapi/requirements.txt +6 -0
  46. devkit_cli/templates/fastapi/tests/test_api.py +17 -0
  47. devkit_cli/templates/package/.gitignore +43 -0
  48. devkit_cli/templates/package/README.md +15 -0
  49. devkit_cli/templates/package/pyproject.toml +29 -0
  50. devkit_cli/templates/package/tests/test_core.py +8 -0
  51. devkit_cli/templates/package/{{module_name}}/__init__.py +3 -0
  52. devkit_cli/templates/package/{{module_name}}/core.py +5 -0
@@ -0,0 +1,130 @@
1
+ """devkit clean — Remove build artifacts, caches, and junk files."""
2
+
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ import click
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ console = Console()
11
+
12
+ # Directories to delete entirely
13
+ CLEAN_DIRS = [
14
+ "__pycache__",
15
+ ".pytest_cache",
16
+ ".mypy_cache",
17
+ ".ruff_cache",
18
+ "build",
19
+ "dist",
20
+ "*.egg-info",
21
+ ".eggs",
22
+ "node_modules",
23
+ ".next",
24
+ ".nuxt",
25
+ "htmlcov",
26
+ "site",
27
+ ".tox",
28
+ ".nox",
29
+ ]
30
+
31
+ # Individual file patterns to delete
32
+ CLEAN_FILES = [
33
+ "*.pyc",
34
+ "*.pyo",
35
+ "*.pyd",
36
+ ".DS_Store",
37
+ "Thumbs.db",
38
+ "*.log",
39
+ "*.tmp",
40
+ "*.bak",
41
+ "*.swp",
42
+ "*.swo",
43
+ ".coverage",
44
+ "coverage.xml",
45
+ "*.orig",
46
+ ]
47
+
48
+
49
+ def _iter_matches(root: Path, patterns: list[str]):
50
+ for pat in patterns:
51
+ yield from root.rglob(pat)
52
+
53
+
54
+ @click.command()
55
+ @click.argument("path", default=".", type=click.Path(exists=True, file_okay=False))
56
+ @click.option("--dry-run", is_flag=True, default=False, help="Show what would be removed without deleting.")
57
+ @click.option("-y", "--yes", is_flag=True, default=False, help="Skip confirmation prompt.")
58
+ @click.option("--include-venv", is_flag=True, default=False, help="Also remove .venv/ directory.")
59
+ def clean(path, dry_run, yes, include_venv):
60
+ """
61
+ Remove __pycache__, build artifacts, .DS_Store, and other junk.
62
+
63
+ \b
64
+ Examples:
65
+ devkit clean # clean current directory
66
+ devkit clean ./my-project # clean a specific directory
67
+ devkit clean --dry-run # preview only
68
+ """
69
+ root = Path(path).resolve()
70
+ to_remove: list[Path] = []
71
+
72
+ dir_patterns = list(CLEAN_DIRS)
73
+ if include_venv:
74
+ dir_patterns.append(".venv")
75
+
76
+ for item in _iter_matches(root, dir_patterns):
77
+ if item.is_dir():
78
+ to_remove.append(item)
79
+
80
+ for item in _iter_matches(root, CLEAN_FILES):
81
+ if item.is_file():
82
+ to_remove.append(item)
83
+
84
+ # De-duplicate: skip children of already-queued dirs
85
+ to_remove_set = sorted(set(to_remove), key=lambda p: len(p.parts))
86
+ final: list[Path] = []
87
+ queued_dirs: list[Path] = []
88
+ for item in to_remove_set:
89
+ if any(item.is_relative_to(d) for d in queued_dirs):
90
+ continue
91
+ final.append(item)
92
+ if item.is_dir():
93
+ queued_dirs.append(item)
94
+
95
+ if not final:
96
+ console.print("[green]✔[/green] Nothing to clean.")
97
+ return
98
+
99
+ table = Table(title=f"Items to remove ({len(final)})", show_lines=False)
100
+ table.add_column("Type", style="dim", width=5)
101
+ table.add_column("Path", style="cyan")
102
+
103
+ for item in final:
104
+ kind = "[blue]DIR[/blue]" if item.is_dir() else "FILE"
105
+ table.add_row(kind, str(item.relative_to(root)))
106
+
107
+ console.print(table)
108
+
109
+ if dry_run:
110
+ console.print("[yellow]Dry-run mode — nothing was deleted.[/yellow]")
111
+ return
112
+
113
+ if not yes:
114
+ click.confirm(f"Delete {len(final)} item(s)?", abort=True)
115
+
116
+ removed_count = 0
117
+ errors = 0
118
+ for item in final:
119
+ try:
120
+ if item.is_dir():
121
+ shutil.rmtree(item)
122
+ else:
123
+ item.unlink()
124
+ removed_count += 1
125
+ except Exception as exc:
126
+ console.print(f"[red]Error removing {item}: {exc}[/red]")
127
+ errors += 1
128
+
129
+ console.print(f"[green]✔[/green] Removed [bold]{removed_count}[/bold] item(s)." +
130
+ (f" [red]{errors} error(s).[/red]" if errors else ""))
@@ -0,0 +1,156 @@
1
+ """devkit env — List, export, diff, and set environment variables."""
2
+
3
+ import os
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import click
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ console = Console()
12
+
13
+
14
+ def _load_dotenv(path: Path) -> dict[str, str]:
15
+ """Parse a .env file into a dict."""
16
+ result = {}
17
+ for line in path.read_text(encoding="utf-8").splitlines():
18
+ line = line.strip()
19
+ if not line or line.startswith("#"):
20
+ continue
21
+ if "=" in line:
22
+ key, _, val = line.partition("=")
23
+ result[key.strip()] = val.strip().strip('"').strip("'")
24
+ return result
25
+
26
+
27
+ @click.group()
28
+ def env():
29
+ """Manage and inspect environment variables."""
30
+ pass
31
+
32
+
33
+ @env.command("list")
34
+ @click.option("--filter", "filter_str", default=None, metavar="KEYWORD",
35
+ help="Only show vars containing this keyword (case-insensitive).")
36
+ @click.option("--json", "as_json", is_flag=True, default=False, help="Output as JSON.")
37
+ def env_list(filter_str, as_json):
38
+ """
39
+ List all current environment variables.
40
+
41
+ \b
42
+ Examples:
43
+ devkit env list
44
+ devkit env list --filter PATH
45
+ devkit env list --json
46
+ """
47
+ vars_dict = dict(os.environ)
48
+
49
+ if filter_str:
50
+ keyword = filter_str.lower()
51
+ vars_dict = {k: v for k, v in vars_dict.items()
52
+ if keyword in k.lower() or keyword in v.lower()}
53
+
54
+ if as_json:
55
+ click.echo(json.dumps(vars_dict, indent=2))
56
+ return
57
+
58
+ table = Table(title=f"Environment Variables ({len(vars_dict)})")
59
+ table.add_column("Variable", style="cyan", no_wrap=True)
60
+ table.add_column("Value", style="green", overflow="fold")
61
+
62
+ for key in sorted(vars_dict):
63
+ table.add_row(key, vars_dict[key])
64
+
65
+ console.print(table)
66
+
67
+
68
+ @env.command("export")
69
+ @click.argument("output", default=".env", metavar="OUTPUT_FILE")
70
+ @click.option("--filter", "filter_str", default=None, metavar="KEYWORD",
71
+ help="Only export vars containing this keyword.")
72
+ @click.option("--format", "fmt",
73
+ type=click.Choice(["dotenv", "json", "shell", "powershell", "cmd"]),
74
+ default="dotenv", show_default=True,
75
+ help="Output format. 'shell'=bash export, 'powershell'=$env: syntax, 'cmd'=set syntax.")
76
+ def env_export(output, filter_str, fmt):
77
+ """
78
+ Export environment variables to a file.
79
+
80
+ \b
81
+ Examples:
82
+ devkit env export # exports all to .env
83
+ devkit env export prod.env --filter AWS
84
+ devkit env export vars.json --format json
85
+ devkit env export vars.ps1 --format powershell # Windows PowerShell
86
+ devkit env export vars.bat --format cmd # Windows CMD
87
+ """
88
+ vars_dict = dict(os.environ)
89
+ if filter_str:
90
+ keyword = filter_str.lower()
91
+ vars_dict = {k: v for k, v in vars_dict.items()
92
+ if keyword in k.lower()}
93
+
94
+ out = Path(output)
95
+
96
+ if fmt == "dotenv":
97
+ lines = [f'{k}="{v}"' for k, v in sorted(vars_dict.items())]
98
+ out.write_text("\n".join(lines) + "\n", encoding="utf-8")
99
+ elif fmt == "json":
100
+ out.write_text(json.dumps(vars_dict, indent=2), encoding="utf-8")
101
+ elif fmt == "shell":
102
+ lines = [f"export {k}={json.dumps(v)}" for k, v in sorted(vars_dict.items())]
103
+ out.write_text("\n".join(lines) + "\n", encoding="utf-8")
104
+ elif fmt == "powershell":
105
+ lines = [f'$env:{k} = {json.dumps(v)}' for k, v in sorted(vars_dict.items())]
106
+ out.write_text("\n".join(lines) + "\n", encoding="utf-8")
107
+ elif fmt == "cmd":
108
+ # Values with special CMD characters are quoted; newlines replaced with space
109
+ lines = [f"set {k}={v.replace(chr(10), ' ')}" for k, v in sorted(vars_dict.items())]
110
+ out.write_text("\n".join(lines) + "\n", encoding="utf-8")
111
+
112
+ console.print(f"[green]✔[/green] Exported [bold]{len(vars_dict)}[/bold] variables to [cyan]{out}[/cyan]")
113
+
114
+
115
+ @env.command("diff")
116
+ @click.argument("file_a", type=click.Path(exists=True))
117
+ @click.argument("file_b", type=click.Path(exists=True))
118
+ def env_diff(file_a, file_b):
119
+ """
120
+ Diff two .env files and show what changed.
121
+
122
+ \b
123
+ Example:
124
+ devkit env diff .env .env.production
125
+ """
126
+ a = _load_dotenv(Path(file_a))
127
+ b = _load_dotenv(Path(file_b))
128
+
129
+ all_keys = sorted(set(a) | set(b))
130
+
131
+ table = Table(title=f"Diff: {file_a} → {file_b}")
132
+ table.add_column("Key", style="cyan")
133
+ table.add_column(Path(file_a).name, style="dim")
134
+ table.add_column(Path(file_b).name, style="bold")
135
+ table.add_column("Status", style="bold")
136
+
137
+ changed = 0
138
+ for key in all_keys:
139
+ va = a.get(key)
140
+ vb = b.get(key)
141
+ if va == vb:
142
+ continue
143
+ if va is None:
144
+ status = "[green]+added[/green]"
145
+ elif vb is None:
146
+ status = "[red]-removed[/red]"
147
+ else:
148
+ status = "[yellow]~changed[/yellow]"
149
+ table.add_row(key, va or "—", vb or "—", status)
150
+ changed += 1
151
+
152
+ if changed == 0:
153
+ console.print("[green]✔[/green] Files are identical.")
154
+ else:
155
+ console.print(table)
156
+ console.print(f"[dim]{changed} difference(s)[/dim]")
@@ -0,0 +1,122 @@
1
+ """devkit find — Fast file search with filters."""
2
+
3
+ import os
4
+ import stat
5
+ import time
6
+ from datetime import datetime, timedelta
7
+ from pathlib import Path
8
+
9
+ import click
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+
13
+ console = Console()
14
+
15
+
16
+ def _bytes_to_human(n: int) -> str:
17
+ for unit in ("B", "KB", "MB", "GB"):
18
+ if n < 1024:
19
+ return f"{n:.1f} {unit}"
20
+ n /= 1024
21
+ return f"{n:.1f} TB"
22
+
23
+
24
+ def _parse_size(s: str) -> int:
25
+ """Parse size strings like '10kb', '2mb', '500' (bytes)."""
26
+ s = s.strip().lower()
27
+ multipliers = {"kb": 1024, "mb": 1024**2, "gb": 1024**3, "b": 1}
28
+ for suffix, mult in multipliers.items():
29
+ if s.endswith(suffix):
30
+ return int(float(s[: -len(suffix)]) * mult)
31
+ return int(s)
32
+
33
+
34
+ @click.command()
35
+ @click.argument("pattern", default="*")
36
+ @click.option("-d", "--dir", "search_dir", default=".", show_default=True,
37
+ type=click.Path(exists=True, file_okay=False),
38
+ help="Root directory to search.")
39
+ @click.option("-e", "--ext", multiple=True,
40
+ help="Filter by extension(s), e.g. -e py -e js")
41
+ @click.option("--min-size", default=None, help="Minimum file size, e.g. 10kb.")
42
+ @click.option("--max-size", default=None, help="Maximum file size, e.g. 5mb.")
43
+ @click.option("--newer-than", default=None, metavar="DAYS",
44
+ help="Files modified within last N days.")
45
+ @click.option("--older-than", default=None, metavar="DAYS",
46
+ help="Files modified more than N days ago.")
47
+ @click.option("-l", "--limit", default=100, show_default=True,
48
+ help="Maximum results to display.")
49
+ @click.option("--dirs-only", is_flag=True, default=False,
50
+ help="Match directories instead of files.")
51
+ def find(pattern, search_dir, ext, min_size, max_size, newer_than, older_than, limit, dirs_only):
52
+ """
53
+ Search for files by name pattern with optional filters.
54
+
55
+ \b
56
+ Examples:
57
+ devkit find "*.py"
58
+ devkit find "config" -e toml -e ini
59
+ devkit find "*" --min-size 1mb --newer-than 7
60
+ devkit find --dirs-only "src"
61
+ """
62
+ root = Path(search_dir).resolve()
63
+ min_bytes = _parse_size(min_size) if min_size else None
64
+ max_bytes = _parse_size(max_size) if max_size else None
65
+ newer_cutoff = (datetime.now() - timedelta(days=int(newer_than))).timestamp() if newer_than else None
66
+ older_cutoff = (datetime.now() - timedelta(days=int(older_than))).timestamp() if older_than else None
67
+
68
+ table = Table(title=f"Search: [cyan]{pattern}[/cyan] in [dim]{root}[/dim]",
69
+ show_lines=False, expand=False)
70
+ table.add_column("#", style="dim", width=4)
71
+ table.add_column("Path", style="cyan")
72
+ if not dirs_only:
73
+ table.add_column("Size", justify="right", style="green")
74
+ table.add_column("Modified", style="dim")
75
+
76
+ results: list[Path] = []
77
+
78
+ for item in root.rglob(pattern):
79
+ if dirs_only and not item.is_dir():
80
+ continue
81
+ if not dirs_only and not item.is_file():
82
+ continue
83
+
84
+ if ext and item.suffix.lstrip(".").lower() not in [e.lstrip(".").lower() for e in ext]:
85
+ continue
86
+
87
+ try:
88
+ st = item.stat()
89
+ except OSError:
90
+ continue
91
+
92
+ if not dirs_only:
93
+ if min_bytes is not None and st.st_size < min_bytes:
94
+ continue
95
+ if max_bytes is not None and st.st_size > max_bytes:
96
+ continue
97
+
98
+ mtime = st.st_mtime
99
+ if newer_cutoff and mtime < newer_cutoff:
100
+ continue
101
+ if older_cutoff and mtime > older_cutoff:
102
+ continue
103
+
104
+ results.append(item)
105
+ if len(results) >= limit:
106
+ break
107
+
108
+ if not results:
109
+ console.print("[yellow]No matches found.[/yellow]")
110
+ return
111
+
112
+ for i, item in enumerate(results, 1):
113
+ rel = item.relative_to(root)
114
+ if dirs_only:
115
+ table.add_row(str(i), str(rel))
116
+ else:
117
+ st = item.stat()
118
+ mtime_str = datetime.fromtimestamp(st.st_mtime).strftime("%Y-%m-%d %H:%M")
119
+ table.add_row(str(i), str(rel), _bytes_to_human(st.st_size), mtime_str)
120
+
121
+ console.print(table)
122
+ console.print(f"[dim]{len(results)} result(s) — limit {limit}[/dim]")
@@ -0,0 +1,119 @@
1
+ """devkit info — Display system, Python, and environment information."""
2
+
3
+ import os
4
+ import platform
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import click
10
+ import psutil
11
+ from rich.console import Console
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+ from rich.columns import Columns
15
+
16
+ console = Console()
17
+
18
+
19
+ def _bytes_to_human(n: int) -> str:
20
+ for unit in ("B", "KB", "MB", "GB", "TB"):
21
+ if n < 1024:
22
+ return f"{n:.1f} {unit}"
23
+ n /= 1024
24
+ return f"{n:.1f} PB"
25
+
26
+
27
+ def _detect_env() -> tuple[str, str]:
28
+ """Return (env_type, env_path)."""
29
+ conda = os.environ.get("CONDA_DEFAULT_ENV")
30
+ if conda:
31
+ return "conda", conda
32
+ venv = os.environ.get("VIRTUAL_ENV")
33
+ if venv:
34
+ return "venv", venv
35
+ return "none", "—"
36
+
37
+
38
+ @click.command()
39
+ @click.option("--json", "as_json", is_flag=True, default=False, help="Output as JSON.")
40
+ def info(as_json):
41
+ """
42
+ Show system info: OS, Python, CPU, memory, disk, and active environment.
43
+
44
+ \b
45
+ Examples:
46
+ devkit info
47
+ devkit info --json
48
+ """
49
+ # --- Gather data ---
50
+ uname = platform.uname()
51
+ vm = psutil.virtual_memory()
52
+ disk = psutil.disk_usage(Path.cwd().anchor)
53
+ cpu_count = psutil.cpu_count(logical=True)
54
+ cpu_phys = psutil.cpu_count(logical=False)
55
+ cpu_freq = psutil.cpu_freq()
56
+ env_type, env_path = _detect_env()
57
+
58
+ data = {
59
+ "os": f"{uname.system} {uname.release}",
60
+ "machine": uname.machine,
61
+ "hostname": uname.node,
62
+ "python_version": sys.version.split()[0],
63
+ "python_impl": platform.python_implementation(),
64
+ "python_path": sys.executable,
65
+ "cpu_logical": cpu_count,
66
+ "cpu_physical": cpu_phys,
67
+ "cpu_freq_mhz": f"{cpu_freq.current:.0f} MHz" if cpu_freq else "n/a",
68
+ "mem_total": _bytes_to_human(vm.total),
69
+ "mem_used": _bytes_to_human(vm.used),
70
+ "mem_pct": f"{vm.percent}%",
71
+ "disk_total": _bytes_to_human(disk.total),
72
+ "disk_used": _bytes_to_human(disk.used),
73
+ "disk_pct": f"{disk.percent}%",
74
+ "env_type": env_type,
75
+ "env_path": env_path,
76
+ "cwd": str(Path.cwd()),
77
+ }
78
+
79
+ if as_json:
80
+ import json
81
+ click.echo(json.dumps(data, indent=2))
82
+ return
83
+
84
+ # --- Rich tables ---
85
+ def kv_table(title: str, rows: list[tuple[str, str]]) -> Table:
86
+ t = Table(title=title, show_header=False, box=None, padding=(0, 2))
87
+ t.add_column("Key", style="dim", no_wrap=True)
88
+ t.add_column("Value", style="bold")
89
+ for k, v in rows:
90
+ t.add_row(k, v)
91
+ return t
92
+
93
+ sys_table = kv_table("System", [
94
+ ("OS", data["os"]),
95
+ ("Arch", data["machine"]),
96
+ ("Hostname", data["hostname"]),
97
+ ("CWD", data["cwd"]),
98
+ ])
99
+
100
+ py_table = kv_table("Python", [
101
+ ("Version", data["python_version"]),
102
+ ("Impl", data["python_impl"]),
103
+ ("Executable", data["python_path"]),
104
+ ("Env Type", data["env_type"]),
105
+ ("Env Path", data["env_path"]),
106
+ ])
107
+
108
+ hw_table = kv_table("Hardware", [
109
+ ("CPU (logical)", str(data["cpu_logical"])),
110
+ ("CPU (physical)", str(data["cpu_physical"])),
111
+ ("CPU Freq", data["cpu_freq_mhz"]),
112
+ ("RAM Total", data["mem_total"]),
113
+ ("RAM Used", f"{data['mem_used']} ({data['mem_pct']})"),
114
+ ("Disk Total", data["disk_total"]),
115
+ ("Disk Used", f"{data['disk_used']} ({data['disk_pct']})"),
116
+ ])
117
+
118
+ console.print(Panel("[bold cyan]devkit info[/bold cyan]", expand=False))
119
+ console.print(Columns([sys_table, py_table, hw_table], equal=False, expand=False))