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.
- datex_studio_cli-0.1.0.dist-info/METADATA +172 -0
- datex_studio_cli-0.1.0.dist-info/RECORD +52 -0
- datex_studio_cli-0.1.0.dist-info/WHEEL +4 -0
- datex_studio_cli-0.1.0.dist-info/entry_points.txt +2 -0
- dxs/__init__.py +4 -0
- dxs/__main__.py +6 -0
- dxs/cli.py +381 -0
- dxs/commands/__init__.py +1 -0
- dxs/commands/api.py +135 -0
- dxs/commands/auth.py +431 -0
- dxs/commands/branch.py +890 -0
- dxs/commands/config.py +118 -0
- dxs/commands/crm.py +1230 -0
- dxs/commands/devops.py +245 -0
- dxs/commands/document.py +2084 -0
- dxs/commands/explore.py +2882 -0
- dxs/commands/marketplace.py +220 -0
- dxs/commands/organization.py +151 -0
- dxs/commands/repo.py +472 -0
- dxs/commands/source.py +3063 -0
- dxs/core/__init__.py +1 -0
- dxs/core/api/__init__.py +67 -0
- dxs/core/api/client.py +299 -0
- dxs/core/api/endpoints.py +254 -0
- dxs/core/api/models.py +507 -0
- dxs/core/auth/__init__.py +13 -0
- dxs/core/auth/decorators.py +105 -0
- dxs/core/auth/msal_client.py +445 -0
- dxs/core/auth/token_cache.py +216 -0
- dxs/core/cache.py +493 -0
- dxs/core/devops/__init__.py +9 -0
- dxs/core/devops/client.py +356 -0
- dxs/core/devops/models.py +140 -0
- dxs/core/dynamics/__init__.py +7 -0
- dxs/core/dynamics/client.py +610 -0
- dxs/core/graph.py +439 -0
- dxs/core/output/__init__.py +5 -0
- dxs/core/output/csv_fmt.py +137 -0
- dxs/core/output/formatter.py +162 -0
- dxs/core/output/json_fmt.py +44 -0
- dxs/core/output/yaml_fmt.py +77 -0
- dxs/core/responses.py +157 -0
- dxs/models/__init__.py +1 -0
- dxs/utils/__init__.py +47 -0
- dxs/utils/click_options.py +165 -0
- dxs/utils/config.py +212 -0
- dxs/utils/errors.py +119 -0
- dxs/utils/filtering.py +248 -0
- dxs/utils/paths.py +38 -0
- dxs/utils/resolvers.py +215 -0
- dxs/utils/responses.py +117 -0
- 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,,
|
dxs/__init__.py
ADDED
dxs/__main__.py
ADDED
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()
|
dxs/commands/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command modules."""
|