xgic-dev-cli 0.2.0__tar.gz → 0.2.1__tar.gz

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.
@@ -26,5 +26,9 @@ venv/
26
26
  .DS_Store
27
27
  Thumbs.db
28
28
 
29
+ # Lockfile: this repo is a PyPI library/CLI module, not an application.
30
+ # Applications should commit uv.lock; libraries gitignore it.
31
+ uv.lock
32
+
29
33
  # .xgic/ directory for temporary Grok Build status reports (mandatory per new standard). All reports are temporary working artifacts only and must never be committed. This is required for the mandatory session status report functionality.
30
34
  .xgic/
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: xgic-dev-cli
3
+ Version: 0.2.1
4
+ Summary: XGIC CLI Dev Container module - Docker Compose orchestration and xgic lifecycle commands (xgic.cli.dev).
5
+ Project-URL: Homepage, https://github.com/xgic/dev-cli
6
+ Project-URL: Repository, https://github.com/xgic/dev-cli
7
+ Project-URL: Issues, https://github.com/xgic/dev-cli/issues
8
+ Project-URL: Documentation, https://github.com/xgic/dev-cli#readme
9
+ Project-URL: Changelog, https://github.com/xgic/dev-cli/releases
10
+ Author: XGIC
11
+ License: Apache-2.0
12
+ License-File: LICENSE
13
+ License-File: NOTICE
14
+ Keywords: cli,dev-containers,docker-compose,orchestration,xgic
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved :: Apache Software License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.14
25
+ Requires-Dist: rich>=13.7
26
+ Requires-Dist: xgic-cli>=0.2.1
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
31
+ Provides-Extra: test
32
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # XGIC Dev Container CLI
36
+
37
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
38
+ [![PyPI](https://img.shields.io/pypi/v/xgic-dev-cli.svg)](https://pypi.org/project/xgic-dev-cli/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/xgic-dev-cli.svg)](https://pypi.org/project/xgic-dev-cli/)
40
+ [![CI](https://github.com/xgic/dev-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/xgic/dev-cli/actions/workflows/ci.yml)
41
+ [![Release](https://img.shields.io/github/v/release/xgic/dev-cli)](https://github.com/xgic/dev-cli/releases)
42
+
43
+ **Product-agnostic Docker Compose and Dev Container lifecycle for the modular [XGIC CLI](https://github.com/xgic/cli).**
44
+
45
+ Namespace: **`xgic.cli.dev`** · Console entry: **`xgic`** (plugins via entry points) · Brand: **XGIC CLI only** ([ADR-0005](https://github.com/xgic/ai/blob/main/docs/adr/0005-modular-xgic-cli-and-retirement-of-xde.md))
46
+
47
+ Standards hub: [xgic/ai](https://github.com/xgic/ai)
48
+
49
+ ---
50
+
51
+ ## Vision
52
+
53
+ Environment orchestration should be **predictable, documented, and AI-operable**. This package owns the **generic** lifecycle that every XGIC Dev Container product needs—`up`, `down`, `check`, logs, shell—without embedding Payload-specific or other product logic.
54
+
55
+ Humans get a stable command map. AI agents get the same map in [AGENTS.md](AGENTS.md) instead of inventing Make targets or host-global installs.
56
+
57
+ ---
58
+
59
+ ## Why this module exists
60
+
61
+ | Benefit | Detail |
62
+ |---------|--------|
63
+ | **Separation of concerns** | Core CLI stays thin; product commands live in domain modules |
64
+ | **Reuse across products** | Same lifecycle for Payload, GitLab tooling, and future stacks |
65
+ | **Configuration over hard-coding** | Compose file, project, service, and profile via flags or env |
66
+ | **Safe defaults for agents** | Destructive actions require explicit confirmation (`--yes`) |
67
+ | **Open-source rigor** | Apache-2.0, Python 3.14+, PyPI release discipline |
68
+
69
+ ---
70
+
71
+ ## Ecosystem
72
+
73
+ | Package | Role |
74
+ |---------|------|
75
+ | [xgic/cli](https://github.com/xgic/cli) | Thin core framework (`xgic`) |
76
+ | **This repo** | Dev Container / Docker Compose lifecycle (`xgic.cli.dev`) |
77
+ | [xgic/payload-cms-cli](https://github.com/xgic/payload-cms-cli) | Payload product commands (`xgic payload …`) |
78
+ | [xgic/payload-cms-dev](https://github.com/xgic/payload-cms-dev) | Payload Dev Container **image producer** |
79
+ | [xgic/payload-cms](https://github.com/xgic/payload-cms) | Payload **end-user template** (consumes GHCR image) |
80
+
81
+ ---
82
+
83
+ ## Quick start
84
+
85
+ ### Install (PyPI)
86
+
87
+ ```bash
88
+ uv pip install "xgic-dev-cli>=0.2.1"
89
+ xgic --help
90
+ xgic up --help
91
+ ```
92
+
93
+ ### Development (editable monorepo layout)
94
+
95
+ ```bash
96
+ uv pip install -e ../cli
97
+ uv pip install -e ".[dev]"
98
+ xgic --help
99
+ xgic up --help
100
+ ```
101
+
102
+ ### Typical session
103
+
104
+ ```bash
105
+ export XGIC_COMPOSE_FILE=.devcontainer/docker-compose.yml
106
+ export XGIC_COMPOSE_PROJECT=my-project
107
+ export XGIC_PRIMARY_SERVICE=app
108
+
109
+ xgic up --profile postgres
110
+ xgic check
111
+ xgic logs
112
+ xgic down
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Console commands
118
+
119
+ Registered on the core `xgic` entrypoint:
120
+
121
+ | Command | Purpose |
122
+ |---------|---------|
123
+ | `xgic up` | Start Compose services (detached) |
124
+ | `xgic down` | Stop services (volumes preserved) |
125
+ | `xgic build [--no-cache]` | Build or rebuild images |
126
+ | `xgic logs` | Follow service logs |
127
+ | `xgic shell` | Shell in primary service (`--service` if unset) |
128
+ | `xgic clean --yes` | Destructive: volumes + `.devcontainer/.env` |
129
+ | `xgic check [--json]` | Services + environment diagnostic |
130
+ | `xgic env [--json]` | Environment status (no secret regeneration) |
131
+
132
+ ### Common flags / environment
133
+
134
+ | Flag | Env var | Default |
135
+ |------|---------|---------|
136
+ | `--compose-file` | `XGIC_COMPOSE_FILE` | `.devcontainer/docker-compose.yml` |
137
+ | `--project` | `XGIC_COMPOSE_PROJECT` | Compose file `name:` (else `xgic-dev`) |
138
+ | `--service` | `XGIC_PRIMARY_SERVICE` | `devcontainer.json` `service` (else none) |
139
+ | `--profile` | `XGIC_COMPOSE_PROFILE` | (none; used by `up`) |
140
+
141
+ **Note:** Payload-specific env regenerate / setup lives in **[payload-cms-cli](https://github.com/xgic/payload-cms-cli)** (`xgic payload env`, `xgic payload setup`, …).
142
+
143
+ ---
144
+
145
+ ## Library API
146
+
147
+ ```python
148
+ from xgic.cli.core import EnvironmentContext
149
+ from xgic.cli.dev import DockerComposeController
150
+
151
+ env = EnvironmentContext.detect()
152
+ docker = DockerComposeController(
153
+ env=env,
154
+ compose_file=".devcontainer/docker-compose.yml",
155
+ project_name="my-project",
156
+ primary_service="app",
157
+ )
158
+ docker.up(profile="postgres")
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Requirements
164
+
165
+ - Python **3.14+**
166
+ - `xgic-cli` ≥ 0.2.1
167
+ - Docker / Docker Compose on the host when running lifecycle commands
168
+
169
+ ---
170
+
171
+ ## Publishing
172
+
173
+ Follow [python-package-release.md](https://github.com/xgic/ai/blob/main/docs/python-package-release.md).
174
+
175
+ Publish **after** `xgic-cli` for stack releases. Tags: `vX.Y.ZrcN` → TestPyPI; `vX.Y.Z` → PyPI.
176
+
177
+ ---
178
+
179
+ ## Working with AI assistants
180
+
181
+ - Prefer documented `xgic` lifecycle commands over ad-hoc `docker compose` one-liners in agent prompts.
182
+ - Point agents at [AGENTS.md](AGENTS.md) and the hub [catalog](https://github.com/xgic/ai/blob/main/docs/ecosystem/catalog.md).
183
+ - Public issues/PRs: [BASE-STANDARDS public-safe gate](https://github.com/xgic/ai/blob/main/docs/BASE-STANDARDS-FOR-ORCHESTRATED-REPOS.md).
184
+
185
+ ---
186
+
187
+ ## Contributing
188
+
189
+ PRs with human UI review only. Conventional Commits; labels required. See [CONTRIBUTING.md](CONTRIBUTING.md).
190
+
191
+ ---
192
+
193
+ ## License
194
+
195
+ Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
196
+ Copyright form: `Copyright 2026 XGIC`.
197
+
198
+ ---
199
+
200
+ **XGIC** — Modular CLI architecture for open-source developer platforms: thin core, domain modules, AI-operable commands.
@@ -0,0 +1,166 @@
1
+ # XGIC Dev Container CLI
2
+
3
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
4
+ [![PyPI](https://img.shields.io/pypi/v/xgic-dev-cli.svg)](https://pypi.org/project/xgic-dev-cli/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/xgic-dev-cli.svg)](https://pypi.org/project/xgic-dev-cli/)
6
+ [![CI](https://github.com/xgic/dev-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/xgic/dev-cli/actions/workflows/ci.yml)
7
+ [![Release](https://img.shields.io/github/v/release/xgic/dev-cli)](https://github.com/xgic/dev-cli/releases)
8
+
9
+ **Product-agnostic Docker Compose and Dev Container lifecycle for the modular [XGIC CLI](https://github.com/xgic/cli).**
10
+
11
+ Namespace: **`xgic.cli.dev`** · Console entry: **`xgic`** (plugins via entry points) · Brand: **XGIC CLI only** ([ADR-0005](https://github.com/xgic/ai/blob/main/docs/adr/0005-modular-xgic-cli-and-retirement-of-xde.md))
12
+
13
+ Standards hub: [xgic/ai](https://github.com/xgic/ai)
14
+
15
+ ---
16
+
17
+ ## Vision
18
+
19
+ Environment orchestration should be **predictable, documented, and AI-operable**. This package owns the **generic** lifecycle that every XGIC Dev Container product needs—`up`, `down`, `check`, logs, shell—without embedding Payload-specific or other product logic.
20
+
21
+ Humans get a stable command map. AI agents get the same map in [AGENTS.md](AGENTS.md) instead of inventing Make targets or host-global installs.
22
+
23
+ ---
24
+
25
+ ## Why this module exists
26
+
27
+ | Benefit | Detail |
28
+ |---------|--------|
29
+ | **Separation of concerns** | Core CLI stays thin; product commands live in domain modules |
30
+ | **Reuse across products** | Same lifecycle for Payload, GitLab tooling, and future stacks |
31
+ | **Configuration over hard-coding** | Compose file, project, service, and profile via flags or env |
32
+ | **Safe defaults for agents** | Destructive actions require explicit confirmation (`--yes`) |
33
+ | **Open-source rigor** | Apache-2.0, Python 3.14+, PyPI release discipline |
34
+
35
+ ---
36
+
37
+ ## Ecosystem
38
+
39
+ | Package | Role |
40
+ |---------|------|
41
+ | [xgic/cli](https://github.com/xgic/cli) | Thin core framework (`xgic`) |
42
+ | **This repo** | Dev Container / Docker Compose lifecycle (`xgic.cli.dev`) |
43
+ | [xgic/payload-cms-cli](https://github.com/xgic/payload-cms-cli) | Payload product commands (`xgic payload …`) |
44
+ | [xgic/payload-cms-dev](https://github.com/xgic/payload-cms-dev) | Payload Dev Container **image producer** |
45
+ | [xgic/payload-cms](https://github.com/xgic/payload-cms) | Payload **end-user template** (consumes GHCR image) |
46
+
47
+ ---
48
+
49
+ ## Quick start
50
+
51
+ ### Install (PyPI)
52
+
53
+ ```bash
54
+ uv pip install "xgic-dev-cli>=0.2.1"
55
+ xgic --help
56
+ xgic up --help
57
+ ```
58
+
59
+ ### Development (editable monorepo layout)
60
+
61
+ ```bash
62
+ uv pip install -e ../cli
63
+ uv pip install -e ".[dev]"
64
+ xgic --help
65
+ xgic up --help
66
+ ```
67
+
68
+ ### Typical session
69
+
70
+ ```bash
71
+ export XGIC_COMPOSE_FILE=.devcontainer/docker-compose.yml
72
+ export XGIC_COMPOSE_PROJECT=my-project
73
+ export XGIC_PRIMARY_SERVICE=app
74
+
75
+ xgic up --profile postgres
76
+ xgic check
77
+ xgic logs
78
+ xgic down
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Console commands
84
+
85
+ Registered on the core `xgic` entrypoint:
86
+
87
+ | Command | Purpose |
88
+ |---------|---------|
89
+ | `xgic up` | Start Compose services (detached) |
90
+ | `xgic down` | Stop services (volumes preserved) |
91
+ | `xgic build [--no-cache]` | Build or rebuild images |
92
+ | `xgic logs` | Follow service logs |
93
+ | `xgic shell` | Shell in primary service (`--service` if unset) |
94
+ | `xgic clean --yes` | Destructive: volumes + `.devcontainer/.env` |
95
+ | `xgic check [--json]` | Services + environment diagnostic |
96
+ | `xgic env [--json]` | Environment status (no secret regeneration) |
97
+
98
+ ### Common flags / environment
99
+
100
+ | Flag | Env var | Default |
101
+ |------|---------|---------|
102
+ | `--compose-file` | `XGIC_COMPOSE_FILE` | `.devcontainer/docker-compose.yml` |
103
+ | `--project` | `XGIC_COMPOSE_PROJECT` | Compose file `name:` (else `xgic-dev`) |
104
+ | `--service` | `XGIC_PRIMARY_SERVICE` | `devcontainer.json` `service` (else none) |
105
+ | `--profile` | `XGIC_COMPOSE_PROFILE` | (none; used by `up`) |
106
+
107
+ **Note:** Payload-specific env regenerate / setup lives in **[payload-cms-cli](https://github.com/xgic/payload-cms-cli)** (`xgic payload env`, `xgic payload setup`, …).
108
+
109
+ ---
110
+
111
+ ## Library API
112
+
113
+ ```python
114
+ from xgic.cli.core import EnvironmentContext
115
+ from xgic.cli.dev import DockerComposeController
116
+
117
+ env = EnvironmentContext.detect()
118
+ docker = DockerComposeController(
119
+ env=env,
120
+ compose_file=".devcontainer/docker-compose.yml",
121
+ project_name="my-project",
122
+ primary_service="app",
123
+ )
124
+ docker.up(profile="postgres")
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Requirements
130
+
131
+ - Python **3.14+**
132
+ - `xgic-cli` ≥ 0.2.1
133
+ - Docker / Docker Compose on the host when running lifecycle commands
134
+
135
+ ---
136
+
137
+ ## Publishing
138
+
139
+ Follow [python-package-release.md](https://github.com/xgic/ai/blob/main/docs/python-package-release.md).
140
+
141
+ Publish **after** `xgic-cli` for stack releases. Tags: `vX.Y.ZrcN` → TestPyPI; `vX.Y.Z` → PyPI.
142
+
143
+ ---
144
+
145
+ ## Working with AI assistants
146
+
147
+ - Prefer documented `xgic` lifecycle commands over ad-hoc `docker compose` one-liners in agent prompts.
148
+ - Point agents at [AGENTS.md](AGENTS.md) and the hub [catalog](https://github.com/xgic/ai/blob/main/docs/ecosystem/catalog.md).
149
+ - Public issues/PRs: [BASE-STANDARDS public-safe gate](https://github.com/xgic/ai/blob/main/docs/BASE-STANDARDS-FOR-ORCHESTRATED-REPOS.md).
150
+
151
+ ---
152
+
153
+ ## Contributing
154
+
155
+ PRs with human UI review only. Conventional Commits; labels required. See [CONTRIBUTING.md](CONTRIBUTING.md).
156
+
157
+ ---
158
+
159
+ ## License
160
+
161
+ Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
162
+ Copyright form: `Copyright 2026 XGIC`.
163
+
164
+ ---
165
+
166
+ **XGIC** — Modular CLI architecture for open-source developer platforms: thin core, domain modules, AI-operable commands.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "xgic-dev-cli"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "XGIC CLI Dev Container module - Docker Compose orchestration and xgic lifecycle commands (xgic.cli.dev)."
9
9
  readme = "README.md"
10
10
  license = { text = "Apache-2.0" }
@@ -13,7 +13,7 @@ authors = [
13
13
  ]
14
14
  requires-python = ">=3.14"
15
15
  dependencies = [
16
- "xgic-cli>=0.2.0",
16
+ "xgic-cli>=0.2.1",
17
17
  "rich>=13.7",
18
18
  ]
19
19
  keywords = [
@@ -2,7 +2,7 @@
2
2
 
3
3
  from xgic.cli.dev.docker import DockerComposeController
4
4
 
5
- __version__ = "0.2.0"
5
+ __version__ = "0.2.1"
6
6
 
7
7
  __all__ = [
8
8
  "DockerComposeController",
@@ -12,24 +12,34 @@ from xgic.cli.utils.output import print_info, print_success, print_warning
12
12
  def run_check(ctx: CommandContext) -> int:
13
13
  """Lightweight compose + environment health check."""
14
14
  docker = make_docker(ctx.env, ctx.args)
15
- services_ok = docker.services_running()
15
+ cli_ok = docker.docker_cli_available()
16
+ services_ok = docker.services_running() if cli_ok else False
16
17
  use_json = bool(getattr(ctx.args, "json", False))
17
18
 
18
19
  if use_json:
19
20
  result = {
21
+ "docker_cli_available": cli_ok,
20
22
  "services_running": services_ok,
21
23
  "compose_file": docker.compose_file,
22
24
  "project_name": docker.project_name,
23
25
  "primary_service": docker.primary_service,
24
26
  "environment": ctx.env.describe(),
25
- "overall_ok": services_ok,
27
+ "overall_ok": cli_ok and services_ok,
26
28
  }
27
29
  print(json.dumps(result, indent=2))
28
- return 0 if services_ok else 1
30
+ return 0 if result["overall_ok"] else 1
29
31
 
30
32
  print_info("Running environment health checks...")
31
33
 
32
- if services_ok:
34
+ if not cli_ok:
35
+ print_warning(
36
+ "Docker CLI not found on PATH. Cannot query Compose services."
37
+ )
38
+ print_info(
39
+ "Install a Docker client (Docker-outside-of-Docker) or run "
40
+ "lifecycle commands where `docker` is on PATH."
41
+ )
42
+ elif services_ok:
33
43
  print_success("Docker Compose services: running")
34
44
  else:
35
45
  print_warning(
@@ -37,13 +47,13 @@ def run_check(ctx: CommandContext) -> int:
37
47
  )
38
48
  print_info("Suggestion: Run `xgic up` to start services.")
39
49
 
40
- print_info(f"Compose file: {docker.compose_file}")
50
+ print_info(f"Docker Compose file: {docker.compose_file}")
41
51
  print_info(f"Project: {docker.project_name}")
42
52
  if docker.primary_service:
43
53
  print_info(f"Primary service: {docker.primary_service}")
44
54
  print_info("Environment context: " + ctx.env.describe())
45
55
 
46
- if services_ok:
56
+ if cli_ok and services_ok:
47
57
  print_success("Basic environment check passed")
48
58
  return 0
49
59
  return 1
@@ -16,7 +16,8 @@ def run_env(ctx: CommandContext) -> int:
16
16
  """Inspect development environment status (no regenerate in this module)."""
17
17
  docker = make_docker(ctx.env, ctx.args)
18
18
  env_file_exists = ENV_FILE.exists()
19
- services_ok = docker.services_running()
19
+ cli_ok = docker.docker_cli_available()
20
+ services_ok = docker.services_running() if cli_ok else False
20
21
  use_json = bool(getattr(ctx.args, "json", False))
21
22
 
22
23
  if use_json:
@@ -25,6 +26,7 @@ def run_env(ctx: CommandContext) -> int:
25
26
  {
26
27
  "env_file_exists": env_file_exists,
27
28
  "env_file": str(ENV_FILE),
29
+ "docker_cli_available": cli_ok,
28
30
  "services_running": services_ok,
29
31
  "compose_file": docker.compose_file,
30
32
  "project_name": docker.project_name,
@@ -40,14 +42,21 @@ def run_env(ctx: CommandContext) -> int:
40
42
  if env_file_exists:
41
43
  print_success(f".env file exists at {ENV_FILE}")
42
44
  else:
43
- print_info(f".env file not found at {ENV_FILE}")
45
+ print_info(
46
+ f".env file not found at {ENV_FILE} "
47
+ "(expected before first product setup)"
48
+ )
44
49
 
45
- if services_ok:
46
- print_success("Compose services: appear to be running")
50
+ if not cli_ok:
51
+ print_info(
52
+ "Docker CLI not found on PATH; Compose services were not queried"
53
+ )
54
+ elif services_ok:
55
+ print_success("Docker Compose services: appear to be running")
47
56
  else:
48
- print_info("Compose services: not detected as running")
57
+ print_info("Docker Compose services: not detected as running")
49
58
 
50
- print_info(f"Compose file: {docker.compose_file}")
59
+ print_info(f"Docker Compose file: {docker.compose_file}")
51
60
  print_info(f"Project: {docker.project_name}")
52
61
  print_info("Environment context: " + ctx.env.describe())
53
62
  return 0
@@ -7,14 +7,30 @@ from pathlib import Path
7
7
 
8
8
  from xgic.cli.app import CommandContext
9
9
  from xgic.cli.dev.context import make_docker, resolve_profile
10
- from xgic.cli.utils.output import print_info, print_success, print_warning
10
+ from xgic.cli.dev.docker import DockerComposeController
11
+ from xgic.cli.utils.output import print_error, print_info, print_success, print_warning
11
12
 
12
13
  ENV_FILE = Path(".devcontainer/.env")
13
14
 
15
+ _NO_DOCKER_CLI = (
16
+ "Docker CLI not found on PATH. Compose lifecycle commands need a Docker "
17
+ "client talking to the host engine (Docker-outside-of-Docker). This image "
18
+ "must install docker-ce-cli; do not enable Docker-in-Docker."
19
+ )
20
+
21
+
22
+ def _require_docker(docker: DockerComposeController) -> int | None:
23
+ if docker.docker_cli_available():
24
+ return None
25
+ print_error(_NO_DOCKER_CLI)
26
+ return 2
27
+
14
28
 
15
29
  def run_up(ctx: CommandContext) -> int:
16
30
  """Start compose services in detached mode."""
17
31
  docker = make_docker(ctx.env, ctx.args)
32
+ if (rc := _require_docker(docker)) is not None:
33
+ return rc
18
34
  profile = resolve_profile(ctx.args)
19
35
  print_info("Starting services...")
20
36
  docker.up(profile=profile)
@@ -25,6 +41,8 @@ def run_up(ctx: CommandContext) -> int:
25
41
  def run_down(ctx: CommandContext) -> int:
26
42
  """Stop services (volumes preserved)."""
27
43
  docker = make_docker(ctx.env, ctx.args)
44
+ if (rc := _require_docker(docker)) is not None:
45
+ return rc
28
46
  print_info("Stopping services...")
29
47
  docker.down()
30
48
  print_success("Services stopped (volumes preserved)")
@@ -34,6 +52,8 @@ def run_down(ctx: CommandContext) -> int:
34
52
  def run_build(ctx: CommandContext) -> int:
35
53
  """Build compose images."""
36
54
  docker = make_docker(ctx.env, ctx.args)
55
+ if (rc := _require_docker(docker)) is not None:
56
+ return rc
37
57
  no_cache = bool(getattr(ctx.args, "no_cache", False))
38
58
  print_info("Building services" + (" (no cache)" if no_cache else "") + "...")
39
59
  docker.build(no_cache=no_cache)
@@ -44,6 +64,8 @@ def run_build(ctx: CommandContext) -> int:
44
64
  def run_logs(ctx: CommandContext) -> int:
45
65
  """Follow logs for all services (blocks until interrupted)."""
46
66
  docker = make_docker(ctx.env, ctx.args)
67
+ if (rc := _require_docker(docker)) is not None:
68
+ return rc
47
69
  print_info("Following logs (press Ctrl+C to exit)...")
48
70
  docker.logs(follow=True)
49
71
  return 0
@@ -52,6 +74,8 @@ def run_logs(ctx: CommandContext) -> int:
52
74
  def run_shell(ctx: CommandContext) -> int:
53
75
  """Open an interactive shell in the primary service."""
54
76
  docker = make_docker(ctx.env, ctx.args)
77
+ if (rc := _require_docker(docker)) is not None:
78
+ return rc
55
79
  service = docker.primary_service
56
80
  if not service:
57
81
  print_warning(
@@ -70,6 +94,8 @@ def run_shell(ctx: CommandContext) -> int:
70
94
  def run_clean(ctx: CommandContext) -> int:
71
95
  """Full environment cleanup (volumes + .env). Extremely destructive."""
72
96
  docker = make_docker(ctx.env, ctx.args)
97
+ if (rc := _require_docker(docker)) is not None:
98
+ return rc
73
99
  yes = bool(getattr(ctx.args, "yes", False))
74
100
 
75
101
  print_warning(
@@ -0,0 +1,105 @@
1
+ """Build a DockerComposeController from CLI args / environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ import re
9
+ from pathlib import Path
10
+
11
+ from xgic.cli.core.environment import EnvironmentContext
12
+ from xgic.cli.dev.docker import (
13
+ DEFAULT_COMPOSE_FILE,
14
+ DEFAULT_PROJECT_NAME,
15
+ DockerComposeController,
16
+ )
17
+
18
+ ENV_COMPOSE_FILE = "XGIC_COMPOSE_FILE"
19
+ ENV_COMPOSE_PROJECT = "XGIC_COMPOSE_PROJECT"
20
+ ENV_PRIMARY_SERVICE = "XGIC_PRIMARY_SERVICE"
21
+ ENV_COMPOSE_PROFILE = "XGIC_COMPOSE_PROFILE"
22
+
23
+ DEFAULT_DEVCONTAINER_JSON = Path(".devcontainer/devcontainer.json")
24
+ _SAFE_NAME = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{0,62}$")
25
+
26
+
27
+ def resolve_compose_file(args: argparse.Namespace) -> str:
28
+ return (
29
+ getattr(args, "compose_file", None)
30
+ or os.environ.get(ENV_COMPOSE_FILE)
31
+ or DEFAULT_COMPOSE_FILE
32
+ )
33
+
34
+
35
+ def _is_compose_safe_name(name: str) -> bool:
36
+ return bool(name and _SAFE_NAME.fullmatch(name))
37
+
38
+
39
+ def compose_name_from_file(compose_path: Path) -> str | None:
40
+ """Parse top-level ``name:`` from a Compose file (best-effort)."""
41
+ if not compose_path.is_file():
42
+ return None
43
+ try:
44
+ for line in compose_path.read_text(encoding="utf-8").splitlines():
45
+ stripped = line.strip()
46
+ if stripped.startswith("#") or not stripped.startswith("name:"):
47
+ continue
48
+ raw = stripped.split(":", 1)[1].strip().strip("\"'")
49
+ if raw and _is_compose_safe_name(raw):
50
+ return raw
51
+ except OSError:
52
+ return None
53
+ return None
54
+
55
+
56
+ def primary_service_from_devcontainer(
57
+ path: Path = DEFAULT_DEVCONTAINER_JSON,
58
+ ) -> str | None:
59
+ """Read ``service`` from ``.devcontainer/devcontainer.json`` if present."""
60
+ if not path.is_file():
61
+ return None
62
+ try:
63
+ data = json.loads(path.read_text(encoding="utf-8"))
64
+ except (OSError, json.JSONDecodeError):
65
+ return None
66
+ if not isinstance(data, dict):
67
+ return None
68
+ raw = data.get("service")
69
+ if isinstance(raw, str) and raw.strip() and _is_compose_safe_name(raw.strip()):
70
+ return raw.strip()
71
+ return None
72
+
73
+
74
+ def resolve_project_name(args: argparse.Namespace) -> str:
75
+ named = getattr(args, "project", None) or os.environ.get(ENV_COMPOSE_PROJECT)
76
+ if isinstance(named, str) and named.strip() and _is_compose_safe_name(named.strip()):
77
+ return named.strip()
78
+ compose_file = resolve_compose_file(args)
79
+ from_file = compose_name_from_file(Path(compose_file))
80
+ if from_file:
81
+ return from_file
82
+ return DEFAULT_PROJECT_NAME
83
+
84
+
85
+ def resolve_primary_service(args: argparse.Namespace) -> str | None:
86
+ named = getattr(args, "service", None) or os.environ.get(ENV_PRIMARY_SERVICE)
87
+ if isinstance(named, str) and named.strip():
88
+ return named.strip()
89
+ return primary_service_from_devcontainer()
90
+
91
+
92
+ def resolve_profile(args: argparse.Namespace) -> str | None:
93
+ return getattr(args, "profile", None) or os.environ.get(ENV_COMPOSE_PROFILE)
94
+
95
+
96
+ def make_docker(
97
+ env: EnvironmentContext, args: argparse.Namespace
98
+ ) -> DockerComposeController:
99
+ """Construct a controller from CommandContext-compatible args."""
100
+ return DockerComposeController(
101
+ env=env,
102
+ compose_file=resolve_compose_file(args),
103
+ project_name=resolve_project_name(args),
104
+ primary_service=resolve_primary_service(args),
105
+ )
@@ -1,12 +1,14 @@
1
1
  """Docker Compose orchestration for Dev Container environments.
2
2
 
3
- Product-agnostic Compose controller. Callers inject compose file, project
4
- name, primary service, and optional profile. Payload CMS–specific defaults
5
- and config readers live in ``xgic.cli.payload`` (xgic/payload-cms-cli).
3
+ Product-agnostic Docker Compose controller. Callers inject compose file,
4
+ project name, primary service, and optional profile. Payload CMS–specific
5
+ defaults and config readers live in ``xgic.cli.payload``
6
+ (xgic/payload-cms-cli).
6
7
  """
7
8
 
8
9
  from __future__ import annotations
9
10
 
11
+ import shutil
10
12
  import subprocess
11
13
  from dataclasses import dataclass
12
14
 
@@ -25,6 +27,10 @@ class DockerComposeController:
25
27
  project_name: str = DEFAULT_PROJECT_NAME
26
28
  primary_service: str | None = None
27
29
 
30
+ def docker_cli_available(self) -> bool:
31
+ """Return True if a ``docker`` executable is on PATH."""
32
+ return shutil.which("docker") is not None
33
+
28
34
  def _run_compose(
29
35
  self,
30
36
  *args: str,
@@ -65,7 +71,9 @@ class DockerComposeController:
65
71
  if target is None:
66
72
  return True
67
73
  return target in running
68
- except (subprocess.CalledProcessError, FileNotFoundError):
74
+ except FileNotFoundError:
75
+ return False
76
+ except subprocess.CalledProcessError:
69
77
  return False
70
78
 
71
79
  def up(
@@ -21,22 +21,22 @@ def _common_parent() -> argparse.ArgumentParser:
21
21
  parent.add_argument(
22
22
  "--compose-file",
23
23
  metavar="PATH",
24
- help="Compose file path (or XGIC_COMPOSE_FILE)",
24
+ help="Docker Compose file path (or XGIC_COMPOSE_FILE)",
25
25
  )
26
26
  parent.add_argument(
27
27
  "--project",
28
28
  metavar="NAME",
29
- help="Compose project name (or XGIC_COMPOSE_PROJECT)",
29
+ help="Docker Compose project name (or XGIC_COMPOSE_PROJECT)",
30
30
  )
31
31
  parent.add_argument(
32
32
  "--service",
33
33
  metavar="NAME",
34
- help="Primary compose service (or XGIC_PRIMARY_SERVICE)",
34
+ help="Primary Docker Compose service (or XGIC_PRIMARY_SERVICE)",
35
35
  )
36
36
  parent.add_argument(
37
37
  "--profile",
38
38
  metavar="NAME",
39
- help="Compose profile for up (or XGIC_COMPOSE_PROFILE)",
39
+ help="Docker Compose profile for up (or XGIC_COMPOSE_PROFILE)",
40
40
  )
41
41
  return parent
42
42
 
@@ -64,7 +64,7 @@ def register(
64
64
  build = subparsers.add_parser(
65
65
  "build",
66
66
  parents=[parent],
67
- help="Build or rebuild compose services",
67
+ help="Build or rebuild Docker Compose services",
68
68
  )
69
69
  build.add_argument(
70
70
  "--no-cache",
@@ -76,7 +76,7 @@ def register(
76
76
  logs = subparsers.add_parser(
77
77
  "logs",
78
78
  parents=[parent],
79
- help="Follow logs for compose services",
79
+ help="Follow logs for Docker Compose services",
80
80
  )
81
81
  logs.set_defaults(func=run_logs)
82
82
 
@@ -102,7 +102,7 @@ def register(
102
102
  check = subparsers.add_parser(
103
103
  "check",
104
104
  parents=[parent],
105
- help="Diagnostic: compose services + environment context",
105
+ help="Diagnostic: Docker Compose services + environment context",
106
106
  )
107
107
  check.add_argument(
108
108
  "--json",
@@ -1,114 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: xgic-dev-cli
3
- Version: 0.2.0
4
- Summary: XGIC CLI Dev Container module - Docker Compose orchestration and xgic lifecycle commands (xgic.cli.dev).
5
- Project-URL: Homepage, https://github.com/xgic/dev-cli
6
- Project-URL: Repository, https://github.com/xgic/dev-cli
7
- Project-URL: Issues, https://github.com/xgic/dev-cli/issues
8
- Project-URL: Documentation, https://github.com/xgic/dev-cli#readme
9
- Project-URL: Changelog, https://github.com/xgic/dev-cli/releases
10
- Author: XGIC
11
- License: Apache-2.0
12
- License-File: LICENSE
13
- License-File: NOTICE
14
- Keywords: cli,dev-containers,docker-compose,orchestration,xgic
15
- Classifier: Development Status :: 3 - Alpha
16
- Classifier: Environment :: Console
17
- Classifier: Intended Audience :: Developers
18
- Classifier: License :: OSI Approved :: Apache Software License
19
- Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3
21
- Classifier: Programming Language :: Python :: 3.14
22
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Classifier: Typing :: Typed
24
- Requires-Python: >=3.14
25
- Requires-Dist: rich>=13.7
26
- Requires-Dist: xgic-cli>=0.2.0
27
- Provides-Extra: dev
28
- Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
29
- Requires-Dist: pytest>=8.0.0; extra == 'dev'
30
- Requires-Dist: ruff>=0.4.0; extra == 'dev'
31
- Provides-Extra: test
32
- Requires-Dist: pytest>=8.0.0; extra == 'test'
33
- Description-Content-Type: text/markdown
34
-
35
- # XGIC Dev Container CLI
36
-
37
- [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
38
-
39
- **XGIC Dev Container CLI** (`xgic.cli.dev`) provides Docker Compose orchestration and Dev Container–oriented **`xgic` subcommands** for the modular [XGIC CLI](https://github.com/xgic/cli).
40
-
41
- Architecture: [ADR-0005](https://github.com/xgic/ai/blob/main/docs/adr/0005-modular-xgic-cli-and-retirement-of-xde.md).
42
-
43
- **Publishing to PyPI:** [python-package-release.md](https://github.com/xgic/ai/blob/main/docs/python-package-release.md)
44
- (publish **after** `xgic-cli` for stack releases). Tags: `vX.Y.ZrcN` → TestPyPI; `vX.Y.Z` → PyPI.
45
-
46
- | Package | Role |
47
- |---------|------|
48
- | [xgic/cli](https://github.com/xgic/cli) | Thin core framework (`xgic`) |
49
- | **This repo** | Dev Container / Compose + lifecycle commands (`xgic.cli.dev`) |
50
- | [xgic/payload-cms-cli](https://github.com/xgic/payload-cms-cli) | Payload CMS product module |
51
-
52
- ## Status
53
-
54
- **0.2.0 — B3 lifecycle commands.** Product-agnostic Compose library + registered `xgic` subcommands. Payload CMS–specific env regenerate / setup remains in **payload-cms-cli**.
55
-
56
- ## Requirements
57
-
58
- - Python **3.14+**
59
- - `xgic-cli` ≥ 0.2.0
60
- - Docker / Docker Compose on the host when running lifecycle commands
61
-
62
- ## Install (development)
63
-
64
- ```bash
65
- python -m pip install -e ../cli
66
- python -m pip install -e ".[dev]"
67
- xgic --help
68
- xgic up --help
69
- ```
70
-
71
- ## Console commands (via entry point)
72
-
73
- Installed with this package, registered on the core `xgic` entrypoint:
74
-
75
- | Command | Purpose |
76
- |---------|---------|
77
- | `xgic up` | Start compose services (detached) |
78
- | `xgic down` | Stop services (volumes preserved) |
79
- | `xgic build [--no-cache]` | Build images |
80
- | `xgic logs` | Follow logs |
81
- | `xgic shell` | Shell in primary service (`--service` required if not set) |
82
- | `xgic clean --yes` | Destructive: volumes + `.devcontainer/.env` |
83
- | `xgic check [--json]` | Services + environment diagnostic |
84
- | `xgic env [--json]` | Environment status (no secret regeneration) |
85
-
86
- Common flags (or env vars):
87
-
88
- | Flag | Env var | Default |
89
- |------|---------|---------|
90
- | `--compose-file` | `XGIC_COMPOSE_FILE` | `.devcontainer/docker-compose.yml` |
91
- | `--project` | `XGIC_COMPOSE_PROJECT` | `xgic-dev` |
92
- | `--service` | `XGIC_PRIMARY_SERVICE` | (none) |
93
- | `--profile` | `XGIC_COMPOSE_PROFILE` | (none; used by `up`) |
94
-
95
- ## Library API
96
-
97
- ```python
98
- from xgic.cli.core import EnvironmentContext
99
- from xgic.cli.dev import DockerComposeController
100
-
101
- env = EnvironmentContext.detect()
102
- docker = DockerComposeController(
103
- env=env,
104
- compose_file=".devcontainer/docker-compose.yml",
105
- project_name="my-project",
106
- primary_service="app",
107
- )
108
- docker.up(profile="postgres")
109
- ```
110
-
111
- ## License
112
-
113
- Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
114
- Copyright form: `Copyright 2026 XGIC`.
@@ -1,80 +0,0 @@
1
- # XGIC Dev Container CLI
2
-
3
- [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
-
5
- **XGIC Dev Container CLI** (`xgic.cli.dev`) provides Docker Compose orchestration and Dev Container–oriented **`xgic` subcommands** for the modular [XGIC CLI](https://github.com/xgic/cli).
6
-
7
- Architecture: [ADR-0005](https://github.com/xgic/ai/blob/main/docs/adr/0005-modular-xgic-cli-and-retirement-of-xde.md).
8
-
9
- **Publishing to PyPI:** [python-package-release.md](https://github.com/xgic/ai/blob/main/docs/python-package-release.md)
10
- (publish **after** `xgic-cli` for stack releases). Tags: `vX.Y.ZrcN` → TestPyPI; `vX.Y.Z` → PyPI.
11
-
12
- | Package | Role |
13
- |---------|------|
14
- | [xgic/cli](https://github.com/xgic/cli) | Thin core framework (`xgic`) |
15
- | **This repo** | Dev Container / Compose + lifecycle commands (`xgic.cli.dev`) |
16
- | [xgic/payload-cms-cli](https://github.com/xgic/payload-cms-cli) | Payload CMS product module |
17
-
18
- ## Status
19
-
20
- **0.2.0 — B3 lifecycle commands.** Product-agnostic Compose library + registered `xgic` subcommands. Payload CMS–specific env regenerate / setup remains in **payload-cms-cli**.
21
-
22
- ## Requirements
23
-
24
- - Python **3.14+**
25
- - `xgic-cli` ≥ 0.2.0
26
- - Docker / Docker Compose on the host when running lifecycle commands
27
-
28
- ## Install (development)
29
-
30
- ```bash
31
- python -m pip install -e ../cli
32
- python -m pip install -e ".[dev]"
33
- xgic --help
34
- xgic up --help
35
- ```
36
-
37
- ## Console commands (via entry point)
38
-
39
- Installed with this package, registered on the core `xgic` entrypoint:
40
-
41
- | Command | Purpose |
42
- |---------|---------|
43
- | `xgic up` | Start compose services (detached) |
44
- | `xgic down` | Stop services (volumes preserved) |
45
- | `xgic build [--no-cache]` | Build images |
46
- | `xgic logs` | Follow logs |
47
- | `xgic shell` | Shell in primary service (`--service` required if not set) |
48
- | `xgic clean --yes` | Destructive: volumes + `.devcontainer/.env` |
49
- | `xgic check [--json]` | Services + environment diagnostic |
50
- | `xgic env [--json]` | Environment status (no secret regeneration) |
51
-
52
- Common flags (or env vars):
53
-
54
- | Flag | Env var | Default |
55
- |------|---------|---------|
56
- | `--compose-file` | `XGIC_COMPOSE_FILE` | `.devcontainer/docker-compose.yml` |
57
- | `--project` | `XGIC_COMPOSE_PROJECT` | `xgic-dev` |
58
- | `--service` | `XGIC_PRIMARY_SERVICE` | (none) |
59
- | `--profile` | `XGIC_COMPOSE_PROFILE` | (none; used by `up`) |
60
-
61
- ## Library API
62
-
63
- ```python
64
- from xgic.cli.core import EnvironmentContext
65
- from xgic.cli.dev import DockerComposeController
66
-
67
- env = EnvironmentContext.detect()
68
- docker = DockerComposeController(
69
- env=env,
70
- compose_file=".devcontainer/docker-compose.yml",
71
- project_name="my-project",
72
- primary_service="app",
73
- )
74
- docker.up(profile="postgres")
75
- ```
76
-
77
- ## License
78
-
79
- Apache License 2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
80
- Copyright form: `Copyright 2026 XGIC`.
@@ -1,54 +0,0 @@
1
- """Build a DockerComposeController from CLI args / environment."""
2
-
3
- from __future__ import annotations
4
-
5
- import argparse
6
- import os
7
-
8
- from xgic.cli.core.environment import EnvironmentContext
9
- from xgic.cli.dev.docker import (
10
- DEFAULT_COMPOSE_FILE,
11
- DEFAULT_PROJECT_NAME,
12
- DockerComposeController,
13
- )
14
-
15
- ENV_COMPOSE_FILE = "XGIC_COMPOSE_FILE"
16
- ENV_COMPOSE_PROJECT = "XGIC_COMPOSE_PROJECT"
17
- ENV_PRIMARY_SERVICE = "XGIC_PRIMARY_SERVICE"
18
- ENV_COMPOSE_PROFILE = "XGIC_COMPOSE_PROFILE"
19
-
20
-
21
- def resolve_compose_file(args: argparse.Namespace) -> str:
22
- return (
23
- getattr(args, "compose_file", None)
24
- or os.environ.get(ENV_COMPOSE_FILE)
25
- or DEFAULT_COMPOSE_FILE
26
- )
27
-
28
-
29
- def resolve_project_name(args: argparse.Namespace) -> str:
30
- return (
31
- getattr(args, "project", None)
32
- or os.environ.get(ENV_COMPOSE_PROJECT)
33
- or DEFAULT_PROJECT_NAME
34
- )
35
-
36
-
37
- def resolve_primary_service(args: argparse.Namespace) -> str | None:
38
- return getattr(args, "service", None) or os.environ.get(ENV_PRIMARY_SERVICE)
39
-
40
-
41
- def resolve_profile(args: argparse.Namespace) -> str | None:
42
- return getattr(args, "profile", None) or os.environ.get(ENV_COMPOSE_PROFILE)
43
-
44
-
45
- def make_docker(
46
- env: EnvironmentContext, args: argparse.Namespace
47
- ) -> DockerComposeController:
48
- """Construct a controller from CommandContext-compatible args."""
49
- return DockerComposeController(
50
- env=env,
51
- compose_file=resolve_compose_file(args),
52
- project_name=resolve_project_name(args),
53
- primary_service=resolve_primary_service(args),
54
- )
File without changes
File without changes