nexla-cli 0.2.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 (75) hide show
  1. nexla_cli-0.2.0/.github/workflows/publish-pypi.yml +34 -0
  2. nexla_cli-0.2.0/.github/workflows/release-binaries.yml +44 -0
  3. nexla_cli-0.2.0/.gitignore +14 -0
  4. nexla_cli-0.2.0/LICENSE +21 -0
  5. nexla_cli-0.2.0/PKG-INFO +196 -0
  6. nexla_cli-0.2.0/README.md +167 -0
  7. nexla_cli-0.2.0/npm/README.md +27 -0
  8. nexla_cli-0.2.0/npm/bin/nexla.js +14 -0
  9. nexla_cli-0.2.0/npm/package.json +18 -0
  10. nexla_cli-0.2.0/npm/scripts/install.js +62 -0
  11. nexla_cli-0.2.0/pyproject.toml +52 -0
  12. nexla_cli-0.2.0/scripts/pyinstaller_entry.py +4 -0
  13. nexla_cli-0.2.0/src/nexla_cli/AGENTS.md +300 -0
  14. nexla_cli-0.2.0/src/nexla_cli/SKILL.md +178 -0
  15. nexla_cli-0.2.0/src/nexla_cli/__init__.py +247 -0
  16. nexla_cli-0.2.0/src/nexla_cli/client.py +130 -0
  17. nexla_cli-0.2.0/src/nexla_cli/dryrun.py +119 -0
  18. nexla_cli-0.2.0/src/nexla_cli/errors.py +45 -0
  19. nexla_cli-0.2.0/src/nexla_cli/login.py +42 -0
  20. nexla_cli-0.2.0/src/nexla_cli/mcp_client.py +139 -0
  21. nexla_cli-0.2.0/src/nexla_cli/openapi_client.py +196 -0
  22. nexla_cli-0.2.0/src/nexla_cli/output.py +146 -0
  23. nexla_cli-0.2.0/src/nexla_cli/py.typed +0 -0
  24. nexla_cli-0.2.0/src/nexla_cli/resources/__init__.py +1 -0
  25. nexla_cli-0.2.0/src/nexla_cli/resources/code_containers.py +23 -0
  26. nexla_cli-0.2.0/src/nexla_cli/resources/connectors.py +125 -0
  27. nexla_cli-0.2.0/src/nexla_cli/resources/context.py +22 -0
  28. nexla_cli-0.2.0/src/nexla_cli/resources/credentials.py +130 -0
  29. nexla_cli-0.2.0/src/nexla_cli/resources/flows.py +90 -0
  30. nexla_cli-0.2.0/src/nexla_cli/resources/mcp_servers.py +103 -0
  31. nexla_cli-0.2.0/src/nexla_cli/resources/metrics.py +47 -0
  32. nexla_cli-0.2.0/src/nexla_cli/resources/nexsets.py +110 -0
  33. nexla_cli-0.2.0/src/nexla_cli/resources/notifications.py +24 -0
  34. nexla_cli-0.2.0/src/nexla_cli/resources/orgs.py +53 -0
  35. nexla_cli-0.2.0/src/nexla_cli/resources/probe.py +94 -0
  36. nexla_cli-0.2.0/src/nexla_cli/resources/sinks.py +264 -0
  37. nexla_cli-0.2.0/src/nexla_cli/resources/sources.py +197 -0
  38. nexla_cli-0.2.0/src/nexla_cli/resources/tools.py +106 -0
  39. nexla_cli-0.2.0/src/nexla_cli/resources/toolsets.py +134 -0
  40. nexla_cli-0.2.0/src/nexla_cli/resources/transforms.py +40 -0
  41. nexla_cli-0.2.0/src/nexla_cli/resources/triage.py +206 -0
  42. nexla_cli-0.2.0/src/nexla_cli/resources/users.py +32 -0
  43. nexla_cli-0.2.0/src/nexla_cli/sanitize.py +83 -0
  44. nexla_cli-0.2.0/src/nexla_cli/schema.py +64 -0
  45. nexla_cli-0.2.0/src/nexla_cli/validate.py +99 -0
  46. nexla_cli-0.2.0/tests/__init__.py +0 -0
  47. nexla_cli-0.2.0/tests/conftest.py +28 -0
  48. nexla_cli-0.2.0/tests/test_app.py +184 -0
  49. nexla_cli-0.2.0/tests/test_client.py +113 -0
  50. nexla_cli-0.2.0/tests/test_connectors.py +129 -0
  51. nexla_cli-0.2.0/tests/test_context.py +27 -0
  52. nexla_cli-0.2.0/tests/test_contract.py +106 -0
  53. nexla_cli-0.2.0/tests/test_credentials.py +56 -0
  54. nexla_cli-0.2.0/tests/test_dryrun.py +300 -0
  55. nexla_cli-0.2.0/tests/test_errors.py +21 -0
  56. nexla_cli-0.2.0/tests/test_flows.py +54 -0
  57. nexla_cli-0.2.0/tests/test_login.py +73 -0
  58. nexla_cli-0.2.0/tests/test_mcp_servers.py +103 -0
  59. nexla_cli-0.2.0/tests/test_nexsets.py +148 -0
  60. nexla_cli-0.2.0/tests/test_openapi_client.py +116 -0
  61. nexla_cli-0.2.0/tests/test_orgs.py +56 -0
  62. nexla_cli-0.2.0/tests/test_output.py +224 -0
  63. nexla_cli-0.2.0/tests/test_packaging.py +49 -0
  64. nexla_cli-0.2.0/tests/test_probe.py +90 -0
  65. nexla_cli-0.2.0/tests/test_sanitize.py +46 -0
  66. nexla_cli-0.2.0/tests/test_schema.py +108 -0
  67. nexla_cli-0.2.0/tests/test_sinks.py +204 -0
  68. nexla_cli-0.2.0/tests/test_sources.py +211 -0
  69. nexla_cli-0.2.0/tests/test_stub_resources.py +45 -0
  70. nexla_cli-0.2.0/tests/test_tools.py +97 -0
  71. nexla_cli-0.2.0/tests/test_toolsets.py +81 -0
  72. nexla_cli-0.2.0/tests/test_transforms.py +80 -0
  73. nexla_cli-0.2.0/tests/test_triage.py +143 -0
  74. nexla_cli-0.2.0/tests/test_validate.py +158 -0
  75. nexla_cli-0.2.0/uv.lock +457 -0
@@ -0,0 +1,34 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ id-token: write # required for PyPI Trusted Publishing (OIDC)
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.12"
19
+ - run: pip install build
20
+ - run: python -m build
21
+
22
+ # Preferred: PyPI Trusted Publishing. Requires a one-time setup on
23
+ # pypi.org (project nexla-cli -> Settings -> Publishing -> add this
24
+ # repo + workflow filename + "pypi" environment as a trusted
25
+ # publisher) -- see README.md's "Publishing to PyPI" section. No
26
+ # secret/token needed here.
27
+ - uses: pypa/gh-action-pypi-publish@release/v1
28
+
29
+ # Optional fallback if Trusted Publishing isn't set up yet: uncomment
30
+ # and set `password` from a repo secret (e.g. PYPI_API_TOKEN) instead
31
+ # of relying on OIDC. Not used by default.
32
+ # - uses: pypa/gh-action-pypi-publish@release/v1
33
+ # with:
34
+ # password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,44 @@
1
+ name: Release binaries
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+
8
+ jobs:
9
+ build:
10
+ strategy:
11
+ matrix:
12
+ include:
13
+ - os: ubuntu-latest
14
+ asset: nexla-linux-x64
15
+ - os: macos-latest
16
+ asset: nexla-macos-arm64
17
+ - os: windows-latest
18
+ asset: nexla-windows-x64.exe
19
+ runs-on: ${{ matrix.os }}
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.12"
25
+ - run: pip install . pyinstaller
26
+ - run: >
27
+ pyinstaller --onefile --name ${{ matrix.asset }}
28
+ scripts/pyinstaller_entry.py
29
+ - uses: actions/upload-artifact@v4
30
+ with:
31
+ name: ${{ matrix.asset }}
32
+ path: dist/${{ matrix.asset }}
33
+
34
+ release:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/download-artifact@v4
39
+ with:
40
+ path: dist
41
+ merge-multiple: true
42
+ - uses: softprops/action-gh-release@v2
43
+ with:
44
+ files: dist/*
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .mypy_cache/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .claude/
10
+ plans/
11
+ npm/bin/nexla-bin*
12
+ npm/node_modules/
13
+ build/
14
+ *.spec
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abhishek Kumar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.4
2
+ Name: nexla-cli
3
+ Version: 0.2.0
4
+ Summary: Command-line client for the Nexla agent API.
5
+ Project-URL: Homepage, https://github.com/abhishekkumar705/nexla-agent-cli
6
+ Project-URL: Repository, https://github.com/abhishekkumar705/nexla-agent-cli
7
+ Project-URL: Issues, https://github.com/abhishekkumar705/nexla-agent-cli/issues
8
+ Author-email: Abhishek Kumar <abhishek.kumar@nexla.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Utilities
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: httpx>=0.28.0
21
+ Requires-Dist: typer>=0.12.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.13.0; extra == 'dev'
24
+ Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
25
+ Requires-Dist: pytest>=9.0.3; extra == 'dev'
26
+ Requires-Dist: respx>=0.23.1; extra == 'dev'
27
+ Requires-Dist: ruff>=0.15.12; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # nexla-cli
31
+
32
+ Command-line client for the Nexla agent API. Depends on only `typer` and
33
+ `httpx` — no FastAPI, Daytona, Supabase, or other backend dependencies.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pipx install "git+https://github.com/abhishekkumar705/nexla-agent-cli.git"
39
+ ```
40
+
41
+ Or with `uv`:
42
+
43
+ ```bash
44
+ uv tool install "git+https://github.com/abhishekkumar705/nexla-agent-cli.git"
45
+ ```
46
+
47
+ ### From Node/TypeScript projects (no Python required)
48
+
49
+ ```bash
50
+ npm install -g nexla-cli
51
+ # or run once-off:
52
+ npx nexla-cli sources list
53
+ ```
54
+
55
+ This installs a prebuilt native binary (published to GitHub Releases by
56
+ `.github/workflows/release-binaries.yml`) behind a thin `npm/` wrapper — no
57
+ Python interpreter needed on the target machine. See `npm/README.md` for how
58
+ releases are cut.
59
+
60
+ ### From PyPI
61
+
62
+ ```bash
63
+ pip install nexla-cli
64
+ # or
65
+ uvx nexla-cli sources list
66
+ pipx install nexla-cli
67
+ ```
68
+
69
+ Published by `.github/workflows/publish-pypi.yml` — see "Publishing to
70
+ PyPI" below.
71
+
72
+ ## Publishing to PyPI
73
+
74
+ Tagging a release (`git tag vX.Y.Z && git push --tags` — same tag used to
75
+ trigger `.github/workflows/release-binaries.yml`, see `npm/README.md` for
76
+ the full release checklist) also triggers `publish-pypi.yml`, which builds
77
+ the sdist+wheel and publishes them to PyPI using
78
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) —
79
+ no long-lived API token stored in this repo.
80
+
81
+ One-time setup required on pypi.org before the first release (repo owner
82
+ only, not automatable from here):
83
+
84
+ 1. Create the `nexla-cli` project on PyPI (or reserve the name).
85
+ 2. Project → Settings → Publishing → add a trusted publisher:
86
+ - Owner: `abhishekkumar705`, repo: `nexla-agent-cli`
87
+ - Workflow filename: `publish-pypi.yml`
88
+ - Environment name: `pypi`
89
+ 3. Nothing else to configure — no secrets to add. The workflow's
90
+ `id-token: write` permission plus the `pypi` GitHub Environment satisfy
91
+ PyPI's OIDC handshake automatically.
92
+
93
+ ## Quick start
94
+
95
+ ```bash
96
+ export NEXLA_API_URL=https://dev-api-express-code.nexla.com
97
+ export NEXLA_TOKEN=$(nexla login --service-key <your-service-key>)
98
+ nexla sources list
99
+ ```
100
+
101
+ `nexla login` prints a bearer token to stdout (see command substitution
102
+ above); alternatively, set the following environment variables directly:
103
+
104
+ - `NEXLA_API_URL` — base URL of the deployed Nexla agent API
105
+ - `NEXLA_TOKEN` — bearer token to authenticate requests
106
+
107
+ ## Using this CLI from Claude Code
108
+
109
+ The package ships a [Claude Code skill](https://docs.claude.com/en/docs/claude-code/skills) (`AGENTS.md` + `SKILL.md`, installed alongside the `nexla_cli` package) encoding invariants an agent can't infer from `--help` alone — output modes, `--dry-run`, `--json`/`--params` precedence, exit codes, and treating API responses as untrusted data.
110
+
111
+ Claude Code only discovers skills placed at `~/.claude/skills/<name>/SKILL.md` (global) or `.claude/skills/<name>/SKILL.md` (project-local) — a file merely present inside an installed pip package isn't picked up automatically. Symlink it in once, after installing the CLI. `pipx`/`uv tool install` both use an isolated venv, so a plain system `python3 -c "import nexla_cli"` won't find it — locate it inside that isolated venv instead:
112
+
113
+ ```bash
114
+ mkdir -p ~/.claude/skills/nexla-cli
115
+
116
+ # if installed with `uv tool install`:
117
+ ln -sf "$(uv tool dir)/nexla-cli/lib/python3."*"/site-packages/nexla_cli/SKILL.md" \
118
+ ~/.claude/skills/nexla-cli/SKILL.md
119
+
120
+ # if installed with `pipx install`:
121
+ ln -sf "$HOME/.local/pipx/venvs/nexla-cli/lib/python3."*"/site-packages/nexla_cli/SKILL.md" \
122
+ ~/.claude/skills/nexla-cli/SKILL.md
123
+ ```
124
+
125
+ A symlink (not a copy) means `nexla-cli` upgrades automatically pick up any future skill content updates. Restart Claude Code (or start a new session) after installing for it to be picked up.
126
+
127
+ ## Global flags
128
+
129
+ Available on every command except `login` and `schema` (both always print raw output regardless of these flags):
130
+
131
+ | Flag | Effect |
132
+ |------|--------|
133
+ | `--output` / `-o` `table\|json\|ndjson` | Force an output mode. Defaults to `table` on a TTY, `json` otherwise (also settable via `NEXLA_OUTPUT`/`OUTPUT_FORMAT`). |
134
+ | `--fields id,name,...` | Mask output down to just these keys, on any `list`/`get`. |
135
+ | `--page-all` | Stream every page of a `list` command as NDJSON instead of returning one page. |
136
+
137
+ These can be placed before or after the subcommand, e.g. both `nexla --output json sources list` and `nexla sources list --output json` work.
138
+
139
+ Every mutating command also accepts `--dry-run`: validates the request body against the live API schema and prints `{"valid": ...}` without making the real (mutating) call.
140
+
141
+ ## Raw JSON payloads
142
+
143
+ `create`/`update`-style commands (`sources`, `sinks`, `credentials`, `toolsets`, `nexsets transform`, `mcp-servers attach`, `tools set-runtime-config`) accept the full request body directly, not just their named flags:
144
+
145
+ ```bash
146
+ nexla sources create --name my-source --connector s3 --json '{"credential_id": 123}'
147
+ nexla sources update 42 --params description="updated via params"
148
+ ```
149
+
150
+ Precedence when a key is given more than one way: named CLI flags win, then `--json`, then `--params` (lowest). This lets you set a field the CLI hasn't added a dedicated flag for yet, without waiting on a CLI release.
151
+
152
+ ## Response sanitization
153
+
154
+ Every response is passed through a sanitizer before rendering, in every output mode: ANSI escape sequences, control characters, and invisible Unicode (zero-width spaces, byte-order marks, bidirectional overrides) are stripped unconditionally — always on, no flag. This defends against a malicious API response field hijacking a human's terminal, or hiding text from a human while an agent still reads it. It is not a semantic filter — API response *content* should still be treated as untrusted data (see `AGENTS.md`), this only strips characters no legitimate field value would ever need.
155
+
156
+ ## Full command reference
157
+
158
+ ```bash
159
+ nexla --help
160
+ nexla <resource> --help
161
+ ```
162
+
163
+ | Resource | Commands |
164
+ |----------|----------|
165
+ | `login` | `login --service-key <key> [--api-url <url>]` — exchanges a service key for a bearer token, printed to stdout |
166
+ | `schema` | `schema [<resource>.<verb>]` — machine-readable JSON signature of one command or the whole `/nexla/*` surface, fetched live from the deployed API's OpenAPI doc |
167
+ | `sources` | `list`, `get`, `create`, `update`, `activate`, `pause`, `delete`, `sample`, `file-upload` |
168
+ | `sinks` | `list`, `get`, `create`, `update`, `activate`, `pause`, `delete` |
169
+ | `nexsets` | `list`, `get`, `transform`, `activate` |
170
+ | `credentials` | `list`, `get`, `create`, `update`, `delete` |
171
+ | `flows` | `list`, `get`, `activate`, `pause`, `delete` |
172
+ | `transforms` | `test` |
173
+ | `connectors` | `search`, `describe`, `describe-credential`, `describe-credential-mode`, `describe-source`, `describe-source-endpoint`, `describe-sink`, `describe-sink-endpoint` |
174
+ | `probe` | `run` |
175
+ | `toolsets` | `list`, `get`, `create`, `update`, `delete`, `add-nexsets` |
176
+ | `tools` | `list`, `get`, `set-runtime-config`, `clear-runtime-config`, `delete` |
177
+ | `mcp-servers` | `list`, `attach`, `sync`, `detach` (nested under a toolset) |
178
+ | `context` | `get` |
179
+ | `orgs` | `list`, `get` |
180
+ | `code-containers` | `list` |
181
+ | `metrics` | `catalog`, `for-resource`, `get` |
182
+ | `users` | `list`, `get` |
183
+ | `notifications` | `list` |
184
+
185
+ `code-containers`, `metrics`, `users`, and `notifications` proxy resources the API hasn't implemented yet (they return HTTP 501 until it does).
186
+
187
+ ## Exit codes
188
+
189
+ | Code | Meaning |
190
+ |------|---------|
191
+ | 0 | success |
192
+ | 2 | bad local input (validation, `--dry-run` failure) |
193
+ | 3 | `NEXLA_API_URL`/`NEXLA_TOKEN` not set |
194
+ | 4 | 401/403 from the API |
195
+ | 5 | 404 |
196
+ | 6 | 5xx from the API |
@@ -0,0 +1,167 @@
1
+ # nexla-cli
2
+
3
+ Command-line client for the Nexla agent API. Depends on only `typer` and
4
+ `httpx` — no FastAPI, Daytona, Supabase, or other backend dependencies.
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ pipx install "git+https://github.com/abhishekkumar705/nexla-agent-cli.git"
10
+ ```
11
+
12
+ Or with `uv`:
13
+
14
+ ```bash
15
+ uv tool install "git+https://github.com/abhishekkumar705/nexla-agent-cli.git"
16
+ ```
17
+
18
+ ### From Node/TypeScript projects (no Python required)
19
+
20
+ ```bash
21
+ npm install -g nexla-cli
22
+ # or run once-off:
23
+ npx nexla-cli sources list
24
+ ```
25
+
26
+ This installs a prebuilt native binary (published to GitHub Releases by
27
+ `.github/workflows/release-binaries.yml`) behind a thin `npm/` wrapper — no
28
+ Python interpreter needed on the target machine. See `npm/README.md` for how
29
+ releases are cut.
30
+
31
+ ### From PyPI
32
+
33
+ ```bash
34
+ pip install nexla-cli
35
+ # or
36
+ uvx nexla-cli sources list
37
+ pipx install nexla-cli
38
+ ```
39
+
40
+ Published by `.github/workflows/publish-pypi.yml` — see "Publishing to
41
+ PyPI" below.
42
+
43
+ ## Publishing to PyPI
44
+
45
+ Tagging a release (`git tag vX.Y.Z && git push --tags` — same tag used to
46
+ trigger `.github/workflows/release-binaries.yml`, see `npm/README.md` for
47
+ the full release checklist) also triggers `publish-pypi.yml`, which builds
48
+ the sdist+wheel and publishes them to PyPI using
49
+ [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) (OIDC) —
50
+ no long-lived API token stored in this repo.
51
+
52
+ One-time setup required on pypi.org before the first release (repo owner
53
+ only, not automatable from here):
54
+
55
+ 1. Create the `nexla-cli` project on PyPI (or reserve the name).
56
+ 2. Project → Settings → Publishing → add a trusted publisher:
57
+ - Owner: `abhishekkumar705`, repo: `nexla-agent-cli`
58
+ - Workflow filename: `publish-pypi.yml`
59
+ - Environment name: `pypi`
60
+ 3. Nothing else to configure — no secrets to add. The workflow's
61
+ `id-token: write` permission plus the `pypi` GitHub Environment satisfy
62
+ PyPI's OIDC handshake automatically.
63
+
64
+ ## Quick start
65
+
66
+ ```bash
67
+ export NEXLA_API_URL=https://dev-api-express-code.nexla.com
68
+ export NEXLA_TOKEN=$(nexla login --service-key <your-service-key>)
69
+ nexla sources list
70
+ ```
71
+
72
+ `nexla login` prints a bearer token to stdout (see command substitution
73
+ above); alternatively, set the following environment variables directly:
74
+
75
+ - `NEXLA_API_URL` — base URL of the deployed Nexla agent API
76
+ - `NEXLA_TOKEN` — bearer token to authenticate requests
77
+
78
+ ## Using this CLI from Claude Code
79
+
80
+ The package ships a [Claude Code skill](https://docs.claude.com/en/docs/claude-code/skills) (`AGENTS.md` + `SKILL.md`, installed alongside the `nexla_cli` package) encoding invariants an agent can't infer from `--help` alone — output modes, `--dry-run`, `--json`/`--params` precedence, exit codes, and treating API responses as untrusted data.
81
+
82
+ Claude Code only discovers skills placed at `~/.claude/skills/<name>/SKILL.md` (global) or `.claude/skills/<name>/SKILL.md` (project-local) — a file merely present inside an installed pip package isn't picked up automatically. Symlink it in once, after installing the CLI. `pipx`/`uv tool install` both use an isolated venv, so a plain system `python3 -c "import nexla_cli"` won't find it — locate it inside that isolated venv instead:
83
+
84
+ ```bash
85
+ mkdir -p ~/.claude/skills/nexla-cli
86
+
87
+ # if installed with `uv tool install`:
88
+ ln -sf "$(uv tool dir)/nexla-cli/lib/python3."*"/site-packages/nexla_cli/SKILL.md" \
89
+ ~/.claude/skills/nexla-cli/SKILL.md
90
+
91
+ # if installed with `pipx install`:
92
+ ln -sf "$HOME/.local/pipx/venvs/nexla-cli/lib/python3."*"/site-packages/nexla_cli/SKILL.md" \
93
+ ~/.claude/skills/nexla-cli/SKILL.md
94
+ ```
95
+
96
+ A symlink (not a copy) means `nexla-cli` upgrades automatically pick up any future skill content updates. Restart Claude Code (or start a new session) after installing for it to be picked up.
97
+
98
+ ## Global flags
99
+
100
+ Available on every command except `login` and `schema` (both always print raw output regardless of these flags):
101
+
102
+ | Flag | Effect |
103
+ |------|--------|
104
+ | `--output` / `-o` `table\|json\|ndjson` | Force an output mode. Defaults to `table` on a TTY, `json` otherwise (also settable via `NEXLA_OUTPUT`/`OUTPUT_FORMAT`). |
105
+ | `--fields id,name,...` | Mask output down to just these keys, on any `list`/`get`. |
106
+ | `--page-all` | Stream every page of a `list` command as NDJSON instead of returning one page. |
107
+
108
+ These can be placed before or after the subcommand, e.g. both `nexla --output json sources list` and `nexla sources list --output json` work.
109
+
110
+ Every mutating command also accepts `--dry-run`: validates the request body against the live API schema and prints `{"valid": ...}` without making the real (mutating) call.
111
+
112
+ ## Raw JSON payloads
113
+
114
+ `create`/`update`-style commands (`sources`, `sinks`, `credentials`, `toolsets`, `nexsets transform`, `mcp-servers attach`, `tools set-runtime-config`) accept the full request body directly, not just their named flags:
115
+
116
+ ```bash
117
+ nexla sources create --name my-source --connector s3 --json '{"credential_id": 123}'
118
+ nexla sources update 42 --params description="updated via params"
119
+ ```
120
+
121
+ Precedence when a key is given more than one way: named CLI flags win, then `--json`, then `--params` (lowest). This lets you set a field the CLI hasn't added a dedicated flag for yet, without waiting on a CLI release.
122
+
123
+ ## Response sanitization
124
+
125
+ Every response is passed through a sanitizer before rendering, in every output mode: ANSI escape sequences, control characters, and invisible Unicode (zero-width spaces, byte-order marks, bidirectional overrides) are stripped unconditionally — always on, no flag. This defends against a malicious API response field hijacking a human's terminal, or hiding text from a human while an agent still reads it. It is not a semantic filter — API response *content* should still be treated as untrusted data (see `AGENTS.md`), this only strips characters no legitimate field value would ever need.
126
+
127
+ ## Full command reference
128
+
129
+ ```bash
130
+ nexla --help
131
+ nexla <resource> --help
132
+ ```
133
+
134
+ | Resource | Commands |
135
+ |----------|----------|
136
+ | `login` | `login --service-key <key> [--api-url <url>]` — exchanges a service key for a bearer token, printed to stdout |
137
+ | `schema` | `schema [<resource>.<verb>]` — machine-readable JSON signature of one command or the whole `/nexla/*` surface, fetched live from the deployed API's OpenAPI doc |
138
+ | `sources` | `list`, `get`, `create`, `update`, `activate`, `pause`, `delete`, `sample`, `file-upload` |
139
+ | `sinks` | `list`, `get`, `create`, `update`, `activate`, `pause`, `delete` |
140
+ | `nexsets` | `list`, `get`, `transform`, `activate` |
141
+ | `credentials` | `list`, `get`, `create`, `update`, `delete` |
142
+ | `flows` | `list`, `get`, `activate`, `pause`, `delete` |
143
+ | `transforms` | `test` |
144
+ | `connectors` | `search`, `describe`, `describe-credential`, `describe-credential-mode`, `describe-source`, `describe-source-endpoint`, `describe-sink`, `describe-sink-endpoint` |
145
+ | `probe` | `run` |
146
+ | `toolsets` | `list`, `get`, `create`, `update`, `delete`, `add-nexsets` |
147
+ | `tools` | `list`, `get`, `set-runtime-config`, `clear-runtime-config`, `delete` |
148
+ | `mcp-servers` | `list`, `attach`, `sync`, `detach` (nested under a toolset) |
149
+ | `context` | `get` |
150
+ | `orgs` | `list`, `get` |
151
+ | `code-containers` | `list` |
152
+ | `metrics` | `catalog`, `for-resource`, `get` |
153
+ | `users` | `list`, `get` |
154
+ | `notifications` | `list` |
155
+
156
+ `code-containers`, `metrics`, `users`, and `notifications` proxy resources the API hasn't implemented yet (they return HTTP 501 until it does).
157
+
158
+ ## Exit codes
159
+
160
+ | Code | Meaning |
161
+ |------|---------|
162
+ | 0 | success |
163
+ | 2 | bad local input (validation, `--dry-run` failure) |
164
+ | 3 | `NEXLA_API_URL`/`NEXLA_TOKEN` not set |
165
+ | 4 | 401/403 from the API |
166
+ | 5 | 404 |
167
+ | 6 | 5xx from the API |
@@ -0,0 +1,27 @@
1
+ # nexla-cli (npm wrapper)
2
+
3
+ Thin npm package that installs the prebuilt `nexla` binary — no Python
4
+ required. `postinstall` (`scripts/install.js`) downloads the binary matching
5
+ the current OS/arch from the GitHub release tagged `v<package version>`;
6
+ `bin/nexla.js` execs it and forwards argv/stdio/exit code.
7
+
8
+ ## Cutting a release
9
+
10
+ 1. Bump the version in both `pyproject.toml` and `npm/package.json` — they
11
+ must match, since `scripts/install.js` derives the release tag from
12
+ `npm/package.json`'s version.
13
+ 2. `git tag vX.Y.Z && git push --tags` — this triggers
14
+ `.github/workflows/release-binaries.yml`, which builds binaries for
15
+ macOS (arm64), Linux (x64), and Windows (x64) via PyInstaller and
16
+ attaches them to the GitHub release.
17
+ 3. Once the release assets are up, publish the npm wrapper:
18
+ ```bash
19
+ cd npm && npm publish
20
+ ```
21
+
22
+ Supported platforms: `darwin-arm64`, `linux-x64`, `win32-x64` (see
23
+ `scripts/install.js`'s `ASSETS` map — add an entry there and a matching
24
+ matrix row in the workflow to support more, e.g. Intel Mac was dropped
25
+ from the matrix due to GitHub's shrinking `macos-13` runner capacity;
26
+ re-add a `macos-13` row + a `darwin-x64` ASSETS entry if that's needed
27
+ again).
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const path = require("path");
4
+ const { spawnSync } = require("child_process");
5
+
6
+ const binName = process.platform === "win32" ? "nexla-bin.exe" : "nexla-bin";
7
+ const bin = path.join(__dirname, binName);
8
+
9
+ const result = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" });
10
+ if (result.error) {
11
+ console.error(`nexla-cli: could not run bundled binary (${result.error.message})`);
12
+ process.exit(1);
13
+ }
14
+ process.exit(result.status === null ? 1 : result.status);
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "nexla-cli",
3
+ "version": "0.2.0",
4
+ "description": "Command-line client for the Nexla agent API (native binary, no Python required)",
5
+ "bin": {
6
+ "nexla": "bin/nexla.js"
7
+ },
8
+ "scripts": {
9
+ "postinstall": "node scripts/install.js"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/abhishekkumar705/nexla-agent-cli.git"
14
+ },
15
+ "license": "MIT",
16
+ "os": ["darwin", "linux", "win32"],
17
+ "cpu": ["x64", "arm64"]
18
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ // Downloads the prebuilt `nexla` binary matching this machine from the
3
+ // GitHub release tagged to match this package's version.
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const https = require("https");
7
+ const pkg = require("../package.json");
8
+
9
+ const ASSETS = {
10
+ "darwin-arm64": "nexla-macos-arm64",
11
+ "linux-x64": "nexla-linux-x64",
12
+ "win32-x64": "nexla-windows-x64.exe",
13
+ };
14
+
15
+ const key = `${process.platform}-${process.arch}`;
16
+ const asset = ASSETS[key];
17
+ if (!asset) {
18
+ console.error(
19
+ `nexla-cli: no prebuilt binary for ${key}. Supported: ${Object.keys(ASSETS).join(", ")}`
20
+ );
21
+ process.exit(1);
22
+ }
23
+
24
+ const url = `https://github.com/abhishekkumar705/nexla-agent-cli/releases/download/v${pkg.version}/${asset}`;
25
+ const destName = process.platform === "win32" ? "nexla-bin.exe" : "nexla-bin";
26
+ const dest = path.join(__dirname, "..", "bin", destName);
27
+ // Skip download if a binary is already present.
28
+
29
+ if (fs.existsSync(dest)) {
30
+ console.log(`nexla-cli: using existing binary at ${dest}`);
31
+ process.exit(0);
32
+ }
33
+
34
+ function download(url, redirectsLeft) {
35
+ return new Promise((resolve, reject) => {
36
+ https
37
+ .get(url, (res) => {
38
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
39
+ if (redirectsLeft <= 0) return reject(new Error("too many redirects"));
40
+ res.resume();
41
+ return resolve(download(res.headers.location, redirectsLeft - 1));
42
+ }
43
+ if (res.statusCode !== 200) {
44
+ return reject(new Error(`download failed: HTTP ${res.statusCode} for ${url}`));
45
+ }
46
+ const file = fs.createWriteStream(dest);
47
+ res.pipe(file);
48
+ file.on("finish", () => file.close(resolve));
49
+ file.on("error", reject);
50
+ })
51
+ .on("error", reject);
52
+ });
53
+ }
54
+
55
+ download(url, 5)
56
+ .then(() => {
57
+ if (process.platform !== "win32") fs.chmodSync(dest, 0o755);
58
+ })
59
+ .catch((err) => {
60
+ console.error(`nexla-cli: failed to download binary from ${url}\n${err.message}`);
61
+ process.exit(1);
62
+ });
@@ -0,0 +1,52 @@
1
+ [project]
2
+ name = "nexla-cli"
3
+ version = "0.2.0"
4
+ description = "Command-line client for the Nexla agent API."
5
+ readme = "README.md"
6
+ # Same MIT license already declared for this codebase in npm/package.json.
7
+ license = "MIT"
8
+ authors = [{ name = "Abhishek Kumar", email = "abhishek.kumar@nexla.com" }]
9
+ requires-python = ">=3.12"
10
+ dependencies = ["typer>=0.12.0", "httpx>=0.28.0"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Topic :: Utilities",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/abhishekkumar705/nexla-agent-cli"
24
+ Repository = "https://github.com/abhishekkumar705/nexla-agent-cli"
25
+ Issues = "https://github.com/abhishekkumar705/nexla-agent-cli/issues"
26
+
27
+ [project.scripts]
28
+ nexla = "nexla_cli:main"
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "pytest>=9.0.3",
33
+ "pytest-asyncio>=1.3.0",
34
+ "respx>=0.23.1",
35
+ "ruff>=0.15.12",
36
+ "mypy>=1.13.0",
37
+ ]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/nexla_cli"]
45
+
46
+ [tool.ruff]
47
+ line-length = 100
48
+ target-version = "py312"
49
+
50
+ [tool.pytest.ini_options]
51
+ asyncio_mode = "auto"
52
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ from nexla_cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()