stackai-cli 0.1.0__py3-none-any.whl → 0.2.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.
- devai/__init__.py +1 -1
- devai/cli.py +16 -14
- devai/detector.py +387 -14
- devai/generator.py +135 -0
- stackai_cli-0.2.0.dist-info/METADATA +119 -0
- stackai_cli-0.2.0.dist-info/RECORD +11 -0
- stackai_cli-0.1.0.dist-info/METADATA +0 -111
- stackai_cli-0.1.0.dist-info/RECORD +0 -11
- {stackai_cli-0.1.0.dist-info → stackai_cli-0.2.0.dist-info}/WHEEL +0 -0
- {stackai_cli-0.1.0.dist-info → stackai_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {stackai_cli-0.1.0.dist-info → stackai_cli-0.2.0.dist-info}/top_level.txt +0 -0
devai/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.2.0"
|
devai/cli.py
CHANGED
|
@@ -2,11 +2,12 @@ import click
|
|
|
2
2
|
from devai.detector import detect_stack
|
|
3
3
|
from devai.generator import generate_files
|
|
4
4
|
from devai.debugger import debug_container
|
|
5
|
+
from devai import __version__
|
|
5
6
|
|
|
6
7
|
@click.group()
|
|
7
|
-
@click.version_option()
|
|
8
|
+
@click.version_option(version=__version__, prog_name="stackai")
|
|
8
9
|
def main():
|
|
9
|
-
"""
|
|
10
|
+
"""stackai — AI-powered Docker assistant. No configuration needed."""
|
|
10
11
|
pass
|
|
11
12
|
|
|
12
13
|
|
|
@@ -14,28 +15,29 @@ def main():
|
|
|
14
15
|
@click.argument("path", default=".", type=click.Path(exists=True))
|
|
15
16
|
def init(path):
|
|
16
17
|
"""Analyse ton projet et génère Dockerfile + docker-compose."""
|
|
17
|
-
click.echo(click.style("🔍
|
|
18
|
+
click.echo(click.style("🔍 Analyzing project...", fg="cyan"))
|
|
18
19
|
|
|
19
20
|
stack = detect_stack(path)
|
|
20
21
|
if not stack:
|
|
21
|
-
click.echo(click.style("❌
|
|
22
|
+
click.echo(click.style("❌ Could not detect stack. Make sure you are in a project directory.", fg="red"))
|
|
22
23
|
raise SystemExit(1)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
click.echo(click.style("
|
|
25
|
+
services = ', '.join(stack['services']) if stack['services'] else 'none'
|
|
26
|
+
click.echo(click.style(f"✅ Stack detected: {stack['language']} / {stack.get('framework','generic')} + {services}", fg="green"))
|
|
27
|
+
click.echo(click.style("⚙️ Generating Docker files...", fg="cyan"))
|
|
26
28
|
|
|
27
29
|
generated = generate_files(path, stack)
|
|
28
30
|
for f in generated:
|
|
29
|
-
click.echo(click.style(f" ✅ {f}
|
|
31
|
+
click.echo(click.style(f" ✅ {f} created", fg="green"))
|
|
30
32
|
|
|
31
|
-
click.echo(click.style("\n🚀
|
|
33
|
+
click.echo(click.style("\n🚀 Ready! Run: docker compose up -d", fg="bright_green", bold=True))
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
@main.command()
|
|
35
37
|
@click.argument("container_name")
|
|
36
38
|
def debug(container_name):
|
|
37
39
|
"""Analyse les logs d'un container et explique l'erreur."""
|
|
38
|
-
click.echo(click.style(f"🔍
|
|
40
|
+
click.echo(click.style(f"🔍 Reading logs from '{container_name}'...", fg="cyan"))
|
|
39
41
|
debug_container(container_name)
|
|
40
42
|
|
|
41
43
|
|
|
@@ -48,9 +50,9 @@ def scan(path):
|
|
|
48
50
|
click.echo(click.style("❌ Aucune stack détectée.", fg="red"))
|
|
49
51
|
return
|
|
50
52
|
|
|
51
|
-
click.echo(click.style("📦 Stack
|
|
52
|
-
click.echo(f"
|
|
53
|
-
click.echo(f" Framework : {stack.get('framework', '
|
|
54
|
-
click.echo(f" Port : {stack.get('port', '
|
|
55
|
-
click.echo(f" Services : {', '.join(stack['services']) if stack['services'] else '
|
|
53
|
+
click.echo(click.style("📦 Stack detected:", fg="cyan", bold=True))
|
|
54
|
+
click.echo(f" Language : {stack['language']}")
|
|
55
|
+
click.echo(f" Framework : {stack.get('framework', 'unknown')}")
|
|
56
|
+
click.echo(f" Port : {stack.get('port', 'unknown')}")
|
|
57
|
+
click.echo(f" Services : {', '.join(stack['services']) if stack['services'] else 'none'}")
|
|
56
58
|
click.echo(f" Python ver. : {stack.get('python_version', 'N/A')}")
|
devai/detector.py
CHANGED
|
@@ -1,21 +1,394 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
+
import json
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# SERVICE MAP dep_name → [service_ids]
|
|
9
|
+
# A single dependency can imply multiple services.
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
SERVICE_MAP: dict[str, list[str]] = {
|
|
12
|
+
# ── Relational databases ──────────────────────────────────────────────
|
|
13
|
+
"psycopg2": ["postgres"], "psycopg2-binary": ["postgres"],
|
|
14
|
+
"asyncpg": ["postgres"], "sqlalchemy": ["postgres"],
|
|
15
|
+
"alembic": ["postgres"], "databases": ["postgres"],
|
|
16
|
+
"tortoise-orm": ["postgres"], "peewee": ["postgres"],
|
|
17
|
+
"dbt-core": ["postgres"], "dbt-postgres": ["postgres"],
|
|
18
|
+
"dbt-spark": ["spark"], "dbt-bigquery": [],
|
|
19
|
+
"pymysql": ["mysql"], "mysql-connector-python": ["mysql"],
|
|
20
|
+
"aiomysql": ["mysql"],
|
|
21
|
+
"cx-oracle": ["oracle"], "oracledb": ["oracle"],
|
|
22
|
+
"pyodbc": ["mssql"], "pymssql": ["mssql"],
|
|
23
|
+
"psycopg": ["postgres"],
|
|
24
|
+
|
|
25
|
+
# ── NoSQL ─────────────────────────────────────────────────────────────
|
|
26
|
+
"pymongo": ["mongodb"], "motor": ["mongodb"],
|
|
27
|
+
"mongoengine": ["mongodb"], "beanie": ["mongodb"],
|
|
28
|
+
"redis": ["redis"], "aioredis": ["redis"],
|
|
29
|
+
"redis-py": ["redis"], "coredis": ["redis"],
|
|
30
|
+
"celery": ["redis"], "dramatiq": ["redis"],
|
|
31
|
+
"rq": ["redis"],
|
|
32
|
+
"cassandra-driver": ["cassandra"],
|
|
33
|
+
"aiofiles": [],
|
|
34
|
+
|
|
35
|
+
# ── Search & Analytics ────────────────────────────────────────────────
|
|
36
|
+
"elasticsearch": ["elasticsearch"],
|
|
37
|
+
"elasticsearch-dsl": ["elasticsearch"],
|
|
38
|
+
"opensearch-py": ["opensearch"],
|
|
39
|
+
"opensearchpy": ["opensearch"],
|
|
40
|
+
"solr": ["solr"],
|
|
41
|
+
"clickhouse-driver": ["clickhouse"],
|
|
42
|
+
"clickhouse-connect": ["clickhouse"],
|
|
43
|
+
|
|
44
|
+
# ── Streaming / Messaging ─────────────────────────────────────────────
|
|
45
|
+
"kafka-python": ["kafka"], "confluent-kafka": ["kafka"],
|
|
46
|
+
"aiokafka": ["kafka"], "faust": ["kafka"],
|
|
47
|
+
"pika": ["rabbitmq"], "aio-pika": ["rabbitmq"],
|
|
48
|
+
"kombu": ["rabbitmq"],
|
|
49
|
+
"nats-py": ["nats"],
|
|
50
|
+
|
|
51
|
+
# ── Object Storage / Cloud ────────────────────────────────────────────
|
|
52
|
+
"boto3": ["minio"], "botocore": ["minio"],
|
|
53
|
+
"s3fs": ["minio"], "s3transfer": ["minio"],
|
|
54
|
+
"minio": ["minio"],
|
|
55
|
+
"google-cloud-storage": [], "azure-storage-blob": [],
|
|
56
|
+
|
|
57
|
+
# ── Big Data / Spark ──────────────────────────────────────────────────
|
|
58
|
+
"pyspark": ["spark"], "delta-spark": ["spark"],
|
|
59
|
+
"spark": ["spark"],
|
|
60
|
+
|
|
61
|
+
# ── Streaming / Real-time ─────────────────────────────────────────────
|
|
62
|
+
"pyflink": ["flink"], "apache-flink": ["flink"],
|
|
63
|
+
|
|
64
|
+
# ── Orchestration ─────────────────────────────────────────────────────
|
|
65
|
+
"apache-airflow": ["airflow", "postgres"],
|
|
66
|
+
"airflow": ["airflow", "postgres"],
|
|
67
|
+
"prefect": ["prefect"],
|
|
68
|
+
"dagster": ["dagster", "postgres"],
|
|
69
|
+
"luigi": [],
|
|
70
|
+
|
|
71
|
+
# ── ML / AI ───────────────────────────────────────────────────────────
|
|
72
|
+
"mlflow": ["mlflow"],
|
|
73
|
+
"torch": [], "tensorflow": [],
|
|
74
|
+
"onnxruntime": [], "triton": [],
|
|
75
|
+
|
|
76
|
+
# ── Tracing / Monitoring ──────────────────────────────────────────────
|
|
77
|
+
"opentelemetry-sdk": ["jaeger"],
|
|
78
|
+
"prometheus-client": ["prometheus"],
|
|
79
|
+
"grafana": ["grafana"],
|
|
80
|
+
|
|
81
|
+
# ── Web frameworks (no extra service needed) ──────────────────────────
|
|
82
|
+
"fastapi": [], "flask": [], "django": [], "tornado": [],
|
|
83
|
+
"aiohttp": [], "starlette": [], "litestar": [],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# ---------------------------------------------------------------------------
|
|
87
|
+
# IMPORT → dep alias (for scanning .py source files)
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
IMPORT_TO_DEP: dict[str, str] = {
|
|
90
|
+
"psycopg2": "psycopg2", "asyncpg": "asyncpg",
|
|
91
|
+
"sqlalchemy": "sqlalchemy", "alembic": "alembic",
|
|
92
|
+
"pymysql": "pymysql", "pymongo": "pymongo",
|
|
93
|
+
"motor": "motor", "redis": "redis",
|
|
94
|
+
"celery": "celery", "kafka": "kafka-python",
|
|
95
|
+
"confluent_kafka": "confluent-kafka", "aiokafka": "aiokafka",
|
|
96
|
+
"pika": "pika", "aio_pika": "aio-pika",
|
|
97
|
+
"boto3": "boto3", "botocore": "botocore",
|
|
98
|
+
"s3fs": "s3fs", "minio": "minio",
|
|
99
|
+
"pyspark": "pyspark", "delta": "delta-spark",
|
|
100
|
+
"airflow": "apache-airflow", "prefect": "prefect",
|
|
101
|
+
"dagster": "dagster", "mlflow": "mlflow",
|
|
15
102
|
"elasticsearch": "elasticsearch",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
103
|
+
"opensearchpy": "opensearch-py",
|
|
104
|
+
"clickhouse_driver": "clickhouse-driver",
|
|
105
|
+
"cassandra": "cassandra-driver",
|
|
106
|
+
"faust": "faust", "pyflink": "pyflink",
|
|
107
|
+
"torch": "torch", "tensorflow": "tensorflow",
|
|
108
|
+
"prometheus_client": "prometheus-client",
|
|
109
|
+
"opentelemetry": "opentelemetry-sdk",
|
|
110
|
+
"dbt": "dbt-core",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Framework detection: import name → (framework_label, default_port)
|
|
114
|
+
FRAMEWORK_MAP: dict[str, tuple[str, int]] = {
|
|
115
|
+
"fastapi": ("fastapi", 8000), "uvicorn": ("fastapi", 8000),
|
|
116
|
+
"flask": ("flask", 5000), "django": ("django", 8000),
|
|
117
|
+
"tornado": ("tornado", 8888), "aiohttp": ("aiohttp", 8080),
|
|
118
|
+
"starlette": ("starlette", 8000), "litestar": ("litestar", 8000),
|
|
119
|
+
"express": ("express", 3000), "next": ("nextjs", 3000),
|
|
120
|
+
"nuxt": ("nuxtjs", 3000),
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
# Public entry point
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
def detect_stack(project_path: str) -> dict | None:
|
|
129
|
+
path = Path(project_path)
|
|
130
|
+
|
|
131
|
+
if (path / "requirements.txt").exists() or (path / "pyproject.toml").exists():
|
|
132
|
+
return _detect_python(path)
|
|
133
|
+
if (path / "package.json").exists():
|
|
134
|
+
return _detect_node(path)
|
|
135
|
+
if (path / "pom.xml").exists() or (path / "build.gradle").exists():
|
|
136
|
+
return _detect_java(path)
|
|
137
|
+
if (path / "go.mod").exists():
|
|
138
|
+
return _detect_go(path)
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Python
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
def _detect_python(path: Path) -> dict:
|
|
147
|
+
deps = _read_python_deps(path)
|
|
148
|
+
deps |= _scan_python_imports(path) # enrich with source scan
|
|
149
|
+
deps |= _read_env_hints(path) # enrich with .env hints
|
|
150
|
+
|
|
151
|
+
framework, port = _detect_framework(deps)
|
|
152
|
+
services = _deps_to_services(deps)
|
|
153
|
+
python_version = _detect_python_version(path)
|
|
154
|
+
dep_file = "requirements.txt" if (path / "requirements.txt").exists() else "pyproject.toml"
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
"language": "python",
|
|
158
|
+
"framework": framework,
|
|
159
|
+
"port": port,
|
|
160
|
+
"services": services,
|
|
161
|
+
"python_version": python_version,
|
|
162
|
+
"dep_file": dep_file,
|
|
163
|
+
"raw_deps": sorted(deps),
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _read_python_deps(path: Path) -> set[str]:
|
|
168
|
+
"""Read requirements.txt and/or pyproject.toml."""
|
|
169
|
+
deps: set[str] = set()
|
|
170
|
+
|
|
171
|
+
req = path / "requirements.txt"
|
|
172
|
+
if req.exists():
|
|
173
|
+
for line in req.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
174
|
+
line = line.strip()
|
|
175
|
+
if not line or line.startswith("#") or line.startswith("-"):
|
|
176
|
+
continue
|
|
177
|
+
# strip version specifiers
|
|
178
|
+
name = re.split(r"[>=<!;\[]", line)[0].strip().lower()
|
|
179
|
+
if name:
|
|
180
|
+
deps.add(name)
|
|
181
|
+
|
|
182
|
+
pyproject = path / "pyproject.toml"
|
|
183
|
+
if pyproject.exists():
|
|
184
|
+
content = pyproject.read_text(encoding="utf-8", errors="ignore")
|
|
185
|
+
for m in re.finditer(r'"([a-zA-Z0-9_\-]+)\s*[>=<!]', content):
|
|
186
|
+
deps.add(m.group(1).lower())
|
|
187
|
+
|
|
188
|
+
return deps
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _scan_python_imports(path: Path) -> set[str]:
|
|
192
|
+
"""Scan .py files for import statements and map to dep names."""
|
|
193
|
+
found: set[str] = set()
|
|
194
|
+
py_files = list(path.rglob("*.py"))[:200] # cap at 200 files
|
|
195
|
+
|
|
196
|
+
import_re = re.compile(
|
|
197
|
+
r"^(?:import|from)\s+([a-zA-Z0-9_]+)", re.MULTILINE
|
|
198
|
+
)
|
|
199
|
+
for f in py_files:
|
|
200
|
+
try:
|
|
201
|
+
content = f.read_text(encoding="utf-8", errors="ignore")
|
|
202
|
+
for m in import_re.finditer(content):
|
|
203
|
+
root = m.group(1).lower()
|
|
204
|
+
if root in IMPORT_TO_DEP:
|
|
205
|
+
found.add(IMPORT_TO_DEP[root])
|
|
206
|
+
except OSError:
|
|
207
|
+
pass
|
|
208
|
+
return found
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _read_env_hints(path: Path) -> set[str]:
|
|
212
|
+
"""Infer services from .env or .env.example variable names."""
|
|
213
|
+
hints: set[str] = set()
|
|
214
|
+
for fname in [".env", ".env.example", ".env.sample"]:
|
|
215
|
+
env_file = path / fname
|
|
216
|
+
if not env_file.exists():
|
|
217
|
+
continue
|
|
218
|
+
content = env_file.read_text(encoding="utf-8", errors="ignore").upper()
|
|
219
|
+
if "POSTGRES" in content or "DATABASE_URL" in content:
|
|
220
|
+
hints.add("psycopg2")
|
|
221
|
+
if "REDIS" in content:
|
|
222
|
+
hints.add("redis")
|
|
223
|
+
if "MONGO" in content:
|
|
224
|
+
hints.add("pymongo")
|
|
225
|
+
if "KAFKA" in content:
|
|
226
|
+
hints.add("kafka-python")
|
|
227
|
+
if "MINIO" in content or "S3_ENDPOINT" in content or "AWS_" in content:
|
|
228
|
+
hints.add("boto3")
|
|
229
|
+
if "SPARK" in content:
|
|
230
|
+
hints.add("pyspark")
|
|
231
|
+
if "AIRFLOW" in content:
|
|
232
|
+
hints.add("apache-airflow")
|
|
233
|
+
if "MLFLOW" in content:
|
|
234
|
+
hints.add("mlflow")
|
|
235
|
+
if "ELASTICSEARCH" in content:
|
|
236
|
+
hints.add("elasticsearch")
|
|
237
|
+
return hints
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
# Node.js
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
def _detect_node(path: Path) -> dict:
|
|
245
|
+
pkg = json.loads((path / "package.json").read_text(encoding="utf-8"))
|
|
246
|
+
all_deps = {
|
|
247
|
+
**pkg.get("dependencies", {}),
|
|
248
|
+
**pkg.get("devDependencies", {}),
|
|
249
|
+
}
|
|
250
|
+
deps = {k.lower() for k in all_deps}
|
|
251
|
+
|
|
252
|
+
framework, port = _detect_framework(deps)
|
|
253
|
+
services = _deps_to_services(deps)
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
"language": "nodejs",
|
|
257
|
+
"framework": framework,
|
|
258
|
+
"port": port,
|
|
259
|
+
"services": services,
|
|
260
|
+
"python_version": None,
|
|
261
|
+
"dep_file": "package.json",
|
|
262
|
+
"raw_deps": sorted(deps),
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Java / Go
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
def _detect_java(path: Path) -> dict:
|
|
271
|
+
content = ""
|
|
272
|
+
for f in ["pom.xml", "build.gradle"]:
|
|
273
|
+
p = path / f
|
|
274
|
+
if p.exists():
|
|
275
|
+
content = p.read_text(encoding="utf-8", errors="ignore").lower()
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
services = []
|
|
279
|
+
if "postgresql" in content or "postgres" in content:
|
|
280
|
+
services.append("postgres")
|
|
281
|
+
if "mysql" in content:
|
|
282
|
+
services.append("mysql")
|
|
283
|
+
if "mongodb" in content or "mongo" in content:
|
|
284
|
+
services.append("mongodb")
|
|
285
|
+
if "redis" in content:
|
|
286
|
+
services.append("redis")
|
|
287
|
+
if "kafka" in content:
|
|
288
|
+
services.append("kafka")
|
|
289
|
+
if "elasticsearch" in content:
|
|
290
|
+
services.append("elasticsearch")
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
"language": "java",
|
|
294
|
+
"framework": "spring-boot",
|
|
295
|
+
"port": 8080,
|
|
296
|
+
"services": services,
|
|
297
|
+
"python_version": None,
|
|
298
|
+
"dep_file": "pom.xml" if (path / "pom.xml").exists() else "build.gradle",
|
|
299
|
+
"raw_deps": [],
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _detect_go(path: Path) -> dict:
|
|
304
|
+
content = (path / "go.mod").read_text(encoding="utf-8", errors="ignore").lower()
|
|
305
|
+
services = []
|
|
306
|
+
if "postgres" in content or "pgx" in content:
|
|
307
|
+
services.append("postgres")
|
|
308
|
+
if "mongo" in content:
|
|
309
|
+
services.append("mongodb")
|
|
310
|
+
if "redis" in content:
|
|
311
|
+
services.append("redis")
|
|
312
|
+
if "kafka" in content:
|
|
313
|
+
services.append("kafka")
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
"language": "go",
|
|
317
|
+
"framework": "generic",
|
|
318
|
+
"port": 8080,
|
|
319
|
+
"services": services,
|
|
320
|
+
"python_version": None,
|
|
321
|
+
"dep_file": "go.mod",
|
|
322
|
+
"raw_deps": [],
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
# Helpers
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
def _deps_to_services(deps: set[str]) -> list[str]:
|
|
331
|
+
seen: set[str] = set()
|
|
332
|
+
result: list[str] = []
|
|
333
|
+
for dep in deps:
|
|
334
|
+
for svc in SERVICE_MAP.get(dep, []):
|
|
335
|
+
if svc and svc not in seen:
|
|
336
|
+
seen.add(svc)
|
|
337
|
+
result.append(svc)
|
|
338
|
+
return result
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _detect_framework(deps: set[str]) -> tuple[str, int]:
|
|
342
|
+
for dep, (fw, port) in FRAMEWORK_MAP.items():
|
|
343
|
+
if dep in deps:
|
|
344
|
+
return fw, port
|
|
345
|
+
return "generic", 8000
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _detect_python_version(path: Path) -> str:
|
|
349
|
+
for f in [".python-version", "runtime.txt"]:
|
|
350
|
+
p = path / f
|
|
351
|
+
if p.exists():
|
|
352
|
+
m = re.search(r"3\.\d+", p.read_text())
|
|
353
|
+
if m:
|
|
354
|
+
return m.group()
|
|
355
|
+
pyproject = path / "pyproject.toml"
|
|
356
|
+
if pyproject.exists():
|
|
357
|
+
m = re.search(r'python_requires\s*=\s*">=\s*(3\.\d+)"', pyproject.read_text())
|
|
358
|
+
if m:
|
|
359
|
+
return m.group(1)
|
|
360
|
+
return "3.11"
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
# Mapping dépendance → service(s) Docker requis
|
|
365
|
+
# Un service peut en impliquer plusieurs (ex: airflow → airflow + postgres)
|
|
366
|
+
SERVICE_HINTS = {
|
|
367
|
+
# Databases
|
|
368
|
+
"psycopg2": ["postgres"],
|
|
369
|
+
"asyncpg": ["postgres"],
|
|
370
|
+
"sqlalchemy": ["postgres"],
|
|
371
|
+
"pymongo": ["mongodb"],
|
|
372
|
+
"motor": ["mongodb"],
|
|
373
|
+
"mysql-connector": ["mysql"],
|
|
374
|
+
"pymysql": ["mysql"],
|
|
375
|
+
# Cache / Queue
|
|
376
|
+
"redis": ["redis"],
|
|
377
|
+
"celery": ["redis"],
|
|
378
|
+
# Search
|
|
379
|
+
"elasticsearch": ["elasticsearch"],
|
|
380
|
+
# Streaming
|
|
381
|
+
"kafka": ["kafka"],
|
|
382
|
+
# Object storage
|
|
383
|
+
"boto3": ["minio"],
|
|
384
|
+
"s3fs": ["minio"],
|
|
385
|
+
# Big Data
|
|
386
|
+
"pyspark": ["spark"],
|
|
387
|
+
"delta-spark": ["spark"],
|
|
388
|
+
"pyflink": ["flink"],
|
|
389
|
+
# Orchestration (Airflow implique postgres)
|
|
390
|
+
"apache-airflow": ["airflow", "postgres"],
|
|
391
|
+
"airflow": ["airflow", "postgres"],
|
|
19
392
|
}
|
|
20
393
|
|
|
21
394
|
# Mapping dépendance → framework
|
|
@@ -65,7 +438,7 @@ def _detect_python(path: Path, dep_file: Path) -> dict:
|
|
|
65
438
|
port = p
|
|
66
439
|
break
|
|
67
440
|
|
|
68
|
-
services = list({
|
|
441
|
+
services = list({s for dep in deps if dep in SERVICE_HINTS for s in SERVICE_HINTS[dep]})
|
|
69
442
|
|
|
70
443
|
python_version = _detect_python_version(path)
|
|
71
444
|
|
|
@@ -97,7 +470,7 @@ def _detect_node(path: Path) -> dict:
|
|
|
97
470
|
framework = "react"
|
|
98
471
|
port = 3000
|
|
99
472
|
|
|
100
|
-
services = list({
|
|
473
|
+
services = list({s for dep in deps_lower if dep in SERVICE_HINTS for s in SERVICE_HINTS[dep]})
|
|
101
474
|
|
|
102
475
|
return {
|
|
103
476
|
"language": "nodejs",
|
devai/generator.py
CHANGED
|
@@ -143,6 +143,140 @@ SERVICE_TEMPLATES = {
|
|
|
143
143
|
- xpack.security.enabled=false
|
|
144
144
|
ports:
|
|
145
145
|
- "9200:9200"
|
|
146
|
+
""",
|
|
147
|
+
"minio": """\
|
|
148
|
+
minio:
|
|
149
|
+
image: minio/minio:latest
|
|
150
|
+
environment:
|
|
151
|
+
MINIO_ROOT_USER: minioadmin
|
|
152
|
+
MINIO_ROOT_PASSWORD: minioadmin
|
|
153
|
+
command: server /data --console-address ":9001"
|
|
154
|
+
ports:
|
|
155
|
+
- "9000:9000"
|
|
156
|
+
- "9001:9001"
|
|
157
|
+
volumes:
|
|
158
|
+
- minio_data:/data
|
|
159
|
+
""",
|
|
160
|
+
"airflow": """\
|
|
161
|
+
airflow-webserver:
|
|
162
|
+
image: apache/airflow:2.8.1-python3.11
|
|
163
|
+
environment:
|
|
164
|
+
- AIRFLOW__CORE__EXECUTOR=LocalExecutor
|
|
165
|
+
- AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres:5432/airflow
|
|
166
|
+
- AIRFLOW__CORE__LOAD_EXAMPLES=False
|
|
167
|
+
volumes:
|
|
168
|
+
- ./dags:/opt/airflow/dags
|
|
169
|
+
ports:
|
|
170
|
+
- "8080:8080"
|
|
171
|
+
command: >
|
|
172
|
+
bash -c "airflow db migrate && airflow users create --username admin --password admin --firstname Admin --lastname User --role Admin --email admin@example.com; airflow webserver"
|
|
173
|
+
depends_on:
|
|
174
|
+
- postgres
|
|
175
|
+
|
|
176
|
+
airflow-scheduler:
|
|
177
|
+
image: apache/airflow:2.8.1-python3.11
|
|
178
|
+
environment:
|
|
179
|
+
- AIRFLOW__CORE__EXECUTOR=LocalExecutor
|
|
180
|
+
- AIRFLOW__DATABASE__SQL_ALCHEMY_CONN=postgresql+psycopg2://airflow:airflow@postgres:5432/airflow
|
|
181
|
+
- AIRFLOW__CORE__LOAD_EXAMPLES=False
|
|
182
|
+
volumes:
|
|
183
|
+
- ./dags:/opt/airflow/dags
|
|
184
|
+
command: airflow scheduler
|
|
185
|
+
depends_on:
|
|
186
|
+
- postgres
|
|
187
|
+
""",
|
|
188
|
+
|
|
189
|
+
"spark": """\
|
|
190
|
+
spark-master:
|
|
191
|
+
image: bitnami/spark:3.5
|
|
192
|
+
environment:
|
|
193
|
+
- SPARK_MODE=master
|
|
194
|
+
- SPARK_MASTER_HOST=spark-master
|
|
195
|
+
ports:
|
|
196
|
+
- "7077:7077"
|
|
197
|
+
- "8081:8080"
|
|
198
|
+
|
|
199
|
+
spark-worker:
|
|
200
|
+
image: bitnami/spark:3.5
|
|
201
|
+
environment:
|
|
202
|
+
- SPARK_MODE=worker
|
|
203
|
+
- SPARK_MASTER_URL=spark://spark-master:7077
|
|
204
|
+
- SPARK_WORKER_MEMORY=2g
|
|
205
|
+
- SPARK_WORKER_CORES=2
|
|
206
|
+
depends_on:
|
|
207
|
+
- spark-master
|
|
208
|
+
""",
|
|
209
|
+
"kafka": """\
|
|
210
|
+
zookeeper:
|
|
211
|
+
image: confluentinc/cp-zookeeper:7.6.0
|
|
212
|
+
environment:
|
|
213
|
+
ZOOKEEPER_CLIENT_PORT: 2181
|
|
214
|
+
|
|
215
|
+
kafka:
|
|
216
|
+
image: confluentinc/cp-kafka:7.6.0
|
|
217
|
+
ports:
|
|
218
|
+
- "9092:9092"
|
|
219
|
+
environment:
|
|
220
|
+
KAFKA_BROKER_ID: 1
|
|
221
|
+
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
|
222
|
+
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
|
223
|
+
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
|
224
|
+
depends_on:
|
|
225
|
+
- zookeeper
|
|
226
|
+
""",
|
|
227
|
+
"rabbitmq": """\
|
|
228
|
+
rabbitmq:
|
|
229
|
+
image: rabbitmq:3-management-alpine
|
|
230
|
+
ports:
|
|
231
|
+
- "5672:5672"
|
|
232
|
+
- "15672:15672"
|
|
233
|
+
""",
|
|
234
|
+
"mlflow": """\
|
|
235
|
+
mlflow:
|
|
236
|
+
image: ghcr.io/mlflow/mlflow:v2.12.1
|
|
237
|
+
ports:
|
|
238
|
+
- "5000:5000"
|
|
239
|
+
command: mlflow server --host 0.0.0.0 --port 5000
|
|
240
|
+
""",
|
|
241
|
+
"prometheus": """\
|
|
242
|
+
prometheus:
|
|
243
|
+
image: prom/prometheus:latest
|
|
244
|
+
ports:
|
|
245
|
+
- "9090:9090"
|
|
246
|
+
volumes:
|
|
247
|
+
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
|
248
|
+
""",
|
|
249
|
+
"prefect": """\
|
|
250
|
+
prefect:
|
|
251
|
+
image: prefecthq/prefect:2-latest
|
|
252
|
+
command: prefect server start --host 0.0.0.0
|
|
253
|
+
ports:
|
|
254
|
+
- "4200:4200"
|
|
255
|
+
""",
|
|
256
|
+
"dagster": """\
|
|
257
|
+
dagster:
|
|
258
|
+
image: dagster/dagster-k8s:latest
|
|
259
|
+
ports:
|
|
260
|
+
- "3000:3000"
|
|
261
|
+
depends_on:
|
|
262
|
+
- postgres
|
|
263
|
+
""",
|
|
264
|
+
"flink": """\
|
|
265
|
+
flink-jobmanager:
|
|
266
|
+
image: flink:1.18-scala_2.12
|
|
267
|
+
command: jobmanager
|
|
268
|
+
ports:
|
|
269
|
+
- "8082:8081"
|
|
270
|
+
environment:
|
|
271
|
+
- JOB_MANAGER_RPC_ADDRESS=flink-jobmanager
|
|
272
|
+
|
|
273
|
+
flink-taskmanager:
|
|
274
|
+
image: flink:1.18-scala_2.12
|
|
275
|
+
command: taskmanager
|
|
276
|
+
depends_on:
|
|
277
|
+
- flink-jobmanager
|
|
278
|
+
environment:
|
|
279
|
+
- JOB_MANAGER_RPC_ADDRESS=flink-jobmanager
|
|
146
280
|
""",
|
|
147
281
|
}
|
|
148
282
|
|
|
@@ -150,6 +284,7 @@ VOLUME_NAMES = {
|
|
|
150
284
|
"postgres": "postgres_data",
|
|
151
285
|
"mongodb": "mongo_data",
|
|
152
286
|
"mysql": "mysql_data",
|
|
287
|
+
"minio": "minio_data",
|
|
153
288
|
}
|
|
154
289
|
|
|
155
290
|
FRAMEWORK_CMDS = {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stackai-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: AI-powered Docker assistant — detects your stack, generates Dockerfile & compose, debugs containers.
|
|
5
|
+
Author-email: El Mehdi Boutahar <boutahar.elmehdi@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/MehdiB7/devai
|
|
8
|
+
Project-URL: Repository, https://github.com/MehdiB7/devai
|
|
9
|
+
Project-URL: Bug-Tracker, https://github.com/MehdiB7/devai/issues
|
|
10
|
+
Keywords: docker,ai,devops,cli,ollama,automation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: click>=8.1
|
|
20
|
+
|
|
21
|
+
# stackai-cli
|
|
22
|
+
|
|
23
|
+
> AI-powered Docker assistant. No configuration needed.
|
|
24
|
+
|
|
25
|
+
`stackai` analyzes your project, generates an optimized Dockerfile and docker-compose, and debugs your containers using a local AI model — all in a single command.
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/stackai-cli/)
|
|
28
|
+
[](https://pypi.org/project/stackai-cli/)
|
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install stackai-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> `stackai debug` requires [Ollama](https://ollama.com) running locally with `ollama pull llama3`.
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
### `stackai init` — Generate Docker files from your project
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd my-project
|
|
45
|
+
stackai init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
🔍 Analyzing project...
|
|
50
|
+
✅ Stack detected: python / fastapi + postgres + redis
|
|
51
|
+
⚙️ Generating Docker files...
|
|
52
|
+
✅ Dockerfile created
|
|
53
|
+
✅ .dockerignore created
|
|
54
|
+
✅ docker-compose.yaml created
|
|
55
|
+
|
|
56
|
+
🚀 Ready! Run: docker compose up -d
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `stackai scan` — Inspect detected stack without generating files
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
stackai scan
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
📦 Stack detected:
|
|
67
|
+
Language : python
|
|
68
|
+
Framework : fastapi
|
|
69
|
+
Port : 8000
|
|
70
|
+
Services : postgres, redis
|
|
71
|
+
Python ver. : 3.11
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `stackai debug` — Analyze a failing container with AI
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
stackai debug my-container
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
📋 Latest logs:
|
|
82
|
+
─────────────────────────────────────────
|
|
83
|
+
Error: could not connect to postgres...
|
|
84
|
+
─────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
🤖 Running AI analysis...
|
|
87
|
+
|
|
88
|
+
💡 Analysis:
|
|
89
|
+
1. PROBLEM : The app cannot connect to PostgreSQL
|
|
90
|
+
2. CAUSE : The postgres container is not ready when the app starts
|
|
91
|
+
3. FIX : Add `depends_on: [postgres]` in your docker-compose.yaml
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Supported Stacks
|
|
95
|
+
|
|
96
|
+
| Language | Detected Frameworks |
|
|
97
|
+
|----------|-------------------|
|
|
98
|
+
| Python | FastAPI, Flask, Django, generic |
|
|
99
|
+
| Node.js | Express, Next.js, Nuxt.js, React |
|
|
100
|
+
| Java | Spring Boot (Maven / Gradle) |
|
|
101
|
+
| Go | generic |
|
|
102
|
+
|
|
103
|
+
## Auto-detected Services
|
|
104
|
+
|
|
105
|
+
`postgres` · `redis` · `mongodb` · `mysql` · `elasticsearch`
|
|
106
|
+
|
|
107
|
+
## Requirements
|
|
108
|
+
|
|
109
|
+
- Python 3.10+
|
|
110
|
+
- Docker installed and running
|
|
111
|
+
- [Ollama](https://ollama.com) + `ollama pull llama3` (only for `stackai debug`)
|
|
112
|
+
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Pull requests are welcome. For major changes, please open an issue first.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
MIT © [El Mehdi Boutahar](https://github.com/MehdiB7)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
devai/__init__.py,sha256=Zn1KFblwuFHiDRdRAiRnDBRkbPttWh44jKa5zG2ov0E,22
|
|
2
|
+
devai/ai.py,sha256=K5T8_7BqkP5gLta0UsjoI-S-k5UGdALqy2DpPvWDfUg,852
|
|
3
|
+
devai/cli.py,sha256=tUVFY1RTTqO6X9JbNkfFGtPh_b5lJZn6UV8ye7pjalM,2322
|
|
4
|
+
devai/debugger.py,sha256=M1cRJD8c462EAxqbE7zMKf3ndM7PHFFpZDkwzmoFE7g,1909
|
|
5
|
+
devai/detector.py,sha256=dHoAdsLKKGg2MWFQaR34CPaqThcIcirxWEVlSg84_BI,18696
|
|
6
|
+
devai/generator.py,sha256=b5DQKaDBc--iKo4pUiecKlmjEqpd-bIWtsPihCn8xtw,8959
|
|
7
|
+
stackai_cli-0.2.0.dist-info/METADATA,sha256=QILe98aEsuBIfHSMLovrdbvCsHY76LicaPRg5JBGti0,3475
|
|
8
|
+
stackai_cli-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
+
stackai_cli-0.2.0.dist-info/entry_points.txt,sha256=NLmdAbALXQnZwvZM0Y8YwsqwH_pArHd6Da8f4pHcK8U,43
|
|
10
|
+
stackai_cli-0.2.0.dist-info/top_level.txt,sha256=Q-WXGK56TxXgAMLtaB1F77uzL4PBxPW22GkcKjJVdTc,6
|
|
11
|
+
stackai_cli-0.2.0.dist-info/RECORD,,
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: stackai-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: AI-powered Docker assistant — detects your stack, generates Dockerfile & compose, debugs containers.
|
|
5
|
-
Author-email: El Mehdi Boutahar <boutahar.elmehdi@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/MehdiB7/devai
|
|
8
|
-
Project-URL: Repository, https://github.com/MehdiB7/devai
|
|
9
|
-
Project-URL: Bug-Tracker, https://github.com/MehdiB7/devai/issues
|
|
10
|
-
Keywords: docker,ai,devops,cli,ollama,automation
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Environment :: Console
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Topic :: Software Development :: Build Tools
|
|
17
|
-
Requires-Python: >=3.10
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
Requires-Dist: click>=8.1
|
|
20
|
-
|
|
21
|
-
# devai
|
|
22
|
-
|
|
23
|
-
> AI-powered Docker assistant. No config needed.
|
|
24
|
-
|
|
25
|
-
`devai` analyse ton projet, génère le Dockerfile et le docker-compose, et debug tes containers avec l'IA — le tout en une commande.
|
|
26
|
-
|
|
27
|
-
## Installation
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
pip install stackai-cli
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
> Requiert [Ollama](https://ollama.com) installé et `ollama pull llama3` pour le mode debug AI.
|
|
34
|
-
|
|
35
|
-
## Utilisation
|
|
36
|
-
|
|
37
|
-
### `devai init` — Générer les fichiers Docker
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
cd mon-projet
|
|
41
|
-
stackai init
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
🔍 Analyse du projet...
|
|
46
|
-
✅ Stack détectée : python / fastapi + postgres + redis
|
|
47
|
-
⚙️ Génération des fichiers Docker...
|
|
48
|
-
✅ Dockerfile créé
|
|
49
|
-
✅ .dockerignore créé
|
|
50
|
-
✅ docker-compose.yaml créé
|
|
51
|
-
|
|
52
|
-
🚀 Prêt ! Lance avec : docker compose up -d
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### `devai scan` — Voir la stack sans rien générer
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
stackai scan
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
```
|
|
62
|
-
📦 Stack détectée :
|
|
63
|
-
Langage : python
|
|
64
|
-
Framework : fastapi
|
|
65
|
-
Port : 8000
|
|
66
|
-
Services : postgres, redis
|
|
67
|
-
Python ver. : 3.11
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### `devai debug` — Analyser un container en erreur
|
|
71
|
-
|
|
72
|
-
```bash
|
|
73
|
-
stackai debug mon-container
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
📋 Derniers logs :
|
|
78
|
-
─────────────────────────────────────────
|
|
79
|
-
Error: could not connect to postgres...
|
|
80
|
-
─────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
🤖 Analyse IA en cours...
|
|
83
|
-
|
|
84
|
-
💡 Analyse :
|
|
85
|
-
1. PROBLÈME : L'application ne peut pas se connecter à PostgreSQL
|
|
86
|
-
2. CAUSE : Le container postgres n'est pas encore prêt au démarrage de l'app
|
|
87
|
-
3. FIX : Ajoute `depends_on: [postgres]` dans ton docker-compose.yaml
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Stacks supportées
|
|
91
|
-
|
|
92
|
-
| Langage | Frameworks détectés |
|
|
93
|
-
|---------|-------------------|
|
|
94
|
-
| Python | FastAPI, Flask, Django, générique |
|
|
95
|
-
| Node.js | Express, Next.js, Nuxt.js, React |
|
|
96
|
-
| Java | Spring Boot (Maven/Gradle) |
|
|
97
|
-
| Go | générique |
|
|
98
|
-
|
|
99
|
-
## Services auto-détectés
|
|
100
|
-
|
|
101
|
-
`postgres` · `redis` · `mongodb` · `mysql` · `elasticsearch`
|
|
102
|
-
|
|
103
|
-
## Prérequis
|
|
104
|
-
|
|
105
|
-
- Python 3.10+
|
|
106
|
-
- Docker installé
|
|
107
|
-
- [Ollama](https://ollama.com) + `ollama pull llama3` (pour `devai debug`)
|
|
108
|
-
|
|
109
|
-
## Licence
|
|
110
|
-
|
|
111
|
-
MIT
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
devai/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
devai/ai.py,sha256=K5T8_7BqkP5gLta0UsjoI-S-k5UGdALqy2DpPvWDfUg,852
|
|
3
|
-
devai/cli.py,sha256=4jK_UZmlGHZmMH0tZOuTEn3qZnMkSurBzWqQVJ88zw8,2183
|
|
4
|
-
devai/debugger.py,sha256=M1cRJD8c462EAxqbE7zMKf3ndM7PHFFpZDkwzmoFE7g,1909
|
|
5
|
-
devai/detector.py,sha256=pPqwp3vsKDInoqrWNOLwTACymeq6ZdA1Tg4gy43reHI,4094
|
|
6
|
-
devai/generator.py,sha256=OCMGfp5qAl-rSvHsjsN65TNkHTMMLKXJX47K3a_CgK8,5627
|
|
7
|
-
stackai_cli-0.1.0.dist-info/METADATA,sha256=WxCh-fTIo67nfR7avAeLFXzKO8qBQLC9pF1SxT1TGZw,2967
|
|
8
|
-
stackai_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
9
|
-
stackai_cli-0.1.0.dist-info/entry_points.txt,sha256=NLmdAbALXQnZwvZM0Y8YwsqwH_pArHd6Da8f4pHcK8U,43
|
|
10
|
-
stackai_cli-0.1.0.dist-info/top_level.txt,sha256=Q-WXGK56TxXgAMLtaB1F77uzL4PBxPW22GkcKjJVdTc,6
|
|
11
|
-
stackai_cli-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|