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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.1.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
- """devai — AI-powered Docker assistant. No config needed."""
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("🔍 Analyse du projet...", fg="cyan"))
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("❌ Impossible de détecter la stack. Vérifie que tu es dans le bon dossier.", fg="red"))
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
- click.echo(click.style(f"✅ Stack détectée : {stack['language']} / {', '.join(stack['services'])}", fg="green"))
25
- click.echo(click.style("⚙️ Génération des fichiers Docker...", fg="cyan"))
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} créé", fg="green"))
31
+ click.echo(click.style(f" ✅ {f} created", fg="green"))
30
32
 
31
- click.echo(click.style("\n🚀 Prêt ! Lance avec : docker compose up -d", fg="bright_green", bold=True))
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"🔍 Lecture des logs de '{container_name}'...", fg="cyan"))
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 détectée :", fg="cyan", bold=True))
52
- click.echo(f" Langage : {stack['language']}")
53
- click.echo(f" Framework : {stack.get('framework', 'inconnu')}")
54
- click.echo(f" Port : {stack.get('port', 'inconnu')}")
55
- click.echo(f" Services : {', '.join(stack['services']) if stack['services'] else 'aucun'}")
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
- # Mapping dépendance → service Docker requis
7
- SERVICE_HINTS = {
8
- "psycopg2": "postgres",
9
- "asyncpg": "postgres",
10
- "sqlalchemy": "postgres",
11
- "pymongo": "mongodb",
12
- "motor": "mongodb",
13
- "redis": "redis",
14
- "celery": "redis",
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
- "kafka": "kafka",
17
- "mysql-connector": "mysql",
18
- "pymysql": "mysql",
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({SERVICE_HINTS[d] for d in deps if d in SERVICE_HINTS})
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({SERVICE_HINTS[d] for d in deps_lower if d in SERVICE_HINTS})
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
+ [![PyPI version](https://img.shields.io/pypi/v/stackai-cli.svg)](https://pypi.org/project/stackai-cli/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/stackai-cli.svg)](https://pypi.org/project/stackai-cli/)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](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,,