datex-studio-cli 0.1.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.
Files changed (52) hide show
  1. datex_studio_cli-0.1.0.dist-info/METADATA +172 -0
  2. datex_studio_cli-0.1.0.dist-info/RECORD +52 -0
  3. datex_studio_cli-0.1.0.dist-info/WHEEL +4 -0
  4. datex_studio_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. dxs/__init__.py +4 -0
  6. dxs/__main__.py +6 -0
  7. dxs/cli.py +381 -0
  8. dxs/commands/__init__.py +1 -0
  9. dxs/commands/api.py +135 -0
  10. dxs/commands/auth.py +431 -0
  11. dxs/commands/branch.py +890 -0
  12. dxs/commands/config.py +118 -0
  13. dxs/commands/crm.py +1230 -0
  14. dxs/commands/devops.py +245 -0
  15. dxs/commands/document.py +2084 -0
  16. dxs/commands/explore.py +2882 -0
  17. dxs/commands/marketplace.py +220 -0
  18. dxs/commands/organization.py +151 -0
  19. dxs/commands/repo.py +472 -0
  20. dxs/commands/source.py +3063 -0
  21. dxs/core/__init__.py +1 -0
  22. dxs/core/api/__init__.py +67 -0
  23. dxs/core/api/client.py +299 -0
  24. dxs/core/api/endpoints.py +254 -0
  25. dxs/core/api/models.py +507 -0
  26. dxs/core/auth/__init__.py +13 -0
  27. dxs/core/auth/decorators.py +105 -0
  28. dxs/core/auth/msal_client.py +445 -0
  29. dxs/core/auth/token_cache.py +216 -0
  30. dxs/core/cache.py +493 -0
  31. dxs/core/devops/__init__.py +9 -0
  32. dxs/core/devops/client.py +356 -0
  33. dxs/core/devops/models.py +140 -0
  34. dxs/core/dynamics/__init__.py +7 -0
  35. dxs/core/dynamics/client.py +610 -0
  36. dxs/core/graph.py +439 -0
  37. dxs/core/output/__init__.py +5 -0
  38. dxs/core/output/csv_fmt.py +137 -0
  39. dxs/core/output/formatter.py +162 -0
  40. dxs/core/output/json_fmt.py +44 -0
  41. dxs/core/output/yaml_fmt.py +77 -0
  42. dxs/core/responses.py +157 -0
  43. dxs/models/__init__.py +1 -0
  44. dxs/utils/__init__.py +47 -0
  45. dxs/utils/click_options.py +165 -0
  46. dxs/utils/config.py +212 -0
  47. dxs/utils/errors.py +119 -0
  48. dxs/utils/filtering.py +248 -0
  49. dxs/utils/paths.py +38 -0
  50. dxs/utils/resolvers.py +215 -0
  51. dxs/utils/responses.py +117 -0
  52. dxs/utils/sorting.py +118 -0
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: datex-studio-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for Datex Studio low-code platform, designed for LLM-based AI agents
5
+ Project-URL: Homepage, https://github.com/datex/datex-studio-cli
6
+ Project-URL: Documentation, https://github.com/datex/datex-studio-cli
7
+ Project-URL: Repository, https://github.com/datex/datex-studio-cli
8
+ Author: Datex
9
+ License-Expression: MIT
10
+ Keywords: ai,cli,datex,llm,low-code
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
21
+ Requires-Python: >=3.10
22
+ Requires-Dist: click>=8.1.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: markdownify>=0.13.0
25
+ Requires-Dist: msal>=1.28.0
26
+ Requires-Dist: pydantic-settings>=2.0.0
27
+ Requires-Dist: pydantic>=2.5.0
28
+ Requires-Dist: pyyaml>=6.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
32
+ Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
34
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
35
+ Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
36
+ Description-Content-Type: text/markdown
37
+
38
+ # Datex Studio CLI (`dxs`)
39
+
40
+ Command-line interface for Datex Studio low-code platform, designed for LLM-based AI agents.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ # Using uv
46
+ uv pip install datex-studio-cli
47
+
48
+ # Using pip
49
+ pip install datex-studio-cli
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```bash
55
+ # Authenticate with Azure Entra
56
+ dxs auth login
57
+
58
+ # View commit history
59
+ dxs source log --repo 10
60
+
61
+ # View configuration history
62
+ dxs source history userGrid --branch 100
63
+
64
+ # View current locks
65
+ dxs source locks --repo 10
66
+ ```
67
+
68
+ ## Output Formats
69
+
70
+ The CLI supports multiple output formats optimized for LLM consumption:
71
+
72
+ ```bash
73
+ # YAML (default)
74
+ dxs source log --repo 10
75
+
76
+ # JSON
77
+ dxs source log --repo 10 --output json
78
+
79
+ # CSV
80
+ dxs source log --repo 10 --output csv
81
+ ```
82
+
83
+ ## Configuration
84
+
85
+ Configuration is stored in `~/.datex/config.yaml`. You can also use environment variables:
86
+
87
+ ```bash
88
+ # Set via environment
89
+ export DXS_API_BASE_URL=https://api.datex.io
90
+ export DXS_DEFAULT_BRANCH=100
91
+ export DXS_DEFAULT_REPO=10
92
+
93
+ # Set via CLI
94
+ dxs config set api_base_url https://api.datex.io
95
+ dxs config set default_branch 100
96
+ dxs config set default_repo 10
97
+
98
+ # View configuration
99
+ dxs config list
100
+ ```
101
+
102
+ ## Commands
103
+
104
+ ### Authentication
105
+
106
+ - `dxs auth login` - Authenticate with Azure Entra
107
+ - `dxs auth logout` - Clear stored credentials
108
+ - `dxs auth status` - Show authentication status
109
+
110
+ ### Configuration
111
+
112
+ - `dxs config get <key>` - Get a configuration value
113
+ - `dxs config set <key> <value>` - Set a configuration value
114
+ - `dxs config list` - List all configuration values
115
+
116
+ ### Source Control
117
+
118
+ - `dxs source log` - Show commit history
119
+ - `dxs source history <ref>` - Show configuration version history
120
+ - `dxs source diff` - Show pending changes (draft vs last commit)
121
+ - `dxs source changes` - Show pending changes in detail
122
+ - `dxs source locks` - Show current lock status
123
+ - `dxs source deps` - Show configuration dependencies
124
+ - `dxs source compare` - Compare two branches
125
+
126
+ ### Exploration
127
+
128
+ - `dxs source explore info` - Show application overview
129
+ - `dxs source explore configs` - List all configurations
130
+ - `dxs source explore config <ref>` - View a specific configuration
131
+ - `dxs source explore summary <ref>` - Show structural summary
132
+ - `dxs source explore trace <ref>` - Show configuration dependencies
133
+
134
+ ### Branch & Repository Management
135
+
136
+ - `dxs source branch list` - List branches
137
+ - `dxs source branch show <id>` - Show branch details
138
+ - `dxs source repo list` - List repositories
139
+ - `dxs source repo show <id>` - Show repository details
140
+
141
+ ### Integrations
142
+
143
+ - `dxs devops workitem <id>` - Get Azure DevOps work item
144
+ - `dxs crm case <id>` - Get Dynamics CRM case
145
+ - `dxs organization list` - List organizations
146
+ - `dxs marketplace list` - List marketplace applications
147
+ - `dxs api <method> <url>` - Make raw API requests
148
+
149
+ ## Development
150
+
151
+ ```bash
152
+ # Clone and install
153
+ git clone https://github.com/datex/datex-studio-cli.git
154
+ cd datex-studio-cli
155
+ uv venv
156
+ source .venv/bin/activate
157
+ uv pip install -e ".[dev]"
158
+
159
+ # Run tests
160
+ pytest
161
+
162
+ # Lint and format
163
+ ruff check .
164
+ ruff format .
165
+
166
+ # Type check
167
+ mypy src/dxs
168
+ ```
169
+
170
+ ## License
171
+
172
+ MIT
@@ -0,0 +1,52 @@
1
+ dxs/__init__.py,sha256=VLIgLaa6Kk-6jlKx1nyDVDpx1GUkNsSNCzqDjtFlns4,119
2
+ dxs/__main__.py,sha256=sTUiT2X1UXIQgcHsEg5205ixvuL0HH1tgWDPUONm3cI,124
3
+ dxs/cli.py,sha256=xyyNDp7GxMnz1XIT2YrxkzBtuAc6nuQmqt-f0_jAk4Y,11447
4
+ dxs/commands/__init__.py,sha256=gQ6tnU0Rvm0-ESWFUBU-KDl5dpNOpUTG509hXOQQjwY,27
5
+ dxs/commands/api.py,sha256=3Bd-V8ODYyLSsmWVquRaoaV_czcL04FYA3pHUoTdVsY,4252
6
+ dxs/commands/auth.py,sha256=mXIhulkv0vjQtA6CCQPgEpXpSl529fN9deko9btmG2Q,15620
7
+ dxs/commands/branch.py,sha256=J2At2sf2Ee7MKeSm8vpMeDqB3jAqFjXzp2Yh3HrjaWA,27626
8
+ dxs/commands/config.py,sha256=5xKoqlfeh6Mi-wSnfwQcjY8XO82Dd1k9HG2qWHhjwP0,3083
9
+ dxs/commands/crm.py,sha256=f6iWrGybECMiUWtvjyNXe-C3KI-s2e2wfrFXhja_2No,37383
10
+ dxs/commands/devops.py,sha256=atBjDFIQEgodNxWugeGLk8x5qC2xxJFy-x4cFtOlano,6712
11
+ dxs/commands/document.py,sha256=91aVlCxD8jx9TUl-mXUTdXMeU-QnjT35tPb7c6ve3NU,71692
12
+ dxs/commands/explore.py,sha256=yVOrzefRQp9cfVC3blBiVefKQbADi6sjtOf9HO-4p7U,109455
13
+ dxs/commands/marketplace.py,sha256=Tls6hHlwBpCNTl7G3AX58YY7I6L0aL9L1VChOAfHroc,5852
14
+ dxs/commands/organization.py,sha256=CqY0b_yk2r5Nk-hjmPstp22QtEq1jUHVHAUs2wh0_Dk,3914
15
+ dxs/commands/repo.py,sha256=TjcWyX4w8C28MvDv32XhNbSw9pNqySYAkcL-oBXM2r0,14550
16
+ dxs/commands/source.py,sha256=7U2kO7VNzwDooJHRqZIIMq9AYe0C40QZNtSOaeiPPsI,101477
17
+ dxs/core/__init__.py,sha256=h1U1ZDndn5rm37vqUOoykW3Simq7L_7JYwLJgXwjSTk,74
18
+ dxs/core/cache.py,sha256=AKrTDfWLbdCS6XHc9RiClf7dcRLM_bxQZKK_Z3cQKLI,17966
19
+ dxs/core/graph.py,sha256=E9DJgesWUKeWzQmJhnL-pr4YnpCTlXgIAZbDv07pI-I,16356
20
+ dxs/core/responses.py,sha256=hUjPi4wj2QcnivGXNsdAc1h_ZhlvniafWJ7CUumWDK4,4379
21
+ dxs/core/api/__init__.py,sha256=E76O7fwyVHOBETT2AsyPu8Zt62uVuhNSjMnrtiZuMZQ,1747
22
+ dxs/core/api/client.py,sha256=1UCOGnWQ3AoebS1lWYZ943pvbx1ysR98qhHWbx87Qpg,10654
23
+ dxs/core/api/endpoints.py,sha256=HNbdQ_bTEwg49cSUMI4JrLZEEBzAd-c5M32KIM0aODc,8224
24
+ dxs/core/api/models.py,sha256=AVAr-JIX93qw9zHxSbOp35ao72BCEaPyn0uge9hS1zc,16793
25
+ dxs/core/auth/__init__.py,sha256=K8lK4mtVkAPu2nTzzX1YprDbs57lNK5IYgX-JejxNFU,351
26
+ dxs/core/auth/decorators.py,sha256=1aqqCL4W6Ri_lTfXK1wJbHhyzkzmipIoOd4PRMGV9ro,3309
27
+ dxs/core/auth/msal_client.py,sha256=KZI2N1YtFFPQBxNl_gsFkNUow-UaiimKDxX-iT0MUOg,16909
28
+ dxs/core/auth/token_cache.py,sha256=mJfM8R9Ffsqyv_q3uo3JpggAqRkV3-CDhQpFaFLOf6k,6792
29
+ dxs/core/devops/__init__.py,sha256=h4mZoir7gOau3YEP1ktEQStxqur_uGvDMQeu-sGotR8,211
30
+ dxs/core/devops/client.py,sha256=PKlV0PKgAAodCyOB3_-VPVkv4WHZQg5zOQ5k6l1r-ls,11817
31
+ dxs/core/devops/models.py,sha256=plCXk04WFMOZJBUS1xzTc1jWUeSRFjdxOaNEsr_Mxw8,4343
32
+ dxs/core/dynamics/__init__.py,sha256=IsVDv6hzJzFn-WMUKgNMHN5d6gKrc9Fu2JTCaws5sGQ,149
33
+ dxs/core/dynamics/client.py,sha256=e9H7gbIsxONZ_o_3L6sevOqJ-j_9W3w5JJ14YI6xwaA,22764
34
+ dxs/core/output/__init__.py,sha256=EYlkM_-fimMtsPdynkq9jwaZm9OXrk4VziwrEsUK5sE,205
35
+ dxs/core/output/csv_fmt.py,sha256=ALfNJkhKfOQJDK0j3efJD6YFtxR9IhC-9p_5Uw_gxTw,3334
36
+ dxs/core/output/formatter.py,sha256=dIuzvw1_X1ZibSNMkfsNZ0_v52T3hwZ-yoR8XjzvSek,4558
37
+ dxs/core/output/json_fmt.py,sha256=ah3ZE8HMkjOAsj442XKeAjUYwSut7-a_MKiMDWCwzUM,959
38
+ dxs/core/output/yaml_fmt.py,sha256=pLV9ez1Mr6cjFYe6CQYukXVC5iJ0w3932REik5g1IAw,2553
39
+ dxs/models/__init__.py,sha256=Olg44Lfn1NJZ590xdEv7rZrS47PxSUnCXo9pOnSJm1s,41
40
+ dxs/utils/__init__.py,sha256=wRNc4MzgJTitIJJuUOMwoQH4_EjQ_4y_5UwMWAidXxk,1065
41
+ dxs/utils/click_options.py,sha256=XeYc4JPnrofNzZv3DLFL0t7MYHz3D1D8EziP5LgtCiM,4850
42
+ dxs/utils/config.py,sha256=nbR2SA2YURPrE2DUe-EDOAVgeYQ0TWwAcQ0eFKpH8VI,6505
43
+ dxs/utils/errors.py,sha256=4KS3BnzF3ZTFpbbNSwHeaCmj3pCKMtJw20vo4HDYH_o,3725
44
+ dxs/utils/filtering.py,sha256=LNy0B-pyQBtYILk-77ojnCSVXtBgZ0R2-lZ4hOfYDw0,6348
45
+ dxs/utils/paths.py,sha256=V5HqDQVF-rfE3OSMMPNVlvOEHJgfQwJ7lsUyuMK7CtE,992
46
+ dxs/utils/resolvers.py,sha256=X2hUBVbf-us1jQtsdlZsXnfIqBWRoHMBGHIAduge_ik,7360
47
+ dxs/utils/responses.py,sha256=jZXWfCfSYFsgqU5RwJ_2_9sKgsbrtTuQAJ9nhxgPrwM,3165
48
+ dxs/utils/sorting.py,sha256=Oo2THyTy-Km3NpLJtNSCxnsYPi4nMADDLvJd3gRw6y4,3142
49
+ datex_studio_cli-0.1.0.dist-info/METADATA,sha256=dnRaVpE-8r2zKSkVSEIqMCfcfHrS5LOcoLmwTCFVYwc,4545
50
+ datex_studio_cli-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
51
+ datex_studio_cli-0.1.0.dist-info/entry_points.txt,sha256=uczHmv1ThDdaldnjFMPBCCBTqLgnWToIbW4RSX-gAFk,37
52
+ datex_studio_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dxs = dxs.cli:main
dxs/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ """Datex Studio CLI - Command-line interface for Datex Studio platform."""
2
+
3
+ __version__ = "0.1.0"
4
+ __app_name__ = "dxs"
dxs/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Entry point for running dxs as a module: python -m dxs"""
2
+
3
+ from dxs.cli import cli
4
+
5
+ if __name__ == "__main__":
6
+ cli()
dxs/cli.py ADDED
@@ -0,0 +1,381 @@
1
+ """Main CLI entry point for Datex Studio CLI."""
2
+
3
+ import sys
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+ from dxs import __app_name__, __version__
9
+ from dxs.core.output import OutputFormat, format_error, format_output
10
+ from dxs.utils.config import get_settings
11
+ from dxs.utils.errors import DxsError
12
+
13
+ # Fields to strip from author objects in concise mode
14
+ _AUTHOR_VERBOSE_FIELDS = {"id", "organizationId", "externalId", "hasManagementAccess"}
15
+
16
+
17
+ def _strip_concise(data: Any, parent_key: str | None = None) -> Any:
18
+ """Strip null values and verbose metadata from data for concise output.
19
+
20
+ Args:
21
+ data: Data to strip (dict, list, or other).
22
+ parent_key: The key of the parent that contains this data.
23
+
24
+ Returns:
25
+ Data with nulls and verbose fields removed.
26
+ """
27
+ if isinstance(data, dict):
28
+ result = {}
29
+ for key, value in data.items():
30
+ # Skip null values
31
+ if value is None:
32
+ continue
33
+ # Skip verbose author fields only within author objects
34
+ if parent_key == "author" and key in _AUTHOR_VERBOSE_FIELDS:
35
+ continue
36
+ # Recursively process nested structures, passing the key as context
37
+ result[key] = _strip_concise(value, parent_key=key)
38
+ return result
39
+ elif isinstance(data, list):
40
+ return [_strip_concise(item, parent_key=parent_key) for item in data]
41
+ else:
42
+ return data
43
+
44
+
45
+ class DxsContext:
46
+ """Shared context passed to all commands."""
47
+
48
+ def __init__(self) -> None:
49
+ self.output_format: OutputFormat = OutputFormat.YAML
50
+ self.verbose: bool = False
51
+ self.quiet: bool = False
52
+ self.concise: bool = True # Concise output is default
53
+ self.org: str | None = None
54
+ self.env: str | None = None
55
+ self.branch: int | None = None
56
+ self.repo: int | None = None
57
+ self.save_path: str | None = None
58
+ self.force_overwrite: bool = False
59
+ self.explicit_output_format: bool = False
60
+
61
+ def output(self, data: Any, include_metadata: bool = True) -> None:
62
+ """Output data in the configured format.
63
+
64
+ Args:
65
+ data: Data to output.
66
+ include_metadata: Include timestamp and version metadata.
67
+ """
68
+ from dxs.core.output.formatter import detect_format_from_extension
69
+
70
+ # Determine format: --output flag > file extension > default
71
+ if self.save_path and not self.explicit_output_format:
72
+ output_format = detect_format_from_extension(self.save_path, self.output_format)
73
+ else:
74
+ output_format = self.output_format
75
+
76
+ formatted = format_output(data, output_format, include_metadata, self.concise)
77
+
78
+ # Save to file if requested (suppresses stdout)
79
+ if self.save_path:
80
+ self._save_to_file(formatted)
81
+ return
82
+
83
+ click.echo(formatted)
84
+
85
+ def _save_to_file(self, content: str) -> None:
86
+ """Save content to file with overwrite protection.
87
+
88
+ Args:
89
+ content: Formatted content to save.
90
+
91
+ Raises:
92
+ ValidationError: If file exists and force_overwrite is False.
93
+ DxsError: If file write fails.
94
+ """
95
+ from pathlib import Path
96
+
97
+ from dxs.utils.errors import ValidationError
98
+
99
+ path = Path(self.save_path)
100
+
101
+ if path.exists() and not self.force_overwrite:
102
+ raise ValidationError(
103
+ message=f"File '{self.save_path}' already exists",
104
+ code="DXS-FILE-001",
105
+ suggestions=[
106
+ "Use --force flag to overwrite the file",
107
+ "Specify a different filename with --save",
108
+ ],
109
+ )
110
+
111
+ path.parent.mkdir(parents=True, exist_ok=True)
112
+
113
+ try:
114
+ path.write_text(content, encoding="utf-8")
115
+ self.debug(f"Output saved to: {self.save_path}")
116
+ except OSError as e:
117
+ raise DxsError(
118
+ code="DXS-FILE-002",
119
+ message=f"Failed to write file '{self.save_path}': {e}",
120
+ ) from e
121
+
122
+ def output_error(self, error: DxsError | Exception) -> None:
123
+ """Output an error in the configured format.
124
+
125
+ Args:
126
+ error: Error to output.
127
+ """
128
+ formatted = format_error(error, self.output_format)
129
+ click.echo(formatted, err=True)
130
+
131
+ def log(self, message: str) -> None:
132
+ """Log a message (respects quiet flag).
133
+
134
+ Args:
135
+ message: Message to log.
136
+ """
137
+ if not self.quiet:
138
+ click.echo(message, err=True)
139
+
140
+ def debug(self, message: str) -> None:
141
+ """Log a debug message (only in verbose mode).
142
+
143
+ Args:
144
+ message: Debug message to log.
145
+ """
146
+ if self.verbose:
147
+ click.echo(f"[DEBUG] {message}", err=True)
148
+
149
+
150
+ # Create a pass decorator for the context
151
+ pass_context = click.make_pass_decorator(DxsContext, ensure=True)
152
+
153
+
154
+ # Global options shared across all commands
155
+ CONTEXT_SETTINGS = {
156
+ "help_option_names": ["-h", "--help"],
157
+ "max_content_width": 120,
158
+ }
159
+
160
+
161
+ class DxsGroup(click.Group):
162
+ """Custom Group that shows global options in subcommand help."""
163
+
164
+ def format_options(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
165
+ """Write options to the formatter, including global options."""
166
+ # First write local options
167
+ super().format_options(ctx, formatter)
168
+
169
+ # Then add global options section if we have a parent
170
+ if ctx.parent is not None:
171
+ # Get global options from the root command
172
+ root_ctx = ctx
173
+ while root_ctx.parent is not None:
174
+ root_ctx = root_ctx.parent
175
+
176
+ if root_ctx.command and hasattr(root_ctx.command, "params"):
177
+ global_opts = []
178
+ for param in root_ctx.command.params:
179
+ if isinstance(param, click.Option):
180
+ record = param.get_help_record(ctx)
181
+ if record:
182
+ global_opts.append(record)
183
+
184
+ if global_opts:
185
+ with formatter.section("Global Options"):
186
+ formatter.write_dl(global_opts)
187
+
188
+
189
+ @click.group(cls=DxsGroup, context_settings=CONTEXT_SETTINGS)
190
+ @click.version_option(version=__version__, prog_name=__app_name__)
191
+ @click.option(
192
+ "-o",
193
+ "--org",
194
+ type=str,
195
+ envvar="DXS_ORG",
196
+ help="Organization ID (overrides default)",
197
+ )
198
+ @click.option(
199
+ "-e",
200
+ "--env",
201
+ type=str,
202
+ envvar="DXS_ENV",
203
+ help="Environment ID (overrides default)",
204
+ )
205
+ @click.option(
206
+ "-b",
207
+ "--branch",
208
+ type=int,
209
+ envvar="DXS_BRANCH",
210
+ help="Branch ID (overrides default)",
211
+ )
212
+ @click.option(
213
+ "-r",
214
+ "--repo",
215
+ type=int,
216
+ envvar="DXS_REPO",
217
+ help="Repository ID (overrides default)",
218
+ )
219
+ @click.option(
220
+ "-O",
221
+ "--output",
222
+ "output_format",
223
+ type=click.Choice(["yaml", "json", "csv"], case_sensitive=False),
224
+ default=None,
225
+ help="Output format (default: yaml)",
226
+ )
227
+ @click.option(
228
+ "-v",
229
+ "--verbose",
230
+ is_flag=True,
231
+ default=False,
232
+ help="Enable verbose output (show progress messages)",
233
+ )
234
+ @click.option(
235
+ "-f",
236
+ "--full",
237
+ is_flag=True,
238
+ default=False,
239
+ help="Show full output including null values and verbose metadata",
240
+ )
241
+ @click.option(
242
+ "-s",
243
+ "--save",
244
+ "save_path",
245
+ type=click.Path(dir_okay=False, writable=True, path_type=str),
246
+ default=None,
247
+ help="Save output to file (format auto-detected from extension)",
248
+ )
249
+ @click.option(
250
+ "--force",
251
+ "force_overwrite",
252
+ is_flag=True,
253
+ default=False,
254
+ help="Force overwrite existing file when using --save",
255
+ )
256
+ @click.pass_context
257
+ def cli(
258
+ ctx: click.Context,
259
+ org: str | None,
260
+ env: str | None,
261
+ branch: int | None,
262
+ repo: int | None,
263
+ output_format: str | None,
264
+ verbose: bool,
265
+ full: bool,
266
+ save_path: str | None,
267
+ force_overwrite: bool,
268
+ ) -> None:
269
+ """Datex Studio CLI - Command-line interface for Datex Studio platform.
270
+
271
+ Designed for LLM-based AI agents. Use --output yaml (default) or --output json
272
+ for structured, parseable responses.
273
+
274
+ \b
275
+ Examples:
276
+ dxs auth login
277
+ dxs source log --repo 10
278
+ dxs source history userGrid --branch 100
279
+ dxs config list
280
+
281
+ \b
282
+ Environment Variables:
283
+ DXS_ORG Default organization ID
284
+ DXS_ENV Default environment ID
285
+ DXS_BRANCH Default branch ID
286
+ DXS_REPO Default repository ID
287
+
288
+ \b
289
+ Configuration:
290
+ Settings are stored in ~/.datex/config.yaml
291
+ Credentials are stored in ~/.datex/credentials.yaml
292
+ """
293
+ # Initialize context
294
+ ctx.ensure_object(DxsContext)
295
+ dxs_ctx: DxsContext = ctx.obj
296
+
297
+ # Load settings
298
+ settings = get_settings()
299
+
300
+ # Set output format (CLI flag > env var > config > default)
301
+ if output_format:
302
+ dxs_ctx.output_format = OutputFormat(output_format.lower())
303
+ dxs_ctx.explicit_output_format = True
304
+ elif settings.default_output_format:
305
+ dxs_ctx.output_format = OutputFormat(settings.default_output_format.lower())
306
+
307
+ # Set verbosity (quiet by default for LLM agents, --verbose enables log output)
308
+ dxs_ctx.verbose = verbose
309
+ dxs_ctx.quiet = not verbose
310
+ # Concise output is default; --full disables it
311
+ dxs_ctx.concise = not full
312
+
313
+ # Set save options
314
+ dxs_ctx.save_path = save_path
315
+ dxs_ctx.force_overwrite = force_overwrite
316
+
317
+ # Set context values (CLI flag > env var > config)
318
+ dxs_ctx.org = org or settings.default_org
319
+ dxs_ctx.env = env or settings.default_env
320
+ dxs_ctx.branch = branch or settings.default_branch
321
+ dxs_ctx.repo = repo or settings.default_repo
322
+
323
+
324
+ # Error handler decorator
325
+ def handle_errors(f):
326
+ """Decorator to handle errors and output them in the configured format."""
327
+
328
+ @click.pass_context
329
+ def wrapper(ctx: click.Context, *args, **kwargs):
330
+ dxs_ctx: DxsContext = ctx.obj
331
+ try:
332
+ return ctx.invoke(f, *args, **kwargs)
333
+ except DxsError as e:
334
+ dxs_ctx.output_error(e)
335
+ sys.exit(1)
336
+ except click.ClickException:
337
+ # Let Click handle its own exceptions
338
+ raise
339
+ except Exception as e:
340
+ dxs_ctx.output_error(e)
341
+ sys.exit(1)
342
+
343
+ return wrapper
344
+
345
+
346
+ # Import and register command groups
347
+ # These imports are here to avoid circular imports
348
+ def register_commands() -> None:
349
+ """Register all command groups with the CLI."""
350
+ from dxs.commands import api, auth, config, crm, devops, marketplace, organization, source
351
+
352
+ cli.add_command(api.api)
353
+ cli.add_command(auth.auth)
354
+ cli.add_command(config.config)
355
+ cli.add_command(crm.crm)
356
+ cli.add_command(devops.devops)
357
+ cli.add_command(marketplace.marketplace)
358
+ cli.add_command(organization.organization)
359
+ cli.add_command(source.source)
360
+ # branch and repo are registered as subcommands of source in source.py
361
+
362
+
363
+ # Register commands when module loads
364
+ register_commands()
365
+
366
+
367
+ # Main entry point
368
+ def main() -> None:
369
+ """Main entry point for the CLI."""
370
+ try:
371
+ cli()
372
+ except DxsError as e:
373
+ # Get the output format from context or default to YAML
374
+ output_format = OutputFormat.YAML
375
+ formatted = format_error(e, output_format)
376
+ click.echo(formatted, err=True)
377
+ sys.exit(1)
378
+
379
+
380
+ if __name__ == "__main__":
381
+ main()
@@ -0,0 +1 @@
1
+ """CLI command modules."""