devctl 1.2.2__py3-none-any.whl → 1.2.4__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.
- devctl/commands/init.py +25 -0
- devctl/generators/agent/__init__.py +7 -0
- devctl/generators/agent/generator.py +269 -0
- devctl/generators/agent/templates/AGENTS.md.j2 +87 -0
- devctl/generators/agent/templates/CLAUDE.md.j2 +8 -0
- devctl/generators/angular/templates/config/proxy.conf.json.j2 +6 -0
- devctl/generators/nodejs/generator.py +12 -0
- devctl/generators/nodejs/templates/config/index.ts.j2 +2 -1
- devctl/generators/nodejs/templates/resource/controller.ts.j2 +1 -1
- devctl/generators/react/generator.py +10 -1
- devctl/generators/svelte/generator.py +3 -3
- devctl/generators/vue/generator.py +11 -2
- devctl/orchestrator/config_builder.py +3 -1
- devctl/tui/app.py +19 -5
- devctl/tui/app.tcss +353 -0
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/METADATA +9 -4
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/RECORD +21 -16
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/WHEEL +1 -1
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/entry_points.txt +0 -0
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/licenses/LICENSE +0 -0
- {devctl-1.2.2.dist-info → devctl-1.2.4.dist-info}/top_level.txt +0 -0
devctl/commands/init.py
CHANGED
|
@@ -6,6 +6,7 @@ Django, Svelte, and Go.
|
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
|
+
from devctl.generators.agent import generate_agent_context
|
|
9
10
|
from devctl.generators.angular import generate_angular_boilerplate
|
|
10
11
|
from devctl.generators.django import generate_django_boilerplate
|
|
11
12
|
from devctl.generators.fastapi import generate_fastapi_boilerplate
|
|
@@ -46,6 +47,7 @@ def init_spring(
|
|
|
46
47
|
|
|
47
48
|
if success_download:
|
|
48
49
|
generate_config(name, db_type=db, custom_port=port)
|
|
50
|
+
generate_agent_context()
|
|
49
51
|
typer.secho("\nSpring project ready!", fg=typer.colors.GREEN)
|
|
50
52
|
|
|
51
53
|
|
|
@@ -61,6 +63,7 @@ def init_angular(name: str):
|
|
|
61
63
|
success = generate_angular_boilerplate(name)
|
|
62
64
|
|
|
63
65
|
if success:
|
|
66
|
+
generate_agent_context()
|
|
64
67
|
typer.secho("\nAngular project ready!", fg=typer.colors.GREEN)
|
|
65
68
|
|
|
66
69
|
|
|
@@ -75,6 +78,7 @@ def init_vue(name: str):
|
|
|
75
78
|
success = generate_vue_boilerplate(name)
|
|
76
79
|
|
|
77
80
|
if success:
|
|
81
|
+
generate_agent_context()
|
|
78
82
|
typer.secho("\nVue.js project ready!", fg=typer.colors.GREEN)
|
|
79
83
|
|
|
80
84
|
|
|
@@ -89,6 +93,7 @@ def init_nest(name: str):
|
|
|
89
93
|
success = generate_nest_boilerplate(name)
|
|
90
94
|
|
|
91
95
|
if success:
|
|
96
|
+
generate_agent_context()
|
|
92
97
|
typer.secho("\nNestJS project ready!", fg=typer.colors.GREEN)
|
|
93
98
|
|
|
94
99
|
|
|
@@ -103,6 +108,7 @@ def init_nodejs(name: str):
|
|
|
103
108
|
success = generate_nodejs_boilerplate(name)
|
|
104
109
|
|
|
105
110
|
if success:
|
|
111
|
+
generate_agent_context()
|
|
106
112
|
typer.secho("\nNodeJS project ready!", fg=typer.colors.GREEN)
|
|
107
113
|
|
|
108
114
|
|
|
@@ -117,6 +123,7 @@ def init_react(name: str):
|
|
|
117
123
|
success = generate_react_boilerplate(name)
|
|
118
124
|
|
|
119
125
|
if success:
|
|
126
|
+
generate_agent_context()
|
|
120
127
|
typer.secho("\nReactJS project ready!", fg=typer.colors.GREEN)
|
|
121
128
|
|
|
122
129
|
|
|
@@ -131,6 +138,7 @@ def init_nextjs(name: str):
|
|
|
131
138
|
success = generate_nextjs_boilerplate(name)
|
|
132
139
|
|
|
133
140
|
if success:
|
|
141
|
+
generate_agent_context()
|
|
134
142
|
typer.secho("\nNextJS project ready!", fg=typer.colors.GREEN)
|
|
135
143
|
|
|
136
144
|
|
|
@@ -145,6 +153,7 @@ def init_fastapi(name: str):
|
|
|
145
153
|
success = generate_fastapi_boilerplate(name)
|
|
146
154
|
|
|
147
155
|
if success:
|
|
156
|
+
generate_agent_context()
|
|
148
157
|
typer.secho("\nFastAPI project ready!", fg=typer.colors.GREEN)
|
|
149
158
|
|
|
150
159
|
|
|
@@ -159,6 +168,7 @@ def init_django(name: str):
|
|
|
159
168
|
success = generate_django_boilerplate(name)
|
|
160
169
|
|
|
161
170
|
if success:
|
|
171
|
+
generate_agent_context()
|
|
162
172
|
typer.secho("\nDjango project ready!", fg=typer.colors.GREEN)
|
|
163
173
|
|
|
164
174
|
|
|
@@ -173,6 +183,7 @@ def init_svelte(name: str):
|
|
|
173
183
|
success = generate_svelte_boilerplate(name)
|
|
174
184
|
|
|
175
185
|
if success:
|
|
186
|
+
generate_agent_context()
|
|
176
187
|
typer.secho("\nSvelte project ready!", fg=typer.colors.GREEN)
|
|
177
188
|
|
|
178
189
|
|
|
@@ -187,4 +198,18 @@ def init_go(name: str):
|
|
|
187
198
|
success = generate_go_boilerplate(name)
|
|
188
199
|
|
|
189
200
|
if success:
|
|
201
|
+
generate_agent_context()
|
|
190
202
|
typer.secho("\nGo project ready!", fg=typer.colors.GREEN)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.command("agent")
|
|
206
|
+
def init_agent():
|
|
207
|
+
"""
|
|
208
|
+
Generates or refreshes AGENTS.md, CLAUDE.md, and .cursorrules for the current project.
|
|
209
|
+
"""
|
|
210
|
+
success = generate_agent_context()
|
|
211
|
+
if success:
|
|
212
|
+
typer.secho("\nAgent context ready!", fg=typer.colors.GREEN)
|
|
213
|
+
else:
|
|
214
|
+
typer.secho("\nFailed to generate agent context.", fg=typer.colors.RED)
|
|
215
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent context generator.
|
|
3
|
+
Produces AGENTS.md, CLAUDE.md, and .cursorrules for AI coding assistants.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Union
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
import yaml
|
|
15
|
+
from jinja2 import Environment, FileSystemLoader
|
|
16
|
+
|
|
17
|
+
from devctl.generators.docker.scaffolder import IGNORED_DIRECTORIES, discover_docker_projects
|
|
18
|
+
|
|
19
|
+
FRAMEWORK_METADATA: Dict[str, Dict[str, Any]] = {
|
|
20
|
+
"spring": {
|
|
21
|
+
"framework": "Spring Boot",
|
|
22
|
+
"category": "backend",
|
|
23
|
+
"default_port": 8080,
|
|
24
|
+
"run_cmd": "./mvnw spring-boot:run",
|
|
25
|
+
"test_cmd": "./mvnw test",
|
|
26
|
+
},
|
|
27
|
+
"fastapi": {
|
|
28
|
+
"framework": "FastAPI",
|
|
29
|
+
"category": "backend",
|
|
30
|
+
"default_port": 8000,
|
|
31
|
+
"run_cmd": "uvicorn main:app --reload",
|
|
32
|
+
"test_cmd": "pytest",
|
|
33
|
+
},
|
|
34
|
+
"django": {
|
|
35
|
+
"framework": "Django",
|
|
36
|
+
"category": "backend",
|
|
37
|
+
"default_port": 8000,
|
|
38
|
+
"run_cmd": "python manage.py runserver",
|
|
39
|
+
"test_cmd": "python manage.py test",
|
|
40
|
+
},
|
|
41
|
+
"nest": {
|
|
42
|
+
"framework": "NestJS",
|
|
43
|
+
"category": "backend",
|
|
44
|
+
"default_port": 3000,
|
|
45
|
+
"run_cmd": "npm run start:dev",
|
|
46
|
+
"test_cmd": "npm test",
|
|
47
|
+
},
|
|
48
|
+
"nodejs": {
|
|
49
|
+
"framework": "NodeJS / Express",
|
|
50
|
+
"category": "backend",
|
|
51
|
+
"default_port": 3000,
|
|
52
|
+
"run_cmd": "npm run dev",
|
|
53
|
+
"test_cmd": "npm test",
|
|
54
|
+
},
|
|
55
|
+
"go": {
|
|
56
|
+
"framework": "Go (Fiber)",
|
|
57
|
+
"category": "backend",
|
|
58
|
+
"default_port": 3000,
|
|
59
|
+
"run_cmd": "go run .",
|
|
60
|
+
"test_cmd": "go test ./...",
|
|
61
|
+
},
|
|
62
|
+
"angular": {
|
|
63
|
+
"framework": "Angular",
|
|
64
|
+
"category": "frontend",
|
|
65
|
+
"default_port": 4200,
|
|
66
|
+
"run_cmd": "npx ng serve",
|
|
67
|
+
"test_cmd": "npm test",
|
|
68
|
+
},
|
|
69
|
+
"vue": {
|
|
70
|
+
"framework": "Vue 3",
|
|
71
|
+
"category": "frontend",
|
|
72
|
+
"default_port": 5173,
|
|
73
|
+
"run_cmd": "npm run dev",
|
|
74
|
+
"test_cmd": "npm test",
|
|
75
|
+
},
|
|
76
|
+
"react": {
|
|
77
|
+
"framework": "React",
|
|
78
|
+
"category": "frontend",
|
|
79
|
+
"default_port": 5173,
|
|
80
|
+
"run_cmd": "npm run dev",
|
|
81
|
+
"test_cmd": "npm test",
|
|
82
|
+
},
|
|
83
|
+
"nextjs": {
|
|
84
|
+
"framework": "Next.js",
|
|
85
|
+
"category": "frontend",
|
|
86
|
+
"default_port": 3000,
|
|
87
|
+
"run_cmd": "npm run dev",
|
|
88
|
+
"test_cmd": "npm test",
|
|
89
|
+
},
|
|
90
|
+
"svelte": {
|
|
91
|
+
"framework": "Svelte",
|
|
92
|
+
"category": "frontend",
|
|
93
|
+
"default_port": 5173,
|
|
94
|
+
"run_cmd": "npm run dev",
|
|
95
|
+
"test_cmd": "npm test",
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _detect_service_port(project_path: Path, kind: str, default_port: int) -> int:
|
|
101
|
+
"""Extract configured service port or fallback to default."""
|
|
102
|
+
if kind == "spring":
|
|
103
|
+
props_path = project_path / "src" / "main" / "resources" / "application.properties"
|
|
104
|
+
if props_path.exists():
|
|
105
|
+
try:
|
|
106
|
+
content = props_path.read_text(encoding="utf-8")
|
|
107
|
+
match = re.search(r"^server\.port\s*=\s*(\d+)", content, re.MULTILINE)
|
|
108
|
+
if match:
|
|
109
|
+
return int(match.group(1))
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
return default_port
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _discover_databases(root: Path) -> List[Dict[str, Any]]:
|
|
116
|
+
"""Scan for docker-compose-db.yml files and extract service configuration."""
|
|
117
|
+
databases: List[Dict[str, Any]] = []
|
|
118
|
+
|
|
119
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
120
|
+
dirnames[:] = [d for d in dirnames if d not in IGNORED_DIRECTORIES]
|
|
121
|
+
if "docker-compose-db.yml" in filenames:
|
|
122
|
+
compose_path = Path(dirpath) / "docker-compose-db.yml"
|
|
123
|
+
try:
|
|
124
|
+
content = yaml.safe_load(compose_path.read_text(encoding="utf-8")) or {}
|
|
125
|
+
services = content.get("services", {})
|
|
126
|
+
for s_name, s_conf in services.items():
|
|
127
|
+
image = str(s_conf.get("image", "unknown"))
|
|
128
|
+
ports = s_conf.get("ports", [])
|
|
129
|
+
host_port = "unknown"
|
|
130
|
+
if ports:
|
|
131
|
+
raw_port = str(ports[0])
|
|
132
|
+
host_port = raw_port.split(":")[0] if ":" in raw_port else raw_port
|
|
133
|
+
|
|
134
|
+
env_vars = s_conf.get("environment", {})
|
|
135
|
+
if isinstance(env_vars, list):
|
|
136
|
+
parsed_env = {}
|
|
137
|
+
for item in env_vars:
|
|
138
|
+
if isinstance(item, str) and "=" in item:
|
|
139
|
+
k, v = item.split("=", 1)
|
|
140
|
+
parsed_env[k.strip()] = v.strip()
|
|
141
|
+
env_vars = parsed_env
|
|
142
|
+
|
|
143
|
+
db_user = (
|
|
144
|
+
env_vars.get("POSTGRES_USER")
|
|
145
|
+
or env_vars.get("MYSQL_USER")
|
|
146
|
+
or env_vars.get("MONGO_INITDB_ROOT_USERNAME")
|
|
147
|
+
or "admin"
|
|
148
|
+
)
|
|
149
|
+
db_password = (
|
|
150
|
+
env_vars.get("POSTGRES_PASSWORD")
|
|
151
|
+
or env_vars.get("MYSQL_PASSWORD")
|
|
152
|
+
or env_vars.get("MONGO_INITDB_ROOT_PASSWORD")
|
|
153
|
+
or "password"
|
|
154
|
+
)
|
|
155
|
+
db_name = (
|
|
156
|
+
env_vars.get("POSTGRES_DB")
|
|
157
|
+
or env_vars.get("MYSQL_DATABASE")
|
|
158
|
+
or env_vars.get("MONGO_INITDB_DATABASE")
|
|
159
|
+
or "default_db"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
image_lower = image.lower()
|
|
163
|
+
if "postgres" in image_lower:
|
|
164
|
+
db_type = "PostgreSQL"
|
|
165
|
+
elif "mysql" in image_lower:
|
|
166
|
+
db_type = "MySQL"
|
|
167
|
+
elif "mongo" in image_lower:
|
|
168
|
+
db_type = "MongoDB"
|
|
169
|
+
else:
|
|
170
|
+
db_type = "Database"
|
|
171
|
+
|
|
172
|
+
rel_compose = compose_path.relative_to(root).as_posix()
|
|
173
|
+
databases.append(
|
|
174
|
+
{
|
|
175
|
+
"service_name": s_name,
|
|
176
|
+
"db_type": db_type,
|
|
177
|
+
"image": image,
|
|
178
|
+
"port": host_port,
|
|
179
|
+
"db_name": db_name,
|
|
180
|
+
"db_user": db_user,
|
|
181
|
+
"db_password": db_password,
|
|
182
|
+
"compose_file": f"./{rel_compose}",
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
return databases
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def generate_agent_context(root_path: Union[str, Path] = ".") -> bool:
|
|
192
|
+
"""
|
|
193
|
+
Scans the repository and generates AGENTS.md, CLAUDE.md, and .cursorrules.
|
|
194
|
+
"""
|
|
195
|
+
root = Path(root_path).resolve()
|
|
196
|
+
if not root.exists() or not root.is_dir():
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
discovered = discover_docker_projects(root)
|
|
201
|
+
except Exception:
|
|
202
|
+
discovered = []
|
|
203
|
+
|
|
204
|
+
services: List[Dict[str, Any]] = []
|
|
205
|
+
backends: List[Dict[str, Any]] = []
|
|
206
|
+
frontends: List[Dict[str, Any]] = []
|
|
207
|
+
|
|
208
|
+
for p in discovered:
|
|
209
|
+
meta = FRAMEWORK_METADATA.get(
|
|
210
|
+
p.kind,
|
|
211
|
+
{
|
|
212
|
+
"framework": p.kind.capitalize(),
|
|
213
|
+
"category": "backend",
|
|
214
|
+
"default_port": 8080,
|
|
215
|
+
"run_cmd": "devctl run",
|
|
216
|
+
"test_cmd": "npm test",
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
port = _detect_service_port(p.path, p.kind, meta["default_port"])
|
|
220
|
+
try:
|
|
221
|
+
rel_path = f"./{p.path.relative_to(root).as_posix()}"
|
|
222
|
+
except ValueError:
|
|
223
|
+
rel_path = f"./{p.name}"
|
|
224
|
+
|
|
225
|
+
service_data = {
|
|
226
|
+
"name": p.name,
|
|
227
|
+
"kind": p.kind,
|
|
228
|
+
"framework": meta["framework"],
|
|
229
|
+
"category": meta["category"],
|
|
230
|
+
"relative_path": rel_path,
|
|
231
|
+
"port": port,
|
|
232
|
+
"run_cmd": meta["run_cmd"],
|
|
233
|
+
"test_cmd": meta["test_cmd"],
|
|
234
|
+
}
|
|
235
|
+
services.append(service_data)
|
|
236
|
+
if meta["category"] == "backend":
|
|
237
|
+
backends.append(service_data)
|
|
238
|
+
else:
|
|
239
|
+
frontends.append(service_data)
|
|
240
|
+
|
|
241
|
+
databases = _discover_databases(root)
|
|
242
|
+
|
|
243
|
+
template_dir = Path(__file__).parent / "templates"
|
|
244
|
+
env = Environment(loader=FileSystemLoader(str(template_dir)), autoescape=False)
|
|
245
|
+
|
|
246
|
+
context = {
|
|
247
|
+
"services": services,
|
|
248
|
+
"backends": backends,
|
|
249
|
+
"frontends": frontends,
|
|
250
|
+
"databases": databases,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
agents_tpl = env.get_template("AGENTS.md.j2")
|
|
255
|
+
claude_tpl = env.get_template("CLAUDE.md.j2")
|
|
256
|
+
cursor_tpl = env.get_template(".cursorrules.j2")
|
|
257
|
+
|
|
258
|
+
(root / "AGENTS.md").write_text(agents_tpl.render(context), encoding="utf-8")
|
|
259
|
+
(root / "CLAUDE.md").write_text(claude_tpl.render(context), encoding="utf-8")
|
|
260
|
+
(root / ".cursorrules").write_text(cursor_tpl.render(context), encoding="utf-8")
|
|
261
|
+
|
|
262
|
+
typer.secho(
|
|
263
|
+
"Agent context generated (AGENTS.md, CLAUDE.md, .cursorrules).",
|
|
264
|
+
fg=typer.colors.CYAN,
|
|
265
|
+
)
|
|
266
|
+
return True
|
|
267
|
+
except Exception as e:
|
|
268
|
+
typer.secho(f"Warning: Failed to generate agent context: {e}", fg=typer.colors.YELLOW)
|
|
269
|
+
return False
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Project Development Guide for AI Agents
|
|
2
|
+
|
|
3
|
+
> This project is orchestrated using `devctl`. All commands, multi-tier processes,
|
|
4
|
+
> and scaffolding workflows must adhere to the boundaries documented below.
|
|
5
|
+
|
|
6
|
+
## 1. Project Architecture & Active Topology
|
|
7
|
+
{% if backends %}
|
|
8
|
+
### Backend Services
|
|
9
|
+
{% for b in backends %}
|
|
10
|
+
- **{{ b.name }}** ({{ b.framework }})
|
|
11
|
+
- Path: `{{ b.relative_path }}`
|
|
12
|
+
- Local Port: `{{ b.port }}`
|
|
13
|
+
- Run Command: `{{ b.run_cmd }}`
|
|
14
|
+
- Test Suite: `{{ b.test_cmd }}`
|
|
15
|
+
{% endfor %}
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if frontends %}
|
|
18
|
+
### Frontend Services
|
|
19
|
+
{% for f in frontends %}
|
|
20
|
+
- **{{ f.name }}** ({{ f.framework }})
|
|
21
|
+
- Path: `{{ f.relative_path }}`
|
|
22
|
+
- Local Dev Port: `{{ f.port }}`
|
|
23
|
+
- Run Command: `{{ f.run_cmd }}`
|
|
24
|
+
- Test Suite: `{{ f.test_cmd }}`
|
|
25
|
+
{% endfor %}
|
|
26
|
+
{% endif %}
|
|
27
|
+
{% if databases %}
|
|
28
|
+
### Databases & Infrastructure
|
|
29
|
+
{% for db in databases %}
|
|
30
|
+
- **Service**: `{{ db.service_name }}`
|
|
31
|
+
- Type: `{{ db.db_type }}`
|
|
32
|
+
- Image: `{{ db.image }}`
|
|
33
|
+
- Host Port: `{{ db.port }}`
|
|
34
|
+
- Database Name: `{{ db.db_name }}`
|
|
35
|
+
- Credentials: User=`{{ db.db_user }}`, Password=`{{ db.db_password }}`
|
|
36
|
+
- Compose File: `{{ db.compose_file }}`
|
|
37
|
+
{% endfor %}
|
|
38
|
+
{% endif %}
|
|
39
|
+
{% if not backends and not frontends and not databases %}
|
|
40
|
+
- Topology: No active microservices detected yet. Run `devctl init <framework> <name>` to add services.
|
|
41
|
+
{% endif %}
|
|
42
|
+
|
|
43
|
+
## 2. Devctl Orchestration & Lifecycle Commands
|
|
44
|
+
|
|
45
|
+
Always use `devctl` to orchestrate services. Do NOT start background daemons, manually detached docker processes, or raw ad-hoc runners unless debugging an isolated failure.
|
|
46
|
+
|
|
47
|
+
- **Start entire stack** (Databases + Backends + Frontends in parallel with unified log streaming):
|
|
48
|
+
```bash
|
|
49
|
+
devctl run
|
|
50
|
+
```
|
|
51
|
+
- **Interactive TUI Dashboard** (System metrics, real-time log inspection, process management):
|
|
52
|
+
```bash
|
|
53
|
+
devctl tui
|
|
54
|
+
```
|
|
55
|
+
- **Vertical Full-Stack Scaffolding** (Generates Entity, DTOs, Repository, Service, Controller, and Frontend client):
|
|
56
|
+
```bash
|
|
57
|
+
devctl add resource <ResourceName> -f "<field1>:<type>,<field2>:<type>"
|
|
58
|
+
```
|
|
59
|
+
- **Containerization & Deployment Scaffolding**:
|
|
60
|
+
```bash
|
|
61
|
+
devctl docker scaffold
|
|
62
|
+
devctl docker generate
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 3. Full-Stack Contract & Coding Conventions
|
|
66
|
+
|
|
67
|
+
When adding or modifying API endpoints or data models:
|
|
68
|
+
- **API Endpoints**: Use standard RESTful patterns with `/api/v1` prefixes.
|
|
69
|
+
- **DTOs / Contracts**:
|
|
70
|
+
- Request payloads must be modeled with `<Entity>Request` DTOs.
|
|
71
|
+
- Response payloads must be modeled with `<Entity>Response` DTOs.
|
|
72
|
+
- **Type Compatibility**:
|
|
73
|
+
- Backend numeric IDs / Long / Integer -> Frontend TypeScript `number`
|
|
74
|
+
- Backend UUID / String -> Frontend TypeScript `string`
|
|
75
|
+
- Backend Instant / LocalDateTime / DateTime -> Frontend TypeScript `string` (ISO 8601 formatted)
|
|
76
|
+
- Backend Boolean -> Frontend TypeScript `boolean`
|
|
77
|
+
- **Synchronization**: Any change to backend DTO fields or endpoints MUST be immediately reflected in the corresponding frontend API client, models, and interfaces.
|
|
78
|
+
|
|
79
|
+
## 4. Agent Operational Guardrails
|
|
80
|
+
|
|
81
|
+
- **Process Management**: Do NOT run detached background processes (`nohup`, `&`, `screen`, `tmux`) that outlive the session. Use `devctl run` for execution.
|
|
82
|
+
- **Testing Before Completion**: Always run the respective test suite for modified services before concluding any task:
|
|
83
|
+
{% for s in services %}
|
|
84
|
+
- `{{ s.name }}`: `{{ s.test_cmd }}`
|
|
85
|
+
{% endfor %}
|
|
86
|
+
- **Docker Compose Boundaries**: Do NOT alter database port mappings in `docker-compose-db.yml` without updating application configuration files in lockstep.
|
|
87
|
+
- **Dependency Checks**: Ensure required tools are available using `devctl` verification conventions before executing framework-specific build scripts.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Claude Code Guidelines
|
|
2
|
+
|
|
3
|
+
This project is managed and orchestrated by `devctl`.
|
|
4
|
+
|
|
5
|
+
- Review [AGENTS.md](AGENTS.md) for complete architecture topology, active services, port allocations, and devctl lifecycle commands.
|
|
6
|
+
- Use `devctl run` to boot services and `devctl add resource` for full-stack CRUD scaffolding.
|
|
7
|
+
- Ensure backend DTOs and frontend TypeScript interfaces remain strictly synchronized.
|
|
8
|
+
- Execute unit and integration tests before finishing tasks.
|
|
@@ -75,6 +75,18 @@ def generate_nodejs_boilerplate(project_name: str) -> bool:
|
|
|
75
75
|
shell=platform.shell_required,
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
# Patch tsconfig.json to disable verbatimModuleSyntax for ES module compatibility
|
|
79
|
+
tsconfig_path = os.path.join(project_path, "tsconfig.json")
|
|
80
|
+
if os.path.exists(tsconfig_path):
|
|
81
|
+
with open(tsconfig_path, "r", encoding="utf-8") as f:
|
|
82
|
+
content = f.read()
|
|
83
|
+
# Replace verbatimModuleSyntax if present or add option
|
|
84
|
+
content = content.replace(
|
|
85
|
+
'"verbatimModuleSyntax": true', '"verbatimModuleSyntax": false'
|
|
86
|
+
)
|
|
87
|
+
with open(tsconfig_path, "w", encoding="utf-8") as f:
|
|
88
|
+
f.write(content)
|
|
89
|
+
|
|
78
90
|
# 4. Create folder structure
|
|
79
91
|
os.makedirs(os.path.join(project_path, "src"), exist_ok=True)
|
|
80
92
|
|
|
@@ -22,7 +22,16 @@ def generate_react_boilerplate(project_name: str) -> bool:
|
|
|
22
22
|
platform = get_platform()
|
|
23
23
|
typer.secho("Scaffolding React project...", fg=typer.colors.CYAN)
|
|
24
24
|
subprocess.run(
|
|
25
|
-
[
|
|
25
|
+
[
|
|
26
|
+
"npm",
|
|
27
|
+
"create",
|
|
28
|
+
"vite@latest",
|
|
29
|
+
safe_name,
|
|
30
|
+
"--",
|
|
31
|
+
"--template",
|
|
32
|
+
"react-ts",
|
|
33
|
+
"--no-interactive",
|
|
34
|
+
],
|
|
26
35
|
check=True,
|
|
27
36
|
shell=platform.shell_required,
|
|
28
37
|
)
|
|
@@ -30,11 +30,11 @@ def generate_svelte_boilerplate(project_name: str) -> bool:
|
|
|
30
30
|
"create",
|
|
31
31
|
safe_name,
|
|
32
32
|
"--template",
|
|
33
|
-
"
|
|
33
|
+
"minimal",
|
|
34
34
|
"--types",
|
|
35
|
-
"
|
|
35
|
+
"ts",
|
|
36
|
+
"--no-add-ons",
|
|
36
37
|
"--no-install",
|
|
37
|
-
"--no-git",
|
|
38
38
|
],
|
|
39
39
|
check=True,
|
|
40
40
|
shell=platform.shell_required,
|
|
@@ -56,7 +56,7 @@ def setup_vue_router(project_path: str):
|
|
|
56
56
|
os.makedirs(router_dir, exist_ok=True)
|
|
57
57
|
|
|
58
58
|
# 3. Jinja2 template rendering
|
|
59
|
-
templates_dir = os.path.join(os.path.dirname(__file__), "
|
|
59
|
+
templates_dir = os.path.join(os.path.dirname(__file__), "templates", "config")
|
|
60
60
|
env = Environment(loader=FileSystemLoader(templates_dir))
|
|
61
61
|
|
|
62
62
|
files_to_generate = {
|
|
@@ -89,7 +89,16 @@ def generate_vue_boilerplate(project_name: str) -> bool:
|
|
|
89
89
|
try:
|
|
90
90
|
typer.secho("Scaffolding Vite project...", fg=typer.colors.CYAN)
|
|
91
91
|
subprocess.run(
|
|
92
|
-
[
|
|
92
|
+
[
|
|
93
|
+
"npm",
|
|
94
|
+
"create",
|
|
95
|
+
"vite@latest",
|
|
96
|
+
safe_name,
|
|
97
|
+
"--",
|
|
98
|
+
"--template",
|
|
99
|
+
"vue-ts",
|
|
100
|
+
"--no-interactive",
|
|
101
|
+
],
|
|
93
102
|
check=True,
|
|
94
103
|
shell=platform.shell_required,
|
|
95
104
|
)
|
|
@@ -13,7 +13,9 @@ def generate_config(project_name: str, db_type: str = "postgres", custom_port: i
|
|
|
13
13
|
"""
|
|
14
14
|
Generates the initial configuration (Docker and Spring properties) for a new project.
|
|
15
15
|
"""
|
|
16
|
-
template_dir = os.path.join(
|
|
16
|
+
template_dir = os.path.join(
|
|
17
|
+
os.path.dirname(__file__), "..", "generators", "spring", "templates"
|
|
18
|
+
)
|
|
17
19
|
env = Environment(loader=FileSystemLoader(template_dir))
|
|
18
20
|
|
|
19
21
|
# Intelligent default port resolution
|
devctl/tui/app.py
CHANGED
|
@@ -55,6 +55,7 @@ from devctl.commands.init import (
|
|
|
55
55
|
generate_svelte_boilerplate,
|
|
56
56
|
generate_vue_boilerplate,
|
|
57
57
|
)
|
|
58
|
+
from devctl.generators.agent import generate_agent_context
|
|
58
59
|
from devctl.generators.docker import discover_docker_projects, scaffold_docker_assets
|
|
59
60
|
from devctl.orchestrator.config_builder import generate_config
|
|
60
61
|
from devctl.orchestrator.process_manager import ProcessManager
|
|
@@ -347,6 +348,7 @@ class DevctlTUI(App):
|
|
|
347
348
|
]
|
|
348
349
|
yield Select(
|
|
349
350
|
frameworks,
|
|
351
|
+
value="spring",
|
|
350
352
|
prompt="Choose target framework",
|
|
351
353
|
id="init-project-framework",
|
|
352
354
|
)
|
|
@@ -456,8 +458,10 @@ class DevctlTUI(App):
|
|
|
456
458
|
if not self.single_panel:
|
|
457
459
|
try:
|
|
458
460
|
select_add = self.query_one("#add-resource-target", Select)
|
|
459
|
-
options = [(f"{p.name} ({p.kind})", p.name) for p in self.projects]
|
|
460
|
-
select_add.options
|
|
461
|
+
options = [(f"{p.name} ({p.kind.upper()})", p.name) for p in self.projects]
|
|
462
|
+
select_add.set_options(options)
|
|
463
|
+
if options and (select_add.value == Select.BLANK or select_add.value is None):
|
|
464
|
+
select_add.value = options[0][1]
|
|
461
465
|
except Exception:
|
|
462
466
|
pass
|
|
463
467
|
|
|
@@ -786,6 +790,7 @@ class DevctlTUI(App):
|
|
|
786
790
|
generate_go_boilerplate(name)
|
|
787
791
|
else:
|
|
788
792
|
print(f"Error: Unknown framework boilerplate '{framework}'")
|
|
793
|
+
generate_agent_context(".")
|
|
789
794
|
print("\nProject initialized successfully!")
|
|
790
795
|
except Exception as e:
|
|
791
796
|
print(f"Error during initialization: {e}", file=f)
|
|
@@ -797,11 +802,20 @@ class DevctlTUI(App):
|
|
|
797
802
|
self.trigger_rescan_and_refresh()
|
|
798
803
|
|
|
799
804
|
def trigger_rescan_and_refresh(self) -> None:
|
|
800
|
-
"""Helper to safely perform rescan and trigger UI updates."""
|
|
801
|
-
|
|
802
|
-
|
|
805
|
+
"""Helper to safely perform rescan and trigger UI updates from background threads."""
|
|
806
|
+
|
|
807
|
+
def _refresh():
|
|
808
|
+
self.perform_rescan()
|
|
809
|
+
self.refresh_ui_elements()
|
|
810
|
+
|
|
811
|
+
self.call_from_thread(_refresh)
|
|
803
812
|
|
|
804
813
|
def on_unmount(self) -> None:
|
|
814
|
+
"""Stops all running background services and containers when quitting TUI."""
|
|
815
|
+
try:
|
|
816
|
+
self.action_stop_all()
|
|
817
|
+
except Exception:
|
|
818
|
+
pass
|
|
805
819
|
self.manager.cleanup_all()
|
|
806
820
|
|
|
807
821
|
|
devctl/tui/app.tcss
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Screen {
|
|
2
|
+
background: $background;
|
|
3
|
+
color: $text;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
Header {
|
|
7
|
+
background: $surface;
|
|
8
|
+
color: $primary;
|
|
9
|
+
text-align: center;
|
|
10
|
+
text-style: bold;
|
|
11
|
+
height: 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Footer {
|
|
15
|
+
background: $background;
|
|
16
|
+
color: $text-muted;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
TabbedContent {
|
|
20
|
+
height: 1fr;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Custom Textual Tabs Styling (htop style) */
|
|
24
|
+
ContentTabs {
|
|
25
|
+
height: 1;
|
|
26
|
+
background: $background;
|
|
27
|
+
border-bottom: solid $border;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ContentTabs Tab {
|
|
31
|
+
height: 1;
|
|
32
|
+
padding: 0 2;
|
|
33
|
+
background: $background;
|
|
34
|
+
color: $text-muted;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ContentTabs Tab:hover {
|
|
38
|
+
background: $surface;
|
|
39
|
+
color: $text;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
ContentTabs Tab.--active {
|
|
43
|
+
background: $primary;
|
|
44
|
+
color: $background;
|
|
45
|
+
text-style: bold;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* TabPane and container rules */
|
|
49
|
+
TabPane {
|
|
50
|
+
padding: 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dashboard-container {
|
|
54
|
+
padding: 0 2;
|
|
55
|
+
layout: vertical;
|
|
56
|
+
overflow-y: auto;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.dashboard-title {
|
|
60
|
+
text-style: bold;
|
|
61
|
+
color: $primary;
|
|
62
|
+
margin-top: 1;
|
|
63
|
+
margin-bottom: 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.dashboard-card {
|
|
67
|
+
background: $background;
|
|
68
|
+
border: solid $border;
|
|
69
|
+
margin-bottom: 1;
|
|
70
|
+
padding: 0 2;
|
|
71
|
+
layout: vertical;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.framework-item {
|
|
75
|
+
margin-bottom: 0;
|
|
76
|
+
padding: 0 1;
|
|
77
|
+
border-left: solid $success;
|
|
78
|
+
background: $background;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Services Control Tab Layout */
|
|
82
|
+
.services-container {
|
|
83
|
+
height: 1fr;
|
|
84
|
+
width: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.services-list-panel {
|
|
88
|
+
background: $background;
|
|
89
|
+
border-right: solid $border;
|
|
90
|
+
padding: 0 1;
|
|
91
|
+
width: 7fr;
|
|
92
|
+
height: 100%;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#services-list {
|
|
96
|
+
height: 1fr;
|
|
97
|
+
border: solid $border;
|
|
98
|
+
margin-top: 1;
|
|
99
|
+
margin-bottom: 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.logs-panel {
|
|
103
|
+
background: $background;
|
|
104
|
+
padding: 0 1;
|
|
105
|
+
layout: vertical;
|
|
106
|
+
width: 9fr;
|
|
107
|
+
height: 100%;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.logs-title-bar {
|
|
111
|
+
text-style: bold;
|
|
112
|
+
color: $text-muted;
|
|
113
|
+
border-bottom: solid $border;
|
|
114
|
+
padding-bottom: 0;
|
|
115
|
+
margin-bottom: 1;
|
|
116
|
+
height: 1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.logs-view {
|
|
120
|
+
background: $background;
|
|
121
|
+
color: $text;
|
|
122
|
+
height: 1fr;
|
|
123
|
+
border: solid $border;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.service-item-row {
|
|
127
|
+
padding: 0 1;
|
|
128
|
+
margin-bottom: 0;
|
|
129
|
+
background: $background;
|
|
130
|
+
border: none;
|
|
131
|
+
layout: horizontal;
|
|
132
|
+
height: 1;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.service-item-row:hover {
|
|
136
|
+
background: $surface;
|
|
137
|
+
color: $text;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.service-item-row.selected {
|
|
141
|
+
background: $accent;
|
|
142
|
+
color: $background;
|
|
143
|
+
text-style: bold;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.service-item-row .status-label {
|
|
147
|
+
text-style: bold;
|
|
148
|
+
margin-left: 2;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.service-item-row .metric-label {
|
|
152
|
+
margin-left: 2;
|
|
153
|
+
color: $text-muted;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* Docker and Scaffolding Forms */
|
|
157
|
+
.form-container {
|
|
158
|
+
padding: 0 2;
|
|
159
|
+
layout: vertical;
|
|
160
|
+
overflow-y: auto;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.form-group {
|
|
164
|
+
margin-top: 1;
|
|
165
|
+
margin-bottom: 1;
|
|
166
|
+
layout: vertical;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.form-label {
|
|
170
|
+
text-style: bold;
|
|
171
|
+
color: $text-muted;
|
|
172
|
+
margin-bottom: 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* CLI style Inputs: 1 cell tall, no border, colored bg */
|
|
176
|
+
.form-input {
|
|
177
|
+
background: $surface;
|
|
178
|
+
color: $text;
|
|
179
|
+
height: 1;
|
|
180
|
+
border: none;
|
|
181
|
+
padding: 0 1;
|
|
182
|
+
width: 60;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.form-input:focus {
|
|
186
|
+
background: $accent;
|
|
187
|
+
color: $background;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* Select Dropdown: 1 cell tall, no border */
|
|
191
|
+
Select {
|
|
192
|
+
border: none;
|
|
193
|
+
width: 60;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
SelectCurrent {
|
|
197
|
+
height: 1;
|
|
198
|
+
border: none;
|
|
199
|
+
background: $surface;
|
|
200
|
+
padding: 0 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
SelectCurrent:focus {
|
|
204
|
+
background: $accent;
|
|
205
|
+
color: $background;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.form-checkbox {
|
|
209
|
+
margin-top: 0;
|
|
210
|
+
margin-bottom: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* CLI style Buttons: 1 cell tall, highlighted bg, no border */
|
|
214
|
+
Button {
|
|
215
|
+
background: $surface;
|
|
216
|
+
color: $text;
|
|
217
|
+
height: 1;
|
|
218
|
+
border: none;
|
|
219
|
+
padding: 0 2;
|
|
220
|
+
min-width: 8;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Button:hover {
|
|
224
|
+
background: $accent;
|
|
225
|
+
color: $background;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Submit Button Specifics */
|
|
229
|
+
.submit-button {
|
|
230
|
+
background: $primary;
|
|
231
|
+
color: $background;
|
|
232
|
+
text-style: bold;
|
|
233
|
+
margin-top: 1;
|
|
234
|
+
margin-bottom: 1;
|
|
235
|
+
width: 30;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.submit-button:hover {
|
|
239
|
+
background: $success;
|
|
240
|
+
color: $background;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.form-container Horizontal {
|
|
244
|
+
height: 1;
|
|
245
|
+
margin-top: 1;
|
|
246
|
+
margin-bottom: 1;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.form-container Horizontal Button {
|
|
250
|
+
width: 30;
|
|
251
|
+
margin-right: 2;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.form-output-panel {
|
|
255
|
+
background: $background;
|
|
256
|
+
border: solid $border;
|
|
257
|
+
padding: 1;
|
|
258
|
+
height: 15;
|
|
259
|
+
color: $success;
|
|
260
|
+
overflow-y: scroll;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Toolbars inside Services and Single Panel */
|
|
264
|
+
#service-control-toolbar, #service-control-bulk-toolbar {
|
|
265
|
+
height: 1;
|
|
266
|
+
margin-top: 1;
|
|
267
|
+
margin-bottom: 1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#service-control-toolbar Button, #service-control-bulk-toolbar Button {
|
|
271
|
+
width: 1fr;
|
|
272
|
+
min-width: 0;
|
|
273
|
+
margin-right: 1;
|
|
274
|
+
height: 1;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#lbl-srv-controls {
|
|
278
|
+
margin-top: 1;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#spring-db-group {
|
|
282
|
+
margin-top: 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Single Panel Layout Styles */
|
|
286
|
+
#single-container {
|
|
287
|
+
height: 1fr;
|
|
288
|
+
width: 100%;
|
|
289
|
+
layout: vertical;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
#single-top-row {
|
|
293
|
+
height: 7;
|
|
294
|
+
width: 100%;
|
|
295
|
+
border-bottom: solid $border;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#single-system-info {
|
|
299
|
+
width: 6fr;
|
|
300
|
+
height: 100%;
|
|
301
|
+
border-right: solid $border;
|
|
302
|
+
padding: 0 2;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#single-db-summary {
|
|
306
|
+
width: 4fr;
|
|
307
|
+
height: 100%;
|
|
308
|
+
padding: 0 2;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
#single-bottom-row {
|
|
312
|
+
height: 1fr;
|
|
313
|
+
width: 100%;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#single-services-panel {
|
|
317
|
+
width: 7fr;
|
|
318
|
+
height: 100%;
|
|
319
|
+
border-right: solid $border;
|
|
320
|
+
padding: 0 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
#single-logs-panel {
|
|
324
|
+
width: 9fr;
|
|
325
|
+
height: 100%;
|
|
326
|
+
padding: 0 1;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.section-title {
|
|
330
|
+
text-style: bold;
|
|
331
|
+
color: $primary;
|
|
332
|
+
margin-bottom: 0;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/* Navigation Helper Bar Styles */
|
|
336
|
+
#top-nav-helper {
|
|
337
|
+
height: 1;
|
|
338
|
+
background: $surface;
|
|
339
|
+
layout: horizontal;
|
|
340
|
+
align: center middle;
|
|
341
|
+
width: 100%;
|
|
342
|
+
margin-bottom: 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.nav-arrow {
|
|
346
|
+
text-style: bold;
|
|
347
|
+
color: $primary;
|
|
348
|
+
padding: 0 2;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
#nav-hint-text {
|
|
352
|
+
color: $text;
|
|
353
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devctl
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Local development orchestrator for Spring Boot, Angular, and Vue.js
|
|
5
5
|
Author: Youssef Fellah
|
|
6
6
|
License: MIT License
|
|
@@ -29,7 +29,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
29
29
|
Classifier: License :: OSI Approved :: MIT License
|
|
30
30
|
Classifier: Operating System :: OS Independent
|
|
31
31
|
Classifier: Topic :: Software Development :: Build Tools
|
|
32
|
-
Requires-Python: >=3.
|
|
32
|
+
Requires-Python: >=3.10
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
34
34
|
License-File: LICENSE
|
|
35
35
|
Requires-Dist: typer>=0.12.3
|
|
@@ -64,6 +64,9 @@ containerization across Linux, macOS, and Windows.
|
|
|
64
64
|
- **Interactive Dashboard**: Real-time system monitoring (CPU/RAM/Disk) and service orchestration with side-by-side log streaming (`devctl tui`).
|
|
65
65
|
- **Security by Default**: Inject standardized JWT authentication and security
|
|
66
66
|
configurations into new projects.
|
|
67
|
+
- **AI Agent Context Generation**: Automatically creates and updates `AGENTS.md`,
|
|
68
|
+
`CLAUDE.md`, and `.cursorrules` on project initialization, aligning coding agents
|
|
69
|
+
(Claude Code, Cursor, Copilot) with active ports, commands, and contract conventions.
|
|
67
70
|
|
|
68
71
|
## Interactive Dashboard (TUI)
|
|
69
72
|
|
|
@@ -153,5 +156,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
153
156
|
for details.
|
|
154
157
|
|
|
155
158
|
---
|
|
156
|
-
|
|
157
|
-
|
|
159
|
+
|
|
160
|
+
## Author
|
|
161
|
+
|
|
162
|
+
- **Author:** Youssef Fellah ([ind4x_](https://github.com/ind4x))
|
|
@@ -4,16 +4,20 @@ devctl/commands/__init__.py,sha256=EZuqGRLRdPpMri5WHmiyhmz8aLs08R1tDQ4FtwMLqMQ,4
|
|
|
4
4
|
devctl/commands/add.py,sha256=Xre6E3jOxdTwGf3OiMwFIc-o-IjyWk5n4f2BYNMVQC8,6753
|
|
5
5
|
devctl/commands/deploy.py,sha256=lzpk1ZcCMxLiMisg9E43B1G9Fhjbklxlbnk3vegm8Us,1688
|
|
6
6
|
devctl/commands/docker.py,sha256=J9Y7Wxnqc_I2EPf-0BMnJngWUJgKJ3BCbNolvGCGASQ,1813
|
|
7
|
-
devctl/commands/init.py,sha256=
|
|
7
|
+
devctl/commands/init.py,sha256=w4ZpUy05Mcu60qfb9P5YdZI-DU-KmjhN9eCkVyy-_QY,6799
|
|
8
8
|
devctl/commands/run.py,sha256=BHqMV0G1AKgwxErVFxcG5QALn-zkQJUT2sozqPA_Cbo,2250
|
|
9
9
|
devctl/commands/tui.py,sha256=HONll0HbHAvP1cdqcceRSUneHjCZj06Jhht5LUMP414,829
|
|
10
10
|
devctl/generators/__init__.py,sha256=Wj-CryHTG5PyR8PEv-Z02wvX_qW2n7GVaE1rfwWSFmo,68
|
|
11
|
+
devctl/generators/agent/__init__.py,sha256=zu5vLCMWMWSfqcAD52MHe4i_ZxtKhhwIbTZZbfxLjIE,149
|
|
12
|
+
devctl/generators/agent/generator.py,sha256=S0KcCi1w-0YYFJtcx_7QmswD1upLuKqB_t5cmpDHiS8,9079
|
|
13
|
+
devctl/generators/agent/templates/AGENTS.md.j2,sha256=oRElhsnsdipkocwI5hZSE30AL_mL_P6Qv0BoCL7DlRI,3651
|
|
14
|
+
devctl/generators/agent/templates/CLAUDE.md.j2,sha256=dEAI8AY7M4CN72e3w0gTQ_DhgC5YNbgjrGa39tlTpok,458
|
|
11
15
|
devctl/generators/angular/__init__.py,sha256=R-ive4k2bf5Z0cWNNa00e5OQJlgNAK4fqDC9vylTfXs,278
|
|
12
16
|
devctl/generators/angular/generator.py,sha256=deo257s_PoJ4jc7WHIX3VPqjiIoAMuW9DSGJjTovdP0,4290
|
|
13
17
|
devctl/generators/angular/scaffolder.py,sha256=IszCxMHUDeJysMbwUAGMkBhhh0MPsQap6hOQYWLwchg,5446
|
|
14
18
|
devctl/generators/angular/templates/config/environment.development.ts.j2,sha256=VoL0vYnyp8c0k9t20ThdW8mZhCq0KdCLP8_onyLjeRc,69
|
|
15
19
|
devctl/generators/angular/templates/config/environment.ts.j2,sha256=rnLcMCwm5FOE5GEIAQmyZyvt9xOrekXGsRzgfCJOPhY,155
|
|
16
|
-
devctl/generators/angular/templates/config/proxy.conf.json.j2,sha256=
|
|
20
|
+
devctl/generators/angular/templates/config/proxy.conf.json.j2,sha256=AoFocoSyxVa2jI0p7DG4Z461nMdI9w0HFmIH9ADThV8,78
|
|
17
21
|
devctl/generators/angular/templates/feature/routes.ts.j2,sha256=Kyk-pbDaYsx_bg-asK8dZxfbqpHV0ss7P35u8VbiQSM,545
|
|
18
22
|
devctl/generators/angular/templates/feature/models/request.model.ts.j2,sha256=3DAAgyb3MLQekqDNVIs3CVgj5nXiS0hneQSvHeuwmHU,125
|
|
19
23
|
devctl/generators/angular/templates/feature/models/response.model.ts.j2,sha256=mhAHeUS9CwOs7fLFJ7OOVByPGgyAvWKeDUdNeVlm9qs,140
|
|
@@ -65,13 +69,13 @@ devctl/generators/nextjs/scaffolder.py,sha256=KdUkr95bscInOaRHp8rZAadmzKmZf3Q5hD
|
|
|
65
69
|
devctl/generators/nextjs/templates/resource/component.tsx.j2,sha256=-uYdBZI56SXmLbdncqgcLZFJ7iBNmo9vOTENDIPZWMk,394
|
|
66
70
|
devctl/generators/nextjs/templates/resource/page.tsx.j2,sha256=6c8fg7NeYulwBQfYQdyhSNoag7JMCO8m-E6RNuRT16Y,323
|
|
67
71
|
devctl/generators/nodejs/__init__.py,sha256=jpvFJZ_VGWp9GjfCNMZbnaXNe7abEchAVIAq8DE9f-A,271
|
|
68
|
-
devctl/generators/nodejs/generator.py,sha256=
|
|
72
|
+
devctl/generators/nodejs/generator.py,sha256=GEDGzkGO5SWM84h37dmClKbhoehiv4g7hWd7WNM8FPU,4028
|
|
69
73
|
devctl/generators/nodejs/scaffolder.py,sha256=cfRHwnSxxy59EuOFMksSJ5RkF9JrbPj_x2Fgu8iTWuk,2117
|
|
70
|
-
devctl/generators/nodejs/templates/config/index.ts.j2,sha256=
|
|
71
|
-
devctl/generators/nodejs/templates/resource/controller.ts.j2,sha256=
|
|
74
|
+
devctl/generators/nodejs/templates/config/index.ts.j2,sha256=WIQ300m9sFHSDv4q28lYbX_EQ0n5Lq-RHqehiZOY6MM,423
|
|
75
|
+
devctl/generators/nodejs/templates/resource/controller.ts.j2,sha256=TJBlTNOXti7IQmv5JshXo6KEgZRXdAEwi-wCdNsSel0,544
|
|
72
76
|
devctl/generators/nodejs/templates/resource/routes.ts.j2,sha256=0YslAicDqu2pHBqYLWUZGOEXQg3iO4Udy5JTJowaYM4,405
|
|
73
77
|
devctl/generators/react/__init__.py,sha256=MlTLvYrmO-v7_7ITsS8hX5Et7dGvLcwxarGbXcM2OA8,264
|
|
74
|
-
devctl/generators/react/generator.py,sha256=
|
|
78
|
+
devctl/generators/react/generator.py,sha256=eujNdgjrXW8KBngvZyJNDWweH7-fqLP6hTYf4L6R1c0,1745
|
|
75
79
|
devctl/generators/react/scaffolder.py,sha256=lA0FGhj1LTOj0SfdWwlqXWppx5N2H4aXeMlX8a6tYZQ,2164
|
|
76
80
|
devctl/generators/react/templates/resource/component.tsx.j2,sha256=7AYliBowiB4A-erHV56cDVXX2SeMRBJ3uCQGxktFPVY,375
|
|
77
81
|
devctl/generators/react/templates/resource/service.ts.j2,sha256=yDCLVM-AGf0sbYsMxSOuS_Yw0dWigCTFzdJPgLrsWHo,409
|
|
@@ -93,12 +97,12 @@ devctl/generators/spring/templates/dto/Request.java.j2,sha256=EoVg3dHqE63prHr_3D
|
|
|
93
97
|
devctl/generators/spring/templates/dto/Response.java.j2,sha256=k1FrU9yqGxlyhvU0slBe53lg-kMor1OxeB713L_8PVg,448
|
|
94
98
|
devctl/generators/spring/templates/mapper/Mapper.java.j2,sha256=1P-J3QkbBfy4i7YEIkWe3e_OrxMQj3gL3qSqqbgqCJo,1116
|
|
95
99
|
devctl/generators/svelte/__init__.py,sha256=zfmH5awxXGqZnnHgjdCE95wwws4TrTl3hZQw3ZwjJpY,271
|
|
96
|
-
devctl/generators/svelte/generator.py,sha256=
|
|
100
|
+
devctl/generators/svelte/generator.py,sha256=AU67y9LpayqnKTXT6WhHucK61ACjws-UQ8H_WYZo-so,1842
|
|
97
101
|
devctl/generators/svelte/scaffolder.py,sha256=90YeH_Wzi9LVX_UvS2n-8Vyz97zGB_RZrJIFnTUJ0io,2111
|
|
98
102
|
devctl/generators/svelte/templates/resource/component.svelte.j2,sha256=38bZSc_AJS-8mYyLavC9psxGBWg8AHvp6BvVoee4FVs,263
|
|
99
103
|
devctl/generators/svelte/templates/resource/page.svelte.j2,sha256=I-9V33ZvAEX1KZw1_Ppka9M1yVzLlRUEJCgGMrQZXeg,179
|
|
100
104
|
devctl/generators/vue/__init__.py,sha256=GDbArDE1XEWsf2RTcm5QPjlccSCcOFAwAHmx6cGN1VU,253
|
|
101
|
-
devctl/generators/vue/generator.py,sha256=
|
|
105
|
+
devctl/generators/vue/generator.py,sha256=BOnSgnSUZHXIvFHIIZG0anZL0w09SNkyigWlwiZ2Sf4,4216
|
|
102
106
|
devctl/generators/vue/scaffolder.py,sha256=BLTBDrCBMsA4y2lJKHi-8rLrgTrhu1doWUs5iuYLXwk,3497
|
|
103
107
|
devctl/generators/vue/templates/config/App.vue.j2,sha256=019FJ-P2cPAFEO99NtqpYcCpE25mEBapdnW8yB-OmzA,542
|
|
104
108
|
devctl/generators/vue/templates/config/main.ts.j2,sha256=edNelsh0qYHybZZ5UWxKEg24n3PYYR8L3heD4iwUP-E,173
|
|
@@ -110,18 +114,19 @@ devctl/generators/vue/templates/feature/models.ts.j2,sha256=am0no09-huAe9woUbMwH
|
|
|
110
114
|
devctl/generators/vue/templates/feature/routes.ts.j2,sha256=ooUpJZa3E8WsF0ykvAo0KLYdBWFha6uv9z3b4sVKjFY,607
|
|
111
115
|
devctl/generators/vue/templates/feature/service.ts.j2,sha256=jzvkxao3LtaEWI2P_H_I59gUuylVIAyQ-tvEjQHAu8I,1616
|
|
112
116
|
devctl/orchestrator/__init__.py,sha256=guj85FATiYbeQ8K7QnaE09YV_VSVWuwEAie1lLQpgKE,87
|
|
113
|
-
devctl/orchestrator/config_builder.py,sha256=
|
|
117
|
+
devctl/orchestrator/config_builder.py,sha256=z8dOCUDBjzbYvEgDCS7ZcROXwpbQHuJQXvntM3gbodQ,2127
|
|
114
118
|
devctl/orchestrator/process_manager.py,sha256=ac6M91W4F_PWFVe4lQhnXIiQBTasJOVA0o5AKpyt8DQ,9868
|
|
115
119
|
devctl/orchestrator/runner.py,sha256=VC4AyfqHd7TNN3PN-lUXP56_tIjrqya-WUje_Qry7e4,7893
|
|
116
120
|
devctl/orchestrator/scanner.py,sha256=b8JtnK0OhZitKzajlIjJJhUsrf5jXVuVW_j08ZOESfo,5677
|
|
117
|
-
devctl/tui/app.py,sha256=
|
|
121
|
+
devctl/tui/app.py,sha256=0XzhT4sVMdWxN3Qe4hyGuUQLjGAoZHJLsf1URXHroAQ,34457
|
|
122
|
+
devctl/tui/app.tcss,sha256=IQOMHHME3dEHhcbBm47uAWsZWz-N7hhNDWJt-QEwfRc,5535
|
|
118
123
|
devctl/utils/__init__.py,sha256=M57MX2uzHxehKbh21XrYLoSVP8XxiU5B5JbM3cIpE9k,111
|
|
119
124
|
devctl/utils/dependencies.py,sha256=6aCy0JU5T1VXe8eCtA9K7HcReU3CtduwgY_OLpTf1Cg,1493
|
|
120
125
|
devctl/utils/env_loader.py,sha256=Q_n4Iv3hhK9whuayp0SpwDMyzH2eliIjtC3g3l6YvJ0,1700
|
|
121
126
|
devctl/utils/platform.py,sha256=FUGP3rBoYhcsdh74IBk-lh8Ho12bOqmvveDAjjV71PI,3435
|
|
122
|
-
devctl-1.2.
|
|
123
|
-
devctl-1.2.
|
|
124
|
-
devctl-1.2.
|
|
125
|
-
devctl-1.2.
|
|
126
|
-
devctl-1.2.
|
|
127
|
-
devctl-1.2.
|
|
127
|
+
devctl-1.2.4.dist-info/licenses/LICENSE,sha256=ejuOzK7v5suPzolNB0qJJZ78UNEG17KV-5vVQR-yMQI,1071
|
|
128
|
+
devctl-1.2.4.dist-info/METADATA,sha256=duKZooBYIpFfvaMT2FfQ-T6h2DnLOSzcXNa7KvzFS3w,5664
|
|
129
|
+
devctl-1.2.4.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
130
|
+
devctl-1.2.4.dist-info/entry_points.txt,sha256=kbeKCy-Jwx7EaubbhJg0ni5f_8VFdrtR1ACnp4QtEWg,44
|
|
131
|
+
devctl-1.2.4.dist-info/top_level.txt,sha256=NrzcRKPiNVlovhxKHBn0ttiP9NWL9obUn9vRxBItB5I,7
|
|
132
|
+
devctl-1.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|