cinna-cli 0.1.4__tar.gz → 0.1.5__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 (40) hide show
  1. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/PKG-INFO +5 -2
  2. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/README.md +4 -1
  3. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/docs/README.md +17 -0
  4. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/pyproject.toml +1 -1
  5. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/main.py +8 -1
  6. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_main.py +29 -1
  7. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/uv.lock +1 -1
  8. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/.github/workflows/publish.yml +0 -0
  9. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/.gitignore +0 -0
  10. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/LICENSE.md +0 -0
  11. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/docs/interface.md +0 -0
  12. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/docs/mutagen_capabilities.md +0 -0
  13. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/__init__.py +0 -0
  14. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/auth.py +0 -0
  15. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/bootstrap.py +0 -0
  16. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/client.py +0 -0
  17. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/config.py +0 -0
  18. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/console.py +0 -0
  19. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/context.py +0 -0
  20. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/errors.py +0 -0
  21. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/logging.py +0 -0
  22. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/mcp_proxy.py +0 -0
  23. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/mutagen_runtime.py +0 -0
  24. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/sync.py +0 -0
  25. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/sync_session.py +0 -0
  26. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/sync_ssh_shim.py +0 -0
  27. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/sync_tui.py +0 -0
  28. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/templates/CLAUDE.md.template +0 -0
  29. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/src/cinna/templates/__init__.py +0 -0
  30. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/__init__.py +0 -0
  31. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/conftest.py +0 -0
  32. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_auth.py +0 -0
  33. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_bootstrap.py +0 -0
  34. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_client.py +0 -0
  35. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_config.py +0 -0
  36. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_context.py +0 -0
  37. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_mutagen_runtime.py +0 -0
  38. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_sync.py +0 -0
  39. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/tests/test_sync_session.py +0 -0
  40. {cinna_cli-0.1.4 → cinna_cli-0.1.5}/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.4
3
+ Version: 0.1.5
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 'bash -c "ls -la"'
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 'bash -c "ls -la"'
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinna-cli"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "Local development CLI for Cinna Core agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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, " ".join(command), timeout=timeout)
149
+ exit_code = _run_remote_exec(config, shlex.join(command), timeout=timeout)
143
150
  sys.exit(exit_code)
144
151
 
145
152
 
@@ -215,7 +215,35 @@ def test_exec_command(mock_load, mock_find, mock_exec, runner, workspace_root, s
215
215
 
216
216
  result = runner.invoke(cli, ["exec", "python", "scripts/main.py"])
217
217
  assert result.exit_code == 0
218
- mock_exec.assert_called_once_with(sample_config, "python scripts/main.py")
218
+ mock_exec.assert_called_once_with(
219
+ sample_config, "python scripts/main.py", timeout=1800
220
+ )
221
+
222
+
223
+ @patch("cinna.main._run_remote_exec")
224
+ @patch("cinna.main.find_workspace_root")
225
+ @patch("cinna.main.load_config")
226
+ def test_exec_command_requotes_args(
227
+ mock_load, mock_find, mock_exec, runner, workspace_root, sample_config
228
+ ):
229
+ """Tokens with spaces / shell metacharacters must be re-quoted so the
230
+ remote shell reconstructs the exact argv the caller intended, rather than
231
+ re-splitting them. Regression for `cinna exec` mangling quoted arguments.
232
+ """
233
+ mock_find.return_value = workspace_root
234
+ mock_load.return_value = sample_config
235
+ mock_exec.return_value = 0
236
+
237
+ result = runner.invoke(
238
+ cli,
239
+ ["exec", "python", "-c", "import sys; print(sys.argv)", "a b", '[{"x":"y z"}]'],
240
+ )
241
+ assert result.exit_code == 0
242
+ mock_exec.assert_called_once_with(
243
+ sample_config,
244
+ """python -c 'import sys; print(sys.argv)' 'a b' '[{"x":"y z"}]'""",
245
+ timeout=1800,
246
+ )
219
247
 
220
248
 
221
249
  @patch("cinna.main.PlatformClient")
@@ -136,7 +136,7 @@ wheels = [
136
136
 
137
137
  [[package]]
138
138
  name = "cinna-cli"
139
- version = "0.1.4"
139
+ version = "0.1.5"
140
140
  source = { editable = "." }
141
141
  dependencies = [
142
142
  { name = "click" },
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