contree-cli 0.8.0__tar.gz → 0.9.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 (70) hide show
  1. {contree_cli-0.8.0 → contree_cli-0.9.0}/PKG-INFO +19 -19
  2. {contree_cli-0.8.0 → contree_cli-0.9.0}/README.md +17 -18
  3. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/__init__.py +5 -4
  4. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/__main__.py +47 -21
  5. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/agent.md +27 -25
  6. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/arguments.py +2 -0
  7. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/auth.py +31 -30
  8. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/build.py +58 -44
  9. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/cat.py +3 -5
  10. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/cd.py +5 -9
  11. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/cp.py +33 -36
  12. contree_cli-0.9.0/contree_cli/cli/export.py +172 -0
  13. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/file.py +41 -73
  14. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/images.py +49 -59
  15. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/ls.py +18 -11
  16. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/operation.py +51 -71
  17. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/run.py +107 -180
  18. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/session.py +16 -9
  19. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/show.py +14 -13
  20. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/skill.py +10 -8
  21. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/tag.py +7 -4
  22. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/use.py +11 -14
  23. contree_cli-0.9.0/contree_cli/client.py +110 -0
  24. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/config.py +69 -94
  25. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/context.py +29 -3
  26. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_add.py +3 -2
  27. contree_cli-0.9.0/contree_cli/docker/kw_copy.py +350 -0
  28. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_from.py +53 -26
  29. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_run.py +30 -31
  30. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/url_fetch.py +8 -14
  31. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/log.py +5 -0
  32. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/manual.md +9 -9
  33. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/session.py +8 -0
  34. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/argmap.py +2 -0
  35. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/completer.py +8 -6
  36. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/repl.py +8 -6
  37. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/sources.py +45 -29
  38. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/skill.py +174 -79
  39. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/skill_body.md +15 -30
  40. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/types.py +4 -6
  41. {contree_cli-0.8.0 → contree_cli-0.9.0}/pyproject.toml +4 -2
  42. contree_cli-0.8.0/contree_cli/client.py +0 -845
  43. contree_cli-0.8.0/contree_cli/docker/kw_copy.py +0 -178
  44. {contree_cli-0.8.0 → contree_cli-0.9.0}/LICENSE +0 -0
  45. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/__init__.py +0 -0
  46. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/agent.py +0 -0
  47. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/cli/env.py +0 -0
  48. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/__init__.py +0 -0
  49. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/dockerignore.py +0 -0
  50. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/keyword.py +0 -0
  51. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_arg.py +0 -0
  52. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_env.py +0 -0
  53. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_skipped.py +0 -0
  54. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_user.py +0 -0
  55. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/kw_workdir.py +0 -0
  56. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/local_context.py +0 -0
  57. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/docker/parser.py +0 -0
  58. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/man.py +0 -0
  59. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/mapped_file.py +0 -0
  60. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/migrations.py +0 -0
  61. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/output.py +0 -0
  62. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/py.typed +0 -0
  63. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/refs.py +0 -0
  64. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/__init__.py +0 -0
  65. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/argspec.py +0 -0
  66. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/cache.py +0 -0
  67. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/history.py +0 -0
  68. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/parser.py +0 -0
  69. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/shell/trie.py +0 -0
  70. {contree_cli-0.8.0 → contree_cli-0.9.0}/contree_cli/update_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: contree-cli
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: CLI client for the ConTree sandbox platform
5
5
  Keywords: sandbox,cli,vm,ai-agent,contree
6
6
  Author: Dmitry Orlov
@@ -197,6 +197,7 @@ Classifier: Programming Language :: Python :: 3.14
197
197
  Classifier: Topic :: Software Development :: Build Tools
198
198
  Classifier: Topic :: System :: Shells
199
199
  Classifier: Typing :: Typed
200
+ Requires-Dist: contree-client>=0.1.3
200
201
  Requires-Python: >=3.10
201
202
  Project-URL: Homepage, https://contree.dev
202
203
  Project-URL: Documentation, https://docs.contree.dev/cli/
@@ -208,7 +209,6 @@ Description-Content-Type: text/markdown
208
209
  # contree-cli
209
210
 
210
211
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
211
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#zero-dependencies)
212
212
  [![PyPI](https://img.shields.io/pypi/v/contree-cli.svg)](https://pypi.org/project/contree-cli/)
213
213
 
214
214
  Command-line client for the [ConTree](https://contree.dev) sandboxing platform — secure, VM-isolated sandboxes with git-like branching for AI agents and developers.
@@ -218,8 +218,9 @@ eval $(contree use tag:ubuntu:latest) # pick a base image for current session
218
218
  contree run apt-get update -qq # each run snapshots the result
219
219
  contree run apt-get install -y curl # builds on the previous snapshot
220
220
  contree session branch experiment # branch the sandbox state
221
+ contree session checkout experiment # switch to the branch
221
222
  contree run -- make test # experiment freely
222
- contree session checkout main # switch back instantly
223
+ contree session checkout main # switch back
223
224
  contree session rollback -- -2 # or rewind two steps
224
225
  ```
225
226
 
@@ -234,8 +235,6 @@ contree session rollback -- -2 # or rewind two steps
234
235
  - **Safe code execution** — run untrusted or LLM-generated code inside VM-level isolation; crashes and side effects stay in the sandbox
235
236
  - **Session continuity** — rewind and resume long-running agent workflows with full filesystem context preserved
236
237
 
237
- `contree-cli` talks to the ConTree API. Install it, authenticate with your project token, and create sandboxes, run commands, inspect filesystems, and manage sessions — all from your terminal, shell scripts, or agent toolchains.
238
-
239
238
  ## Install
240
239
 
241
240
  ```bash
@@ -269,7 +268,7 @@ Verify:
269
268
  contree --help
270
269
  ```
271
270
 
272
- **Requirements:** Python 3.10+ and nothing else. Zero external dependencies — stdlib only.
271
+ **Requirements:** Python 3.10+.
273
272
 
274
273
  ## Quick Start
275
274
 
@@ -289,7 +288,7 @@ If `--token`/`--url`/`--project` flags are omitted, `contree auth` reads `CONTRE
289
288
  contree skill install
290
289
  ```
291
290
 
292
- Autodetects installed agents (Claude Code, Codex, OpenCode, Cline, Amp) and installs ConTree skill files into their skill directories. Use `contree skill install -F` to force-overwrite.
291
+ Autodetects installed agents (Claude Code, Codex, OpenCode, Cline, Amp) and installs ConTree skill files into their skill directories. Use `contree skill install -f` to force-overwrite.
293
292
 
294
293
  ### 3. Start a session
295
294
 
@@ -321,7 +320,8 @@ contree cp /app/output.log . # download to local machine
321
320
 
322
321
  ```bash
323
322
  contree session branch experiment # create a branch
324
- contree run -- make test # experiment on it
323
+ contree session checkout experiment # switch to it
324
+ contree run -- make test # experiment on the branch
325
325
  contree session checkout main # switch back
326
326
  contree session rollback # undo last run (default: back 1 entry)
327
327
  ```
@@ -441,7 +441,7 @@ main: A ── B ── C ── D
441
441
  experiment: E ── F
442
442
  ```
443
443
 
444
- Every `run` creates a checkpoint. Branch to explore alternatives. Roll back to any point. Switch branches instantly.
444
+ Every non-disposable `run` creates a checkpoint in the session history.
445
445
 
446
446
  ```bash
447
447
  contree session # show current state
@@ -454,14 +454,14 @@ contree session use other-session # import image from another session
454
454
 
455
455
  ## Output Formats
456
456
 
457
- All commands support structured output via `-f`/`--format`:
457
+ All commands support structured output via `-o`/`--format`/`--output`:
458
458
 
459
459
  ```bash
460
- contree images -f json # JSON (one object per line)
461
- contree images -f json-pretty # pretty-printed JSON array
462
- contree ps -f csv # RFC 4180 CSV
463
- contree ps -f tsv # tab-separated values
464
- contree ls -f table # ASCII table
460
+ contree -o json images # JSON (one object per line)
461
+ contree -o json-pretty images # pretty-printed JSON array
462
+ contree -o csv ps # RFC 4180 CSV
463
+ contree -o tsv ps # tab-separated values
464
+ contree -o table ls # ASCII table
465
465
  ```
466
466
 
467
467
  Pipe JSON output into `jq`, feed CSV into spreadsheets, or parse programmatically in your agent toolchain.
@@ -490,7 +490,7 @@ contree auth --profile=staging # save staging token
490
490
  contree auth --profile=prod # save production token
491
491
  contree auth profiles # list all profiles + status probe
492
492
  contree auth profiles --offline # list profiles without network checks
493
- contree -f json auth profiles # structured profile health output
493
+ contree -o json auth profiles # structured profile health output
494
494
  contree auth switch staging # switch active profile
495
495
  ```
496
496
 
@@ -515,9 +515,9 @@ Read only by `contree auth` (registration-time fallbacks for omitted flags):
515
515
 
516
516
  Credentials come strictly from the saved profile at runtime. `--token`, `--url`, `--project` CLI flags override profile fields for a single invocation.
517
517
 
518
- ## Zero Dependencies
518
+ ## Dependencies
519
519
 
520
- `contree-cli` uses only the Python standard library. No `requests`, no `click`, no `rich` just `http.client`, `argparse`, `json`, `sqlite3`, and friends. It runs anywhere Python 3.10+ is available with nothing to install beyond the package itself.
520
+ `contree-cli` has a single runtime dependency: the `contree-client` library, which provides the HTTP transport and typed API bindings.
521
521
 
522
522
  ## Development
523
523
 
@@ -534,7 +534,7 @@ make check # lint + types
534
534
  make tests # lint + types + pytest
535
535
  ```
536
536
 
537
- The project enforces strict mypy, ruff linting (E/F/W/I/UP/B/SIM/RUF rules), and full test coverage across 23+ test modules.
537
+ The project enforces strict mypy and ruff linting (E/F/W/I/UP/B/SIM/RUF rules).
538
538
 
539
539
  ## Documentation
540
540
 
@@ -1,7 +1,6 @@
1
1
  # contree-cli
2
2
 
3
3
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
4
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#zero-dependencies)
5
4
  [![PyPI](https://img.shields.io/pypi/v/contree-cli.svg)](https://pypi.org/project/contree-cli/)
6
5
 
7
6
  Command-line client for the [ConTree](https://contree.dev) sandboxing platform — secure, VM-isolated sandboxes with git-like branching for AI agents and developers.
@@ -11,8 +10,9 @@ eval $(contree use tag:ubuntu:latest) # pick a base image for current session
11
10
  contree run apt-get update -qq # each run snapshots the result
12
11
  contree run apt-get install -y curl # builds on the previous snapshot
13
12
  contree session branch experiment # branch the sandbox state
13
+ contree session checkout experiment # switch to the branch
14
14
  contree run -- make test # experiment freely
15
- contree session checkout main # switch back instantly
15
+ contree session checkout main # switch back
16
16
  contree session rollback -- -2 # or rewind two steps
17
17
  ```
18
18
 
@@ -27,8 +27,6 @@ contree session rollback -- -2 # or rewind two steps
27
27
  - **Safe code execution** — run untrusted or LLM-generated code inside VM-level isolation; crashes and side effects stay in the sandbox
28
28
  - **Session continuity** — rewind and resume long-running agent workflows with full filesystem context preserved
29
29
 
30
- `contree-cli` talks to the ConTree API. Install it, authenticate with your project token, and create sandboxes, run commands, inspect filesystems, and manage sessions — all from your terminal, shell scripts, or agent toolchains.
31
-
32
30
  ## Install
33
31
 
34
32
  ```bash
@@ -62,7 +60,7 @@ Verify:
62
60
  contree --help
63
61
  ```
64
62
 
65
- **Requirements:** Python 3.10+ and nothing else. Zero external dependencies — stdlib only.
63
+ **Requirements:** Python 3.10+.
66
64
 
67
65
  ## Quick Start
68
66
 
@@ -82,7 +80,7 @@ If `--token`/`--url`/`--project` flags are omitted, `contree auth` reads `CONTRE
82
80
  contree skill install
83
81
  ```
84
82
 
85
- Autodetects installed agents (Claude Code, Codex, OpenCode, Cline, Amp) and installs ConTree skill files into their skill directories. Use `contree skill install -F` to force-overwrite.
83
+ Autodetects installed agents (Claude Code, Codex, OpenCode, Cline, Amp) and installs ConTree skill files into their skill directories. Use `contree skill install -f` to force-overwrite.
86
84
 
87
85
  ### 3. Start a session
88
86
 
@@ -114,7 +112,8 @@ contree cp /app/output.log . # download to local machine
114
112
 
115
113
  ```bash
116
114
  contree session branch experiment # create a branch
117
- contree run -- make test # experiment on it
115
+ contree session checkout experiment # switch to it
116
+ contree run -- make test # experiment on the branch
118
117
  contree session checkout main # switch back
119
118
  contree session rollback # undo last run (default: back 1 entry)
120
119
  ```
@@ -234,7 +233,7 @@ main: A ── B ── C ── D
234
233
  experiment: E ── F
235
234
  ```
236
235
 
237
- Every `run` creates a checkpoint. Branch to explore alternatives. Roll back to any point. Switch branches instantly.
236
+ Every non-disposable `run` creates a checkpoint in the session history.
238
237
 
239
238
  ```bash
240
239
  contree session # show current state
@@ -247,14 +246,14 @@ contree session use other-session # import image from another session
247
246
 
248
247
  ## Output Formats
249
248
 
250
- All commands support structured output via `-f`/`--format`:
249
+ All commands support structured output via `-o`/`--format`/`--output`:
251
250
 
252
251
  ```bash
253
- contree images -f json # JSON (one object per line)
254
- contree images -f json-pretty # pretty-printed JSON array
255
- contree ps -f csv # RFC 4180 CSV
256
- contree ps -f tsv # tab-separated values
257
- contree ls -f table # ASCII table
252
+ contree -o json images # JSON (one object per line)
253
+ contree -o json-pretty images # pretty-printed JSON array
254
+ contree -o csv ps # RFC 4180 CSV
255
+ contree -o tsv ps # tab-separated values
256
+ contree -o table ls # ASCII table
258
257
  ```
259
258
 
260
259
  Pipe JSON output into `jq`, feed CSV into spreadsheets, or parse programmatically in your agent toolchain.
@@ -283,7 +282,7 @@ contree auth --profile=staging # save staging token
283
282
  contree auth --profile=prod # save production token
284
283
  contree auth profiles # list all profiles + status probe
285
284
  contree auth profiles --offline # list profiles without network checks
286
- contree -f json auth profiles # structured profile health output
285
+ contree -o json auth profiles # structured profile health output
287
286
  contree auth switch staging # switch active profile
288
287
  ```
289
288
 
@@ -308,9 +307,9 @@ Read only by `contree auth` (registration-time fallbacks for omitted flags):
308
307
 
309
308
  Credentials come strictly from the saved profile at runtime. `--token`, `--url`, `--project` CLI flags override profile fields for a single invocation.
310
309
 
311
- ## Zero Dependencies
310
+ ## Dependencies
312
311
 
313
- `contree-cli` uses only the Python standard library. No `requests`, no `click`, no `rich` just `http.client`, `argparse`, `json`, `sqlite3`, and friends. It runs anywhere Python 3.10+ is available with nothing to install beyond the package itself.
312
+ `contree-cli` has a single runtime dependency: the `contree-client` library, which provides the HTTP transport and typed API bindings.
314
313
 
315
314
  ## Development
316
315
 
@@ -327,7 +326,7 @@ make check # lint + types
327
326
  make tests # lint + types + pytest
328
327
  ```
329
328
 
330
- The project enforces strict mypy, ruff linting (E/F/W/I/UP/B/SIM/RUF rules), and full test coverage across 23+ test modules.
329
+ The project enforces strict mypy and ruff linting (E/F/W/I/UP/B/SIM/RUF rules).
331
330
 
332
331
  ## Documentation
333
332
 
@@ -6,13 +6,14 @@ from contextvars import ContextVar
6
6
  from typing import TYPE_CHECKING, Protocol
7
7
 
8
8
  if TYPE_CHECKING:
9
- from contree_cli.client import ContreeClient
10
- from contree_cli.config import ConfigProfile
9
+ from contree_client.profiles import Profile
10
+
11
+ from contree_cli.client import CliClient
11
12
  from contree_cli.output import OutputFormatter
12
13
  from contree_cli.session import SessionStore
13
14
 
14
- PROFILE: ContextVar[ConfigProfile] = ContextVar("PROFILE")
15
- CLIENT: ContextVar[ContreeClient] = ContextVar("CLIENT")
15
+ PROFILE: ContextVar[Profile] = ContextVar("PROFILE")
16
+ CLIENT: ContextVar[CliClient] = ContextVar("CLIENT")
16
17
  FORMATTER: ContextVar[OutputFormatter] = ContextVar("FORMATTER")
17
18
  SESSION_STORE: ContextVar[SessionStore] = ContextVar("SESSION_STORE")
18
19
  IN_SHELL: ContextVar[bool] = ContextVar("IN_SHELL", default=False)
@@ -3,13 +3,15 @@ import http.client
3
3
  import logging
4
4
  import sys
5
5
  from collections.abc import Callable
6
- from contextlib import suppress
6
+ from contextlib import ExitStack, suppress
7
7
  from dataclasses import replace
8
8
 
9
+ from contree_client.exceptions import ContreeError
10
+
9
11
  import contree_cli.config as config_mod
10
12
  from contree_cli import CLIENT, FORMATTER, PROFILE, SESSION_STORE, ArgumentsProtocol
11
13
  from contree_cli.arguments import parser
12
- from contree_cli.client import ApiError, client_from_profile
14
+ from contree_cli.client import client_from_profile
13
15
  from contree_cli.config import SETTINGS, Config
14
16
  from contree_cli.log import setup_logging
15
17
  from contree_cli.output import FORMATTERS
@@ -18,6 +20,26 @@ from contree_cli.update_check import UpdateChecker
18
20
 
19
21
  log = logging.getLogger(__name__)
20
22
 
23
+ # Network errors raised by the auto-detected transport backend. The
24
+ # stdlib ones are always available; urllib3/httpx raise their own
25
+ # hierarchies (neither inherits from OSError) when auto-detected as
26
+ # the backend instead of `requests` (whose exceptions do subclass
27
+ # OSError). Both imports are optional, mirroring how
28
+ # contree_client.sync.detect_backend() itself probes for them.
29
+ _NETWORK_ERRORS: tuple[type[BaseException], ...] = (OSError, http.client.HTTPException)
30
+ try:
31
+ import urllib3.exceptions # type: ignore[import-not-found]
32
+
33
+ _NETWORK_ERRORS = (*_NETWORK_ERRORS, urllib3.exceptions.HTTPError)
34
+ except ModuleNotFoundError:
35
+ pass
36
+ try:
37
+ import httpx # type: ignore[import-not-found]
38
+
39
+ _NETWORK_ERRORS = (*_NETWORK_ERRORS, httpx.HTTPError)
40
+ except ModuleNotFoundError:
41
+ pass
42
+
21
43
 
22
44
  def main() -> None:
23
45
  if len(sys.argv) == 1:
@@ -71,24 +93,30 @@ def main() -> None:
71
93
  )
72
94
  exit(1)
73
95
 
74
- if needs_client:
75
- try:
76
- client = client_from_profile(profile)
77
- except ValueError as exc:
78
- log.error("%s", exc)
79
- exit(1)
80
- CLIENT.set(client)
81
-
82
- formatter = FORMATTERS[args.output_format]()
83
-
84
- session_key = get_session_key(profile.name, override=args.session_key)
85
- db_path = profile.session_db_path
86
- log.debug("Running in session: %s", session_key)
96
+ # One stack owns every resource the command needs; entries are
97
+ # added as they come to life and unwound together on the way out.
98
+ with ExitStack() as stack:
99
+ if needs_client:
100
+ # Session-based transports (requests/httpx/urllib3) hold
101
+ # pooled connections; enter the client so open()/close()
102
+ # run around the whole command.
103
+ try:
104
+ client = client_from_profile(profile)
105
+ except ValueError as exc:
106
+ log.error("%s", exc)
107
+ exit(1)
108
+ CLIENT.set(stack.enter_context(client))
109
+
110
+ formatter = FORMATTERS[args.output_format]()
111
+ stack.callback(formatter.close)
112
+
113
+ session_key = get_session_key(profile.name, override=args.session_key)
114
+ db_path = config_mod.session_db_path(profile.name)
115
+ log.debug("Running in session: %s", session_key)
87
116
 
88
- with SessionStore(db_path, session_key) as store:
89
117
  PROFILE.set(profile)
90
118
  FORMATTER.set(formatter)
91
- SESSION_STORE.set(store)
119
+ SESSION_STORE.set(stack.enter_context(SessionStore(db_path, session_key)))
92
120
  ctx = contextvars.copy_context()
93
121
 
94
122
  loader: type[ArgumentsProtocol] = args.load_args
@@ -96,7 +124,7 @@ def main() -> None:
96
124
 
97
125
  try:
98
126
  exit_code = ctx.run(handler, loader.from_args(args))
99
- except ApiError as exc:
127
+ except ContreeError as exc:
100
128
  log.error("%s", exc)
101
129
  exit(1)
102
130
  except ValueError as exc:
@@ -104,14 +132,12 @@ def main() -> None:
104
132
  # (invalid UUIDs, etc.); the message is already user-facing.
105
133
  log.error("%s", exc)
106
134
  exit(1)
107
- except (OSError, http.client.HTTPException) as exc:
135
+ except _NETWORK_ERRORS as exc:
108
136
  log.error("Network error: %s", exc)
109
137
  exit(1)
110
138
  except KeyboardInterrupt:
111
139
  log.error("User interrupted")
112
140
  exit(1)
113
- finally:
114
- formatter.close()
115
141
 
116
142
  exit(exit_code or 0)
117
143
 
@@ -18,6 +18,7 @@ Agent protocol — follow this sequence for every task:
18
18
  4. Inspect first (read-only):
19
19
  contree ls /path
20
20
  contree cat /path/file
21
+ contree export /path -F subtree.tar.gz
21
22
  contree images --prefix=...
22
23
  contree session show
23
24
 
@@ -104,7 +105,7 @@ Listing:
104
105
  contree images --prefix=python filter by tag prefix
105
106
  contree images -a include untagged
106
107
  contree images --since 1d last 24 hours
107
- contree -f json images JSON output for scripting
108
+ contree -o json images JSON output for scripting
108
109
 
109
110
  Tagging:
110
111
  contree tag my-app:v1.0 tag current session image
@@ -145,7 +146,7 @@ Building from a Dockerfile:
145
146
 
146
147
  .dockerignore is applied to every COPY/ADD walk on top of the default exclude list (.git, __pycache__, node_modules, etc.).
147
148
 
148
- build runs in its own session keyed by abspath(CONTEXT) (visible as "session": "build:<hash>" in -f json output). `-S <agent_key>` on `build` is harmless but does not bind the build to your agent session. Verify the resulting image from a normal session:
149
+ build runs in its own session keyed by abspath(CONTEXT) (visible as "session": "build:<hash>" in -o json output). `-S <agent_key>` on `build` is harmless but does not bind the build to your agent session. Verify the resulting image from a normal session:
149
150
  contree build . --tag myapp:dev
150
151
  contree -S agent_verify use tag:myapp:dev
151
152
  contree -S agent_verify run -D -- myapp --version
@@ -188,7 +189,7 @@ Listing uploaded files:
188
189
  contree file ls list all uploaded files in the project
189
190
  contree file ls --since 1d narrow by upload time
190
191
  contree file ls -q uuid + sha256 + source only (quiet)
191
- contree -f json file ls JSON output for jq
192
+ contree -o json file ls JSON output for jq
192
193
 
193
194
  Output joins remote files (uuid, sha256, size, created_at) with the local upload cache. The SOURCE column shows whatever this machine used to produce the file:
194
195
  - absolute host path for files uploaded via `run --file` / `COPY`;
@@ -243,7 +244,7 @@ Detached mode (-d):
243
244
  contree session wait block until done + advance branch
244
245
  contree session wait UUID1 UUID2 poll only (NO branch advance)
245
246
 
246
- NOTE: status filtering uses --status, NOT -S. `-S` is the global session flag and only works BEFORE the subcommand. Also, the default `run -d` output is plain/table -- use `contree -f json run -d ...` to capture the UUID via `jq -r .uuid` reliably.
247
+ NOTE: status filtering uses --status, NOT -S. `-S` is the global session flag and only works BEFORE the subcommand. Also, the default `run -d` output is plain/table -- use `contree -o json run -d ...` to capture the UUID via `jq -r .uuid` reliably.
247
248
 
248
249
  Operation references (UUID_OR_REF):
249
250
  Every positional that `--help` labels `UUID_OR_REF` -- `op show`, `op cancel`, `op wait`, top-level `show`/`kill`, and `session wait` -- accepts either a real operation UUID OR a session-history reference. References are resolved against the active session (the one selected by `-S <key>`) before the API is called, so the same notation works everywhere.
@@ -281,24 +282,24 @@ Monitoring background operations:
281
282
 
282
283
  Default `op ls`/`ps` lists only `EXECUTING`; `PENDING` and `ASSIGNED` are hidden until `-a` or an explicit `--status`. For a full active snapshot, fetch with `-a` and filter client-side.
283
284
 
284
- `op wait` is a pure observer: polls and prints one operation record per completion. Default formatter pins uuid, status, exit_code, timed_out, duration first and error last; every other scalar API field appears between them, so column count is not fixed. For scripts use `-f json` (one object per line) or `-f tsv` and select fields explicitly. `status` is the server's word verbatim (orchestration outcome — did the API run the job?); the sandbox process's exit code lives in the separate `exit_code` column. The CLI exit code is 1 when any op finishes non-SUCCESS, or the actual `exit_code` when a SUCCESS op had a non-zero sandbox exit (so `op wait && next-step` still composes naturally with `run -- false`). --timeout (default 60s) caps the wait. Use --all to wait for every currently active op in the project.
285
+ `op wait` is a pure observer: polls and prints one operation record per completion. Default formatter pins uuid, status, exit_code, timed_out, duration first and error last; every other scalar API field appears between them, so column count is not fixed. For scripts use `-o json` (one object per line) or `-o tsv` and select fields explicitly. `status` is the server's word verbatim (orchestration outcome — did the API run the job?); the sandbox process's exit code lives in the separate `exit_code` column. The CLI exit code is 1 when any op finishes non-SUCCESS, or the actual `exit_code` when a SUCCESS op had a non-zero sandbox exit (so `op wait && next-step` still composes naturally with `run -- false`). --timeout (default 60s) caps the wait. Use --all to wait for every currently active op in the project.
285
286
 
286
287
  Rule of thumb -- use `op wait` ONLY outside session context: `op wait` is the right tool when the UUIDs came from somewhere else (different session, different agent, `images import`, raw API call) and you only need "is it done yet?". For ops you spawned in *this* session, use `session wait` (no-arg form) instead -- it polls AND advances the active branch to each result image, which `op wait` will not do.
287
288
 
288
289
  Caveat 1 -- `op wait` does NOT advance session state: each `run -d` (non-disposable) creates a `detached-<op-uuid>` branch pointing at the START image. `op wait` does not move those branches to the result image; the result lives only on the server. After fan-out + wait the session looks the same as before the wait, just with `detached-*` branches accumulated.
289
290
 
290
291
  PREFERRED fan-out (--disposable, no image-tracking concerns):
291
- A=$(contree -S <key> -f json run -d --disposable -- pytest tests/a | jq -r .uuid)
292
- B=$(contree -S <key> -f json run -d --disposable -- pytest tests/b | jq -r .uuid)
293
- C=$(contree -S <key> -f json run -d --disposable -- pytest tests/c | jq -r .uuid)
292
+ A=$(contree -S <key> -o json run -d --disposable -- pytest tests/a | jq -r .uuid)
293
+ B=$(contree -S <key> -o json run -d --disposable -- pytest tests/b | jq -r .uuid)
294
+ C=$(contree -S <key> -o json run -d --disposable -- pytest tests/c | jq -r .uuid)
294
295
  contree -S <key> op wait "$A" "$B" "$C" block until all complete
295
296
  contree -S <key> op show "$A" "$B" "$C" stdout/stderr per op
296
297
 
297
298
  Non-disposable fan-out (must recover images manually):
298
- A=$(contree -S <key> -f json run -d -- apt-get install -y curl | jq -r .uuid)
299
- B=$(contree -S <key> -f json run -d -- apt-get install -y wget | jq -r .uuid)
299
+ A=$(contree -S <key> -o json run -d -- apt-get install -y curl | jq -r .uuid)
300
+ B=$(contree -S <key> -o json run -d -- apt-get install -y wget | jq -r .uuid)
300
301
  contree -S <key> op wait "$A" "$B"
301
- IMG_A=$(contree -f json op show "$A" | jq -r .image)
302
+ IMG_A=$(contree -o json op show "$A" | jq -r .image)
302
303
  contree use "$IMG_A" bind chosen result back
303
304
 
304
305
  Caveat 2 -- `op wait --all` is project-wide: if another agent (or another shell of yours) is running concurrently in the same project, your --all will block on its ops too. The result is still a valid wait, just possibly not over the set you expected. For session-spawned fan-out the correct alternative is `contree -S <key> session wait` (no args): it drains only this session's pending detached ops and advances the active branch with each result image. Reach for `op wait --all` only when you really want a project-wide observer (admin/cleanup tooling).
@@ -362,10 +363,10 @@ Rules for reliable agent workflows:
362
363
  3. Why split? Chained runs collapse into one checkpoint. If `make test` fails, you can't rollback to just after `apt install`. Split runs give you granular rollback.
363
364
 
364
365
  4. Global flags (-f, -S, -p) MUST come before the subcommand:
365
- Right: contree -S key -f json images
366
- Wrong: contree images -S key -f json
366
+ Right: contree -S key -o json images
367
+ Wrong: contree images -S key -o json
367
368
 
368
- 5. Use -f json for structured output in automation: `contree -f json images | jq '.uuid'`.
369
+ 5. Use -o json for structured output in automation: `contree -o json images | jq '.uuid'`.
369
370
 
370
371
  6. Agents must never run `contree auth`. Only users manage auth.
371
372
 
@@ -378,23 +379,23 @@ Rules for reliable agent workflows:
378
379
  Output formats
379
380
  ==============
380
381
 
381
- Global -f flag goes before the subcommand. Always available formats:
382
+ Global -o flag goes before the subcommand. Always available formats:
382
383
 
383
- contree -f json images one JSON object per line (JSONL)
384
- contree -f json-pretty ps pretty-printed JSON array
385
- contree -f csv images CSV with header row
386
- contree -f tsv ps tab-separated values
387
- contree -f plain images key: value blocks
384
+ contree -o json images one JSON object per line (JSONL)
385
+ contree -o json-pretty ps pretty-printed JSON array
386
+ contree -o csv images CSV with header row
387
+ contree -o tsv ps tab-separated values
388
+ contree -o plain images key: value blocks
388
389
 
389
- `-f toml` is available only on Python 3.11+ (it relies on stdlib `tomllib`). On Python 3.10 it is silently absent from --help.
390
+ `-o toml` is available only on Python 3.11+ (it relies on stdlib `tomllib`). On Python 3.10 it is silently absent from --help.
390
391
 
391
392
  Scripting examples:
392
- contree -f json images --prefix=python | jq -r '.uuid'
393
- contree -f json ps -a | jq 'select(.status=="SUCCESS")'
394
- contree -f csv images > images.csv
393
+ contree -o json images --prefix=python | jq -r '.uuid'
394
+ contree -o json ps -a | jq 'select(.status=="SUCCESS")'
395
+ contree -o csv images > images.csv
395
396
  contree ps -q | xargs contree show
396
397
 
397
- Note: `run` with default formatter prints raw stdout/stderr. Use -f json to get structured operation metadata instead.
398
+ Note: `run` with default formatter prints raw stdout/stderr. Use -o json to get structured operation metadata instead.
398
399
 
399
400
  Profiles
400
401
  ========
@@ -450,6 +451,7 @@ All commands
450
451
  ls [PATH] List files in image (no VM)
451
452
  cat PATH Show file content (no VM)
452
453
  cp PATH DEST Download file from image
454
+ export [PATH] Export rootfs or a subtree as tar.gz (-F FILE or pipe; --decompress for plain tar)
453
455
  cd [PATH] Change session working directory
454
456
  env [KEY=VALUE ...] Session env vars (-U to unset)
455
457
  file edit PATH Edit remote file via $EDITOR
@@ -11,6 +11,7 @@ from contree_cli.cli import (
11
11
  cd,
12
12
  cp,
13
13
  env,
14
+ export,
14
15
  file,
15
16
  images,
16
17
  ls,
@@ -235,6 +236,7 @@ register(
235
236
  register("ls", "List files in image", ls.setup_parser)
236
237
  register("cat", "Show file content from image", cat.setup_parser)
237
238
  register("cp", "Copy file from image to local path", cp.setup_parser)
239
+ register("export", "Export image rootfs or a subtree as tar.gz", export.setup_parser)
238
240
  register("file", "Manage files in session image", file.setup_parser, aliases=["f"])
239
241
  register(
240
242
  "session",