envshield 4.3.0__tar.gz → 4.4.0__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.
Files changed (65) hide show
  1. {envshield-4.3.0 → envshield-4.4.0}/PKG-INFO +72 -9
  2. {envshield-4.3.0 → envshield-4.4.0}/README.md +70 -7
  3. envshield-4.4.0/envshield/__init__.py +1 -0
  4. {envshield-4.3.0 → envshield-4.4.0}/envshield/cli.py +177 -51
  5. {envshield-4.3.0 → envshield-4.4.0}/envshield/config/manager.py +25 -11
  6. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/doctor.py +49 -11
  7. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/exceptions.py +1 -5
  8. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/generator.py +25 -12
  9. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/hooks_manager.py +4 -4
  10. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/importer.py +7 -8
  11. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/inspector.py +3 -2
  12. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/scanner.py +86 -24
  13. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/schema_manager.py +72 -9
  14. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/schema_types.py +1 -0
  15. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/service_discovery.py +15 -4
  16. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/setup_manager.py +18 -13
  17. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_base.py +1 -1
  18. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_docker_compose.py +5 -5
  19. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_dotenv.py +2 -1
  20. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_kubernetes.py +4 -3
  21. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_python.py +1 -0
  22. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_doctor.py +6 -11
  23. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_generator.py +10 -4
  24. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_importer.py +6 -15
  25. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_scanner_compliance.py +8 -20
  26. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_schema_manager.py +28 -44
  27. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_schema_types.py +8 -3
  28. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_service_discovery.py +14 -7
  29. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_service_manager.py +10 -8
  30. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_setup_manager.py +13 -21
  31. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/parsers/test_docker_compose_parser.py +9 -22
  32. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/parsers/test_kubernetes_parser.py +4 -10
  33. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/parsers/test_python_parser.py +1 -9
  34. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/test_cli.py +169 -16
  35. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/test_config_manager.py +36 -37
  36. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/test_diff_aware_scanning.py +23 -11
  37. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/test_git_utils.py +15 -5
  38. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/test_service_cli.py +8 -2
  39. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/PKG-INFO +72 -9
  40. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/requires.txt +1 -1
  41. {envshield-4.3.0 → envshield-4.4.0}/pyproject.toml +19 -2
  42. envshield-4.3.0/envshield/__init__.py +0 -1
  43. {envshield-4.3.0 → envshield-4.4.0}/LICENSE.md +0 -0
  44. {envshield-4.3.0 → envshield-4.4.0}/envshield/__main__.py +0 -0
  45. {envshield-4.3.0 → envshield-4.4.0}/envshield/config/__init.py +0 -0
  46. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/__init__.py +0 -0
  47. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/file_updater.py +0 -0
  48. {envshield-4.3.0 → envshield-4.4.0}/envshield/core/service_manager.py +0 -0
  49. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/__init__.py +0 -0
  50. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/_deployment.py +0 -0
  51. {envshield-4.3.0 → envshield-4.4.0}/envshield/parsers/factory.py +0 -0
  52. {envshield-4.3.0 → envshield-4.4.0}/envshield/state.py +1 -1
  53. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/__init__.py +0 -0
  54. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/conftest.py +0 -0
  55. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/__init__.py +0 -0
  56. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/core/test_file_updater.py +0 -0
  57. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/parsers/__init__.py +0 -0
  58. {envshield-4.3.0 → envshield-4.4.0}/envshield/tests/parsers/test_dotenv_parser.py +0 -0
  59. {envshield-4.3.0 → envshield-4.4.0}/envshield/utils/__init__.py +0 -0
  60. {envshield-4.3.0 → envshield-4.4.0}/envshield/utils/git_utils.py +0 -0
  61. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/SOURCES.txt +0 -0
  62. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/dependency_links.txt +0 -0
  63. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/entry_points.txt +0 -0
  64. {envshield-4.3.0 → envshield-4.4.0}/envshield.egg-info/top_level.txt +0 -0
  65. {envshield-4.3.0 → envshield-4.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: envshield
3
- Version: 4.3.0
3
+ Version: 4.4.0
4
4
  Summary: EnvShield: A CLI for secure local environment management and secret prevention.
5
5
  Author-email: Rabbil Yasar Sajal <rabbilyasar@gmail.com>
6
6
  License: MIT License
@@ -47,7 +47,7 @@ Requires-Dist: questionary<3.0,>=2.0
47
47
  Requires-Dist: typer[all]<1.0,>=0.9
48
48
  Requires-Dist: toml<1.0,>=0.10
49
49
  Provides-Extra: dev
50
- Requires-Dist: ruff; extra == "dev"
50
+ Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
51
51
  Requires-Dist: pytest; extra == "dev"
52
52
  Requires-Dist: pytest-mock; extra == "dev"
53
53
  Requires-Dist: bump2version; extra == "dev"
@@ -92,6 +92,7 @@ It works the same way whether you have one repo with one `.env` file, or a monor
92
92
  - [Command reference](#command-reference)
93
93
  - [Core commands](#core-commands)
94
94
  - [Monorepo: managing multiple services (monorepo)](#monorepo-managing-multiple-services)
95
+ - [Machine-readable output (`--json`)](#machine-readable-output---json)
95
96
  - [Typed config code generation](#typed-config-code-generation)
96
97
  - [Validating deployment manifests](#validating-deployment-manifests)
97
98
  - [Secret scanning and git hooks](#secret-scanning-and-git-hooks)
@@ -360,12 +361,12 @@ Everything a single-service project ever needs. `--service` shows up on most of
360
361
  |---|---|
361
362
  | `envshield init [--force/-f]` | Detects your framework and builds `env.schema.toml` from a real config source if it finds one, otherwise a framework-aware template. Also scaffolds `envshield.yml`, updates `.gitignore`, and offers to install git hooks. Auto-registers a root-level `docker-compose.yml` as the project's deployment manifest if it finds one. `--force` re-runs on a project that already has a config (with a confirmation before overwriting). |
362
363
  | `envshield import <file> [--output/-o PATH] [--force/-f] [--interactive] [--service NAME]` | Runs the same real-variable analysis `init` does automatically, as its own command — for re-importing after your code gains new variables, pointing at a file `init` wouldn't have found, or adding `--interactive` to confirm each secret/type classification by hand instead of accepting the automatic guess. `--output` changes where the schema is written (defaults to `env.schema.toml`, or the target service's schema path with `--service`). |
363
- | `envshield check [file] [--service NAME] [--container NAME]` | Validates a local file (or, if omitted, the project's/service's default local file *and* its registered deployment manifest, if any) against the schema. `file` can be a plain `.env`, a Python config module, a docker-compose file, or a Kubernetes manifest. `--container` picks which service/container to check in a manifest that declares more than one (tried against `--service`'s name automatically first). Exits non-zero on any drift — safe to use as a CI gate. |
364
- | `envshield doctor [--fix] [--service NAME]` | Runs every health check at once (see below) and reports a summary. `--fix` interactively offers to fix whatever it can — re-running `init`, regenerating the template, installing the git hook, or running `setup` to fill in missing/invalid local values. Exits non-zero if anything's still broken afterward. |
364
+ | `envshield check [file] [--service NAME] [--container NAME] [--json]` | Validates a local file (or, if omitted, the project's/service's default local file *and* its registered deployment manifest, if any) against the schema. `file` can be a plain `.env`, a Python config module, a docker-compose file, or a Kubernetes manifest. `--container` picks which service/container to check in a manifest that declares more than one (tried against `--service`'s name automatically first). Exits non-zero on any drift — safe to use as a CI gate. `--json` prints a machine-readable result instead (see below) and never falls into an interactive service picker. |
365
+ | `envshield doctor [--fix] [--service NAME] [--json]` | Runs every health check at once (see below) and reports a summary. `--fix` interactively offers to fix whatever it can — re-running `init`, regenerating the template, installing the git hook, or running `setup` to fill in missing/invalid local values. Exits non-zero if anything's still broken afterward. `--json` is incompatible with `--fix` (an interactive confirm prompt makes no sense in a machine-readable mode). |
365
366
  | `envshield setup [output_file] [--service NAME]` | Interactive onboarding wizard: walks through every variable that's missing, blank, or has an existing value the schema no longer allows, prompting with the variable's description, masking secret input, and offering a picker for `enum` fields. Leaves everything already correct untouched. |
366
367
  | `envshield schema sync [--service NAME]` | Regenerates `.env.example` from the schema (a dotenv project), or patches a Python-module local file in place to declare any schema variable it's missing (never rewrites it wholesale — only appends/patches the specific lines it owns). `import` already calls this automatically for you when it changes a project's/service's real schema, so you'll rarely need to run it by hand except after a manual schema edit. |
367
- | `envshield generate [output_file] [--lang/-l python\|typescript] [--force/-f] [--service NAME]` | Compiles the schema into a typed, validated config module. `--lang` is auto-detected from your project (Next.js/Vite/Node.js → TypeScript, everything else → Python) if omitted. Defaults to writing `config.py`/`config.ts`; `--force` overwrites an existing output file. See [Typed config code generation](#typed-config-code-generation). |
368
- | `envshield scan [paths...] [--staged] [--config/-c PATH] [--exclude/-e PATTERN]` [--service NAME] | Scans code for hardcoded secrets and for env vars used in code (`os.getenv`, `os.environ.get`, `process.env.X`) but never declared in the schema. `--staged` scans only what's staged for the next commit (what the pre-commit hook runs); `--exclude` (repeatable) adds glob patterns to skip, on top of whatever `secret_scanning.exclude_files` is set in `envshield.yml`. See [Secret scanning and git hooks](#secret-scanning-and-git-hooks). |
368
+ | `envshield generate [output_file] [--lang/-l python\|typescript] [--force/-f] [--service NAME]` | Compiles the schema into a typed, validated config module. `--lang` is auto-detected from your project (Next.js/Vite/Node.js → TypeScript; Python/Django/Flask, or nothing detected → Python) if omitted. A detected ecosystem with no codegen target at all (currently: Go) errors and asks for `--lang` explicitly, rather than silently guessing Python. Defaults to writing `config.py`/`config.ts`; `--force` overwrites an existing output file. See [Typed config code generation](#typed-config-code-generation). |
369
+ | `envshield scan [paths...] [--staged] [--config/-c PATH] [--exclude/-e PATTERN] [--service NAME] [--json]` | Scans code for hardcoded secrets and for env vars used in code (`os.getenv`, `os.environ.get`, `process.env.X`) but never declared in the schema. `--staged` scans only what's staged for the next commit (what the pre-commit hook runs); `--exclude` (repeatable) adds glob patterns to skip, on top of whatever `secret_scanning.exclude_files` is set in `envshield.yml`. See [Secret scanning and git hooks](#secret-scanning-and-git-hooks). |
369
370
  | `envshield install-hook` | Installs both git hooks by hand, without going through `init`/`setup`/`service discover`'s interactive prompt. |
370
371
  | `envshield --version` / `-v` | Prints the installed version and exits. |
371
372
 
@@ -390,6 +391,63 @@ services:
390
391
 
391
392
  ---
392
393
 
394
+ ## Machine-readable output (`--json`)
395
+
396
+ `check`, `doctor`, and `scan` all accept `--json`: every Rich table/panel/progress-bar is suppressed, and exactly one JSON object is printed to stdout instead. Exit codes are unchanged (non-zero on any issue), so `--json` is a drop-in for CI gates, dashboards, or an agent loop that needs to branch on the result instead of parsing colored text. If multiple services are configured and `--service` isn't given, `--json` runs every service automatically rather than falling into the interactive "Which service?" picker.
397
+
398
+ ```bash
399
+ envshield check --json
400
+ ```
401
+
402
+ ```json
403
+ {
404
+ "success": false,
405
+ "results": [
406
+ {
407
+ "file": ".env",
408
+ "service": null,
409
+ "clean": false,
410
+ "missing": [],
411
+ "blank": ["SECRETS_ENCRYPTION_KEY"],
412
+ "invalid": {},
413
+ "extra": []
414
+ }
415
+ ]
416
+ }
417
+ ```
418
+
419
+ `doctor --json` returns the same per-service shape, with each service's individual health checks instead of a variable diff (`--fix` is rejected together with `--json` — an interactive confirm prompt has no place in a machine-readable mode):
420
+
421
+ ```json
422
+ {
423
+ "success": true,
424
+ "results": [
425
+ {
426
+ "service": "hermes",
427
+ "passed": true,
428
+ "checks": [
429
+ {"name": "Configuration Files", "passed": true, "message": "Found and accessible."}
430
+ ]
431
+ }
432
+ ]
433
+ }
434
+ ```
435
+
436
+ `scan --json` returns one flat object (scan doesn't fan out per-service the way check/doctor do — it scans a set of files against whichever schema each one belongs to in a single pass):
437
+
438
+ ```json
439
+ {
440
+ "clean": false,
441
+ "secrets": [
442
+ {"file_path": "./config.py", "line_num": 12, "secret_type": "Generic API Key", "line_content": "..."}
443
+ ],
444
+ "undeclared_variables": [],
445
+ "skipped_files": []
446
+ }
447
+ ```
448
+
449
+ ---
450
+
393
451
  ## Typed config code generation
394
452
 
395
453
  Stop writing `os.getenv("DATABASE_URL")` — untyped, unvalidated, and silently `None` on a typo — and generate a real module instead:
@@ -405,6 +463,7 @@ Source of truth: env.schema.toml. Regenerate with: envshield generate
405
463
 
406
464
  Requires: pip install pydantic pydantic-settings
407
465
  """
466
+
408
467
  from pydantic import Field, SecretStr
409
468
  from pydantic_settings import BaseSettings, SettingsConfigDict
410
469
 
@@ -417,11 +476,13 @@ class Settings(BaseSettings):
417
476
  )
418
477
 
419
478
  database_url: SecretStr = Field(
420
- ..., description="PostgreSQL connection string for the API", alias="DATABASE_URL"
479
+ ...,
480
+ description="PostgreSQL connection string for the API",
481
+ alias="DATABASE_URL",
421
482
  )
422
483
 
423
484
  api_port: int = Field(
424
- '5000', description="Port the API listens on", alias="API_PORT", ge=1, le=65535
485
+ "5000", description="Port the API listens on", alias="API_PORT", ge=1, le=65535
425
486
  )
426
487
 
427
488
 
@@ -431,7 +492,9 @@ settings = Settings()
431
492
  ```python
432
493
  from config import settings
433
494
 
434
- db = psycopg2.connect(settings.database_url.get_secret_value()) # SecretStr — masked in logs/reprs
495
+ db = psycopg2.connect(
496
+ settings.database_url.get_secret_value()
497
+ ) # SecretStr — masked in logs/reprs
435
498
  port = settings.api_port # a real int, validated 1-65535 on startup — not a string
436
499
  ```
437
500
 
@@ -37,6 +37,7 @@ It works the same way whether you have one repo with one `.env` file, or a monor
37
37
  - [Command reference](#command-reference)
38
38
  - [Core commands](#core-commands)
39
39
  - [Monorepo: managing multiple services (monorepo)](#monorepo-managing-multiple-services)
40
+ - [Machine-readable output (`--json`)](#machine-readable-output---json)
40
41
  - [Typed config code generation](#typed-config-code-generation)
41
42
  - [Validating deployment manifests](#validating-deployment-manifests)
42
43
  - [Secret scanning and git hooks](#secret-scanning-and-git-hooks)
@@ -305,12 +306,12 @@ Everything a single-service project ever needs. `--service` shows up on most of
305
306
  |---|---|
306
307
  | `envshield init [--force/-f]` | Detects your framework and builds `env.schema.toml` from a real config source if it finds one, otherwise a framework-aware template. Also scaffolds `envshield.yml`, updates `.gitignore`, and offers to install git hooks. Auto-registers a root-level `docker-compose.yml` as the project's deployment manifest if it finds one. `--force` re-runs on a project that already has a config (with a confirmation before overwriting). |
307
308
  | `envshield import <file> [--output/-o PATH] [--force/-f] [--interactive] [--service NAME]` | Runs the same real-variable analysis `init` does automatically, as its own command — for re-importing after your code gains new variables, pointing at a file `init` wouldn't have found, or adding `--interactive` to confirm each secret/type classification by hand instead of accepting the automatic guess. `--output` changes where the schema is written (defaults to `env.schema.toml`, or the target service's schema path with `--service`). |
308
- | `envshield check [file] [--service NAME] [--container NAME]` | Validates a local file (or, if omitted, the project's/service's default local file *and* its registered deployment manifest, if any) against the schema. `file` can be a plain `.env`, a Python config module, a docker-compose file, or a Kubernetes manifest. `--container` picks which service/container to check in a manifest that declares more than one (tried against `--service`'s name automatically first). Exits non-zero on any drift — safe to use as a CI gate. |
309
- | `envshield doctor [--fix] [--service NAME]` | Runs every health check at once (see below) and reports a summary. `--fix` interactively offers to fix whatever it can — re-running `init`, regenerating the template, installing the git hook, or running `setup` to fill in missing/invalid local values. Exits non-zero if anything's still broken afterward. |
309
+ | `envshield check [file] [--service NAME] [--container NAME] [--json]` | Validates a local file (or, if omitted, the project's/service's default local file *and* its registered deployment manifest, if any) against the schema. `file` can be a plain `.env`, a Python config module, a docker-compose file, or a Kubernetes manifest. `--container` picks which service/container to check in a manifest that declares more than one (tried against `--service`'s name automatically first). Exits non-zero on any drift — safe to use as a CI gate. `--json` prints a machine-readable result instead (see below) and never falls into an interactive service picker. |
310
+ | `envshield doctor [--fix] [--service NAME] [--json]` | Runs every health check at once (see below) and reports a summary. `--fix` interactively offers to fix whatever it can — re-running `init`, regenerating the template, installing the git hook, or running `setup` to fill in missing/invalid local values. Exits non-zero if anything's still broken afterward. `--json` is incompatible with `--fix` (an interactive confirm prompt makes no sense in a machine-readable mode). |
310
311
  | `envshield setup [output_file] [--service NAME]` | Interactive onboarding wizard: walks through every variable that's missing, blank, or has an existing value the schema no longer allows, prompting with the variable's description, masking secret input, and offering a picker for `enum` fields. Leaves everything already correct untouched. |
311
312
  | `envshield schema sync [--service NAME]` | Regenerates `.env.example` from the schema (a dotenv project), or patches a Python-module local file in place to declare any schema variable it's missing (never rewrites it wholesale — only appends/patches the specific lines it owns). `import` already calls this automatically for you when it changes a project's/service's real schema, so you'll rarely need to run it by hand except after a manual schema edit. |
312
- | `envshield generate [output_file] [--lang/-l python\|typescript] [--force/-f] [--service NAME]` | Compiles the schema into a typed, validated config module. `--lang` is auto-detected from your project (Next.js/Vite/Node.js → TypeScript, everything else → Python) if omitted. Defaults to writing `config.py`/`config.ts`; `--force` overwrites an existing output file. See [Typed config code generation](#typed-config-code-generation). |
313
- | `envshield scan [paths...] [--staged] [--config/-c PATH] [--exclude/-e PATTERN]` [--service NAME] | Scans code for hardcoded secrets and for env vars used in code (`os.getenv`, `os.environ.get`, `process.env.X`) but never declared in the schema. `--staged` scans only what's staged for the next commit (what the pre-commit hook runs); `--exclude` (repeatable) adds glob patterns to skip, on top of whatever `secret_scanning.exclude_files` is set in `envshield.yml`. See [Secret scanning and git hooks](#secret-scanning-and-git-hooks). |
313
+ | `envshield generate [output_file] [--lang/-l python\|typescript] [--force/-f] [--service NAME]` | Compiles the schema into a typed, validated config module. `--lang` is auto-detected from your project (Next.js/Vite/Node.js → TypeScript; Python/Django/Flask, or nothing detected → Python) if omitted. A detected ecosystem with no codegen target at all (currently: Go) errors and asks for `--lang` explicitly, rather than silently guessing Python. Defaults to writing `config.py`/`config.ts`; `--force` overwrites an existing output file. See [Typed config code generation](#typed-config-code-generation). |
314
+ | `envshield scan [paths...] [--staged] [--config/-c PATH] [--exclude/-e PATTERN] [--service NAME] [--json]` | Scans code for hardcoded secrets and for env vars used in code (`os.getenv`, `os.environ.get`, `process.env.X`) but never declared in the schema. `--staged` scans only what's staged for the next commit (what the pre-commit hook runs); `--exclude` (repeatable) adds glob patterns to skip, on top of whatever `secret_scanning.exclude_files` is set in `envshield.yml`. See [Secret scanning and git hooks](#secret-scanning-and-git-hooks). |
314
315
  | `envshield install-hook` | Installs both git hooks by hand, without going through `init`/`setup`/`service discover`'s interactive prompt. |
315
316
  | `envshield --version` / `-v` | Prints the installed version and exits. |
316
317
 
@@ -335,6 +336,63 @@ services:
335
336
 
336
337
  ---
337
338
 
339
+ ## Machine-readable output (`--json`)
340
+
341
+ `check`, `doctor`, and `scan` all accept `--json`: every Rich table/panel/progress-bar is suppressed, and exactly one JSON object is printed to stdout instead. Exit codes are unchanged (non-zero on any issue), so `--json` is a drop-in for CI gates, dashboards, or an agent loop that needs to branch on the result instead of parsing colored text. If multiple services are configured and `--service` isn't given, `--json` runs every service automatically rather than falling into the interactive "Which service?" picker.
342
+
343
+ ```bash
344
+ envshield check --json
345
+ ```
346
+
347
+ ```json
348
+ {
349
+ "success": false,
350
+ "results": [
351
+ {
352
+ "file": ".env",
353
+ "service": null,
354
+ "clean": false,
355
+ "missing": [],
356
+ "blank": ["SECRETS_ENCRYPTION_KEY"],
357
+ "invalid": {},
358
+ "extra": []
359
+ }
360
+ ]
361
+ }
362
+ ```
363
+
364
+ `doctor --json` returns the same per-service shape, with each service's individual health checks instead of a variable diff (`--fix` is rejected together with `--json` — an interactive confirm prompt has no place in a machine-readable mode):
365
+
366
+ ```json
367
+ {
368
+ "success": true,
369
+ "results": [
370
+ {
371
+ "service": "hermes",
372
+ "passed": true,
373
+ "checks": [
374
+ {"name": "Configuration Files", "passed": true, "message": "Found and accessible."}
375
+ ]
376
+ }
377
+ ]
378
+ }
379
+ ```
380
+
381
+ `scan --json` returns one flat object (scan doesn't fan out per-service the way check/doctor do — it scans a set of files against whichever schema each one belongs to in a single pass):
382
+
383
+ ```json
384
+ {
385
+ "clean": false,
386
+ "secrets": [
387
+ {"file_path": "./config.py", "line_num": 12, "secret_type": "Generic API Key", "line_content": "..."}
388
+ ],
389
+ "undeclared_variables": [],
390
+ "skipped_files": []
391
+ }
392
+ ```
393
+
394
+ ---
395
+
338
396
  ## Typed config code generation
339
397
 
340
398
  Stop writing `os.getenv("DATABASE_URL")` — untyped, unvalidated, and silently `None` on a typo — and generate a real module instead:
@@ -350,6 +408,7 @@ Source of truth: env.schema.toml. Regenerate with: envshield generate
350
408
 
351
409
  Requires: pip install pydantic pydantic-settings
352
410
  """
411
+
353
412
  from pydantic import Field, SecretStr
354
413
  from pydantic_settings import BaseSettings, SettingsConfigDict
355
414
 
@@ -362,11 +421,13 @@ class Settings(BaseSettings):
362
421
  )
363
422
 
364
423
  database_url: SecretStr = Field(
365
- ..., description="PostgreSQL connection string for the API", alias="DATABASE_URL"
424
+ ...,
425
+ description="PostgreSQL connection string for the API",
426
+ alias="DATABASE_URL",
366
427
  )
367
428
 
368
429
  api_port: int = Field(
369
- '5000', description="Port the API listens on", alias="API_PORT", ge=1, le=65535
430
+ "5000", description="Port the API listens on", alias="API_PORT", ge=1, le=65535
370
431
  )
371
432
 
372
433
 
@@ -376,7 +437,9 @@ settings = Settings()
376
437
  ```python
377
438
  from config import settings
378
439
 
379
- db = psycopg2.connect(settings.database_url.get_secret_value()) # SecretStr — masked in logs/reprs
440
+ db = psycopg2.connect(
441
+ settings.database_url.get_secret_value()
442
+ ) # SecretStr — masked in logs/reprs
380
443
  port = settings.api_port # a real int, validated 1-65535 on startup — not a string
381
444
  ```
382
445
 
@@ -0,0 +1 @@
1
+ __version__ = "4.4.0"