cinna-cli 0.1.4__tar.gz → 0.1.6__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.
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/PKG-INFO +5 -2
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/README.md +4 -1
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/docs/README.md +56 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/pyproject.toml +1 -1
- cinna_cli-0.1.6/src/cinna/__init__.py +8 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/main.py +8 -1
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_main.py +34 -2
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/uv.lock +1 -1
- cinna_cli-0.1.4/src/cinna/__init__.py +0 -3
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/.github/workflows/publish.yml +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/.gitignore +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/LICENSE.md +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/docs/interface.md +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/docs/mutagen_capabilities.md +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/auth.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/bootstrap.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/client.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/config.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/console.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/context.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/errors.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/logging.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/mcp_proxy.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/mutagen_runtime.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/sync.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/sync_session.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/sync_ssh_shim.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/sync_tui.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/templates/CLAUDE.md.template +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/src/cinna/templates/__init__.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/__init__.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/conftest.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_auth.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_bootstrap.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_client.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_config.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_context.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_mutagen_runtime.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_sync.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_sync_session.py +0 -0
- {cinna_cli-0.1.4 → cinna_cli-0.1.6}/tests/test_sync_ssh_shim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cinna-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Local development CLI for Cinna Core agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/opencinna/cinna-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/opencinna/cinna-cli
|
|
@@ -127,10 +127,13 @@ Read-only views onto the live sync session (started by `cinna dev`).
|
|
|
127
127
|
|
|
128
128
|
Stream a command through the platform to the remote agent environment. Output streams back live; Ctrl+C aborts. Exit code matches the remote process.
|
|
129
129
|
|
|
130
|
+
Arguments pass through transparently — each token is re-quoted before being sent, so spaces and shell metacharacters inside an argument survive intact. Use ordinary single-level quoting, exactly as for a local command. To run a shell snippet (pipes, redirects, `&&`), pass it to a shell explicitly: `cinna exec bash -c '…'`.
|
|
131
|
+
|
|
130
132
|
```bash
|
|
131
133
|
cinna exec python scripts/main.py
|
|
132
134
|
cinna exec pip install pandas
|
|
133
|
-
cinna exec
|
|
135
|
+
cinna exec bash -c 'ls -la'
|
|
136
|
+
cinna exec python -c 'import sys; print(sys.argv)' "a b"
|
|
134
137
|
```
|
|
135
138
|
|
|
136
139
|
### `cinna status`
|
|
@@ -90,10 +90,13 @@ Read-only views onto the live sync session (started by `cinna dev`).
|
|
|
90
90
|
|
|
91
91
|
Stream a command through the platform to the remote agent environment. Output streams back live; Ctrl+C aborts. Exit code matches the remote process.
|
|
92
92
|
|
|
93
|
+
Arguments pass through transparently — each token is re-quoted before being sent, so spaces and shell metacharacters inside an argument survive intact. Use ordinary single-level quoting, exactly as for a local command. To run a shell snippet (pipes, redirects, `&&`), pass it to a shell explicitly: `cinna exec bash -c '…'`.
|
|
94
|
+
|
|
93
95
|
```bash
|
|
94
96
|
cinna exec python scripts/main.py
|
|
95
97
|
cinna exec pip install pandas
|
|
96
|
-
cinna exec
|
|
98
|
+
cinna exec bash -c 'ls -la'
|
|
99
|
+
cinna exec python -c 'import sys; print(sys.argv)' "a b"
|
|
97
100
|
```
|
|
98
101
|
|
|
99
102
|
### `cinna status`
|
|
@@ -328,6 +328,23 @@ Two surfaces expose conflicts:
|
|
|
328
328
|
|
|
329
329
|
Ctrl+C closes the SSE stream; the platform cleans up the remote process. Interactive stdin (REPLs, debuggers) is out of scope for the current `/exec` endpoint.
|
|
330
330
|
|
|
331
|
+
### Argument quoting
|
|
332
|
+
|
|
333
|
+
There are **two shell passes** between the keyboard and the remote process, and only one round of quoting belongs to each:
|
|
334
|
+
|
|
335
|
+
1. **Local shell** (the caller's terminal / agent Bash tool) splits the command line into argv and strips one layer of quotes. `exec` is declared `nargs=-1`, so Click receives these already-split tokens as a tuple.
|
|
336
|
+
2. **Remote shell** — the platform runs the `command` string through `/bin/sh -c`, re-parsing it a second time.
|
|
337
|
+
|
|
338
|
+
`exec_cmd` (`main.py`) bridges the two with `shlex.join(command)`: it re-quotes each token so the remote `sh -c` reconstructs the *exact* argv the caller typed. This makes `cinna exec` a transparent passthrough — callers write ordinary single-level quoting, exactly as for a local command:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
cinna exec python -c 'import sys; print(sys.argv)' "a b" '[{"x":"y z"}]'
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
The historical `" ".join(command)` dropped the word boundaries that the local shell's quoting had implied, so any argument containing a space or a shell metacharacter (`;`, `(`, `{`, …) was re-split or mis-parsed by the remote shell — e.g. `print(sys.argv)` failing with `/bin/sh: Syntax error: word unexpected (expecting ")")`. Regression coverage: `test_exec_command_requotes_args` in `tests/test_main.py`.
|
|
345
|
+
|
|
346
|
+
To run an actual remote shell snippet (pipes, redirects, `&&`), pass it explicitly to a shell: `cinna exec bash -c 'a | b > c'`.
|
|
347
|
+
|
|
331
348
|
---
|
|
332
349
|
|
|
333
350
|
## Bootstrap Flow
|
|
@@ -425,6 +442,45 @@ uv run ruff format --check src/
|
|
|
425
442
|
|
|
426
443
|
---
|
|
427
444
|
|
|
445
|
+
## Release Management
|
|
446
|
+
|
|
447
|
+
The CLI is published to [PyPI](https://pypi.org/project/cinna-cli/) by `.github/workflows/publish.yml`, which runs on any pushed tag matching `v*` (and via manual `workflow_dispatch`). It builds the sdist + wheel with `uv build`, runs `twine check`, then publishes through PyPI **Trusted Publishing** (OIDC — no stored API token) from the GitHub `pypi` environment.
|
|
448
|
+
|
|
449
|
+
### Cutting a release
|
|
450
|
+
|
|
451
|
+
Versioning is SemVer (`MAJOR.MINOR.PATCH`); a patch release is the common case. From a clean `main` with the changes you want to ship already merged:
|
|
452
|
+
|
|
453
|
+
1. **Bump the version** — edit `version` in `pyproject.toml` (e.g. `0.1.4` → `0.1.5`).
|
|
454
|
+
2. **Refresh the lockfile** — run `uv lock` so the `cinna-cli` entry in `uv.lock` matches the new version. These two files are the *only* changes in a release commit.
|
|
455
|
+
3. **Commit** with the exact message `release vX.Y.Z` (this is the established convention — release commits touch nothing but `pyproject.toml` + `uv.lock`).
|
|
456
|
+
4. **Tag** the release commit: `git tag vX.Y.Z` (lightweight tag, on the `release` commit).
|
|
457
|
+
5. **Push both** the branch and the tag: `git push origin main && git push origin vX.Y.Z`. Pushing the tag is what triggers the publish workflow.
|
|
458
|
+
|
|
459
|
+
```bash
|
|
460
|
+
# version already bumped in pyproject.toml
|
|
461
|
+
uv lock
|
|
462
|
+
git add pyproject.toml uv.lock
|
|
463
|
+
git commit -m "release v0.1.5"
|
|
464
|
+
git tag v0.1.5
|
|
465
|
+
git push origin main && git push origin v0.1.5
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Manual approval gate
|
|
469
|
+
|
|
470
|
+
> **The release manager must manually approve the PyPI publish.** Pushing the tag only *starts* the workflow — the `publish` job targets the protected `pypi` environment, which requires a reviewer to sign off before it runs. Go to **https://github.com/opencinna/cinna-cli/actions**, open the run for the tag you just pushed, and **approve the deployment**. Until you approve, the build completes but nothing is published to PyPI.
|
|
471
|
+
|
|
472
|
+
### Required changes checklist
|
|
473
|
+
|
|
474
|
+
| File | Change | Why |
|
|
475
|
+
|------|--------|-----|
|
|
476
|
+
| `pyproject.toml` | `version = "X.Y.Z"` | The single source of truth for the published package version. |
|
|
477
|
+
| `uv.lock` | regenerated via `uv lock` | Keeps the lockfile's `cinna-cli` entry in sync; CI builds from a consistent tree. |
|
|
478
|
+
| git tag `vX.Y.Z` | created on the release commit | The only trigger for the publish workflow. |
|
|
479
|
+
|
|
480
|
+
`pyproject.toml` is the **only** place the version lives. `src/cinna/__init__.py` derives `__version__` at runtime from the installed package metadata (`importlib.metadata.version("cinna-cli")`), which is what `cinna --version` reports — so it tracks `pyproject.toml` automatically and never needs a manual bump. (It falls back to `0.0.0+unknown` only when run from a source tree with no installed metadata.)
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
428
484
|
## Tech Stack
|
|
429
485
|
|
|
430
486
|
| Component | Choice | Rationale |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""cinna-cli — Local development CLI for Cinna Core agents."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("cinna-cli")
|
|
7
|
+
except PackageNotFoundError: # not installed (e.g. running from a source tree without metadata)
|
|
8
|
+
__version__ = "0.0.0+unknown"
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import platform
|
|
6
|
+
import shlex
|
|
6
7
|
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
import time
|
|
@@ -125,10 +126,16 @@ def exec_cmd(timeout: int, command: tuple[str, ...]):
|
|
|
125
126
|
Output streams back in real time via the platform. Exit code matches the
|
|
126
127
|
remote process's exit code. Ctrl+C aborts the stream.
|
|
127
128
|
|
|
129
|
+
Arguments are passed through transparently: each token you type is
|
|
130
|
+
re-quoted (``shlex.quote``) before being sent, so spaces and shell
|
|
131
|
+
metacharacters inside an argument survive the remote shell intact. Use
|
|
132
|
+
ordinary single-level quoting, exactly as for a local command.
|
|
133
|
+
|
|
128
134
|
Examples:
|
|
129
135
|
cinna exec python scripts/main.py
|
|
130
136
|
cinna exec pip install pandas
|
|
131
137
|
cinna exec bash -c 'ls -la'
|
|
138
|
+
cinna exec python -c 'import sys; print(sys.argv)' "a b"
|
|
132
139
|
cinna exec --timeout 3600 python long_backfill.py
|
|
133
140
|
|
|
134
141
|
If your remote command takes its own ``--timeout`` flag, separate it
|
|
@@ -139,7 +146,7 @@ def exec_cmd(timeout: int, command: tuple[str, ...]):
|
|
|
139
146
|
root = find_workspace_root()
|
|
140
147
|
config = load_config(root)
|
|
141
148
|
|
|
142
|
-
exit_code = _run_remote_exec(config,
|
|
149
|
+
exit_code = _run_remote_exec(config, shlex.join(command), timeout=timeout)
|
|
143
150
|
sys.exit(exit_code)
|
|
144
151
|
|
|
145
152
|
|
|
@@ -13,9 +13,13 @@ def runner():
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def test_version(runner):
|
|
16
|
+
from cinna import __version__
|
|
17
|
+
|
|
16
18
|
result = runner.invoke(cli, ["--version"])
|
|
17
19
|
assert result.exit_code == 0
|
|
18
|
-
|
|
20
|
+
# __version__ is derived from package metadata, not hard-coded — assert the
|
|
21
|
+
# CLI reports whatever the installed package version is.
|
|
22
|
+
assert __version__ in result.output
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
def test_status_no_workspace(runner):
|
|
@@ -215,7 +219,35 @@ def test_exec_command(mock_load, mock_find, mock_exec, runner, workspace_root, s
|
|
|
215
219
|
|
|
216
220
|
result = runner.invoke(cli, ["exec", "python", "scripts/main.py"])
|
|
217
221
|
assert result.exit_code == 0
|
|
218
|
-
mock_exec.assert_called_once_with(
|
|
222
|
+
mock_exec.assert_called_once_with(
|
|
223
|
+
sample_config, "python scripts/main.py", timeout=1800
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@patch("cinna.main._run_remote_exec")
|
|
228
|
+
@patch("cinna.main.find_workspace_root")
|
|
229
|
+
@patch("cinna.main.load_config")
|
|
230
|
+
def test_exec_command_requotes_args(
|
|
231
|
+
mock_load, mock_find, mock_exec, runner, workspace_root, sample_config
|
|
232
|
+
):
|
|
233
|
+
"""Tokens with spaces / shell metacharacters must be re-quoted so the
|
|
234
|
+
remote shell reconstructs the exact argv the caller intended, rather than
|
|
235
|
+
re-splitting them. Regression for `cinna exec` mangling quoted arguments.
|
|
236
|
+
"""
|
|
237
|
+
mock_find.return_value = workspace_root
|
|
238
|
+
mock_load.return_value = sample_config
|
|
239
|
+
mock_exec.return_value = 0
|
|
240
|
+
|
|
241
|
+
result = runner.invoke(
|
|
242
|
+
cli,
|
|
243
|
+
["exec", "python", "-c", "import sys; print(sys.argv)", "a b", '[{"x":"y z"}]'],
|
|
244
|
+
)
|
|
245
|
+
assert result.exit_code == 0
|
|
246
|
+
mock_exec.assert_called_once_with(
|
|
247
|
+
sample_config,
|
|
248
|
+
"""python -c 'import sys; print(sys.argv)' 'a b' '[{"x":"y z"}]'""",
|
|
249
|
+
timeout=1800,
|
|
250
|
+
)
|
|
219
251
|
|
|
220
252
|
|
|
221
253
|
@patch("cinna.main.PlatformClient")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|