contree-cli 0.5.0__tar.gz → 0.6.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.
- {contree_cli-0.5.0 → contree_cli-0.6.0}/PKG-INFO +16 -9
- {contree_cli-0.5.0 → contree_cli-0.6.0}/README.md +15 -8
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/__main__.py +17 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/agent.md +87 -8
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/arguments.py +28 -17
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/auth.py +68 -24
- contree_cli-0.6.0/contree_cli/cli/build.py +224 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/file.py +149 -3
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/images.py +57 -14
- contree_cli-0.6.0/contree_cli/cli/operation.py +157 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/ps.py +87 -69
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/run.py +29 -12
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/client.py +41 -6
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/config.py +13 -18
- contree_cli-0.6.0/contree_cli/docker/__init__.py +42 -0
- contree_cli-0.6.0/contree_cli/docker/context.py +161 -0
- contree_cli-0.6.0/contree_cli/docker/dockerignore.py +102 -0
- contree_cli-0.6.0/contree_cli/docker/keyword.py +94 -0
- contree_cli-0.6.0/contree_cli/docker/kw_add.py +132 -0
- contree_cli-0.6.0/contree_cli/docker/kw_arg.py +41 -0
- contree_cli-0.6.0/contree_cli/docker/kw_copy.py +178 -0
- contree_cli-0.6.0/contree_cli/docker/kw_env.py +33 -0
- contree_cli-0.6.0/contree_cli/docker/kw_from.py +119 -0
- contree_cli-0.6.0/contree_cli/docker/kw_run.py +190 -0
- contree_cli-0.6.0/contree_cli/docker/kw_skipped.py +30 -0
- contree_cli-0.6.0/contree_cli/docker/kw_user.py +31 -0
- contree_cli-0.6.0/contree_cli/docker/kw_workdir.py +37 -0
- contree_cli-0.6.0/contree_cli/docker/local_context.py +159 -0
- contree_cli-0.6.0/contree_cli/docker/parser.py +93 -0
- contree_cli-0.6.0/contree_cli/docker/url_fetch.py +303 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/manual.md +13 -8
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/mapped_file.py +11 -1
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/session.py +63 -3
- contree_cli-0.6.0/contree_cli/shell/argmap.py +69 -0
- contree_cli-0.6.0/contree_cli/shell/argspec.py +343 -0
- contree_cli-0.6.0/contree_cli/shell/cache.py +109 -0
- contree_cli-0.6.0/contree_cli/shell/completer.py +371 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/repl.py +256 -54
- contree_cli-0.6.0/contree_cli/shell/sources.py +495 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/skill_body.md +92 -4
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/types.py +17 -0
- contree_cli-0.6.0/contree_cli/update_check.py +165 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/pyproject.toml +1 -1
- contree_cli-0.5.0/contree_cli/shell/completer.py +0 -485
- {contree_cli-0.5.0 → contree_cli-0.6.0}/LICENSE +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/__init__.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/__init__.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/agent.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cat.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cd.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cp.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/env.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/kill.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/ls.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/session.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/show.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/skill.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/tag.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/use.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/log.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/man.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/migrations.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/output.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/py.typed +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/__init__.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/history.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/parser.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/trie.py +0 -0
- {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/skill.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: contree-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -279,9 +279,9 @@ contree --help
|
|
|
279
279
|
contree auth
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
-
You'll be prompted to enter your API token and project ID. The CLI verifies the token and saves credentials to `~/.config/contree
|
|
282
|
+
You'll be prompted to enter your API token and project ID. The CLI verifies the token and saves credentials to `~/.config/contree/auth.ini` (override the data directory via `CONTREE_HOME`).
|
|
283
283
|
|
|
284
|
-
If `NEBIUS_API_KEY` and `NEBIUS_AI_PROJECT`
|
|
284
|
+
If `--token`/`--url`/`--project` flags are omitted, `contree auth` reads `CONTREE_TOKEN` (or `NEBIUS_API_KEY`), `CONTREE_URL`, and `CONTREE_PROJECT` (or `NEBIUS_AI_PROJECT`) from the environment instead of prompting. These variables are read only during registration; runtime commands use the saved profile only.
|
|
285
285
|
|
|
286
286
|
### 2. Install agent skills (optional)
|
|
287
287
|
|
|
@@ -492,17 +492,24 @@ contree auth switch staging # switch active profile
|
|
|
492
492
|
|
|
493
493
|
### Environment variables
|
|
494
494
|
|
|
495
|
+
Read at runtime (any command):
|
|
496
|
+
|
|
495
497
|
| Variable | Purpose |
|
|
496
498
|
|---|---|
|
|
497
|
-
| `CONTREE_HOME` | Data directory (default `~/.config/contree
|
|
498
|
-
| `
|
|
499
|
-
| `CONTREE_URL` | API base URL (overrides config) |
|
|
500
|
-
| `CONTREE_PROJECT` | Project ID (overrides config) |
|
|
501
|
-
| `CONTREE_PROFILE` | Active profile name |
|
|
499
|
+
| `CONTREE_HOME` | Data directory (default `$XDG_CONFIG_HOME/contree`, or `~/.config/contree`) |
|
|
500
|
+
| `CONTREE_PROFILE` | Active profile name (selects which profile commands use) |
|
|
502
501
|
| `CONTREE_SESSION` | Explicit session key (for multi-terminal workflows) |
|
|
503
502
|
| `CONTREE_SESSION_DB` | Path to session SQLite database |
|
|
504
503
|
|
|
505
|
-
|
|
504
|
+
Read only by `contree auth` (registration-time fallbacks for omitted flags):
|
|
505
|
+
|
|
506
|
+
| Variable | Used for |
|
|
507
|
+
|---|---|
|
|
508
|
+
| `CONTREE_TOKEN` / `NEBIUS_API_KEY` | `--token` |
|
|
509
|
+
| `CONTREE_URL` | `--url` |
|
|
510
|
+
| `CONTREE_PROJECT` / `NEBIUS_AI_PROJECT` | `--project` |
|
|
511
|
+
|
|
512
|
+
Credentials come strictly from the saved profile at runtime. `--token`, `--url`, `--project` CLI flags override profile fields for a single invocation.
|
|
506
513
|
|
|
507
514
|
## Zero Dependencies
|
|
508
515
|
|
|
@@ -72,9 +72,9 @@ contree --help
|
|
|
72
72
|
contree auth
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
You'll be prompted to enter your API token and project ID. The CLI verifies the token and saves credentials to `~/.config/contree
|
|
75
|
+
You'll be prompted to enter your API token and project ID. The CLI verifies the token and saves credentials to `~/.config/contree/auth.ini` (override the data directory via `CONTREE_HOME`).
|
|
76
76
|
|
|
77
|
-
If `NEBIUS_API_KEY` and `NEBIUS_AI_PROJECT`
|
|
77
|
+
If `--token`/`--url`/`--project` flags are omitted, `contree auth` reads `CONTREE_TOKEN` (or `NEBIUS_API_KEY`), `CONTREE_URL`, and `CONTREE_PROJECT` (or `NEBIUS_AI_PROJECT`) from the environment instead of prompting. These variables are read only during registration; runtime commands use the saved profile only.
|
|
78
78
|
|
|
79
79
|
### 2. Install agent skills (optional)
|
|
80
80
|
|
|
@@ -285,17 +285,24 @@ contree auth switch staging # switch active profile
|
|
|
285
285
|
|
|
286
286
|
### Environment variables
|
|
287
287
|
|
|
288
|
+
Read at runtime (any command):
|
|
289
|
+
|
|
288
290
|
| Variable | Purpose |
|
|
289
291
|
|---|---|
|
|
290
|
-
| `CONTREE_HOME` | Data directory (default `~/.config/contree
|
|
291
|
-
| `
|
|
292
|
-
| `CONTREE_URL` | API base URL (overrides config) |
|
|
293
|
-
| `CONTREE_PROJECT` | Project ID (overrides config) |
|
|
294
|
-
| `CONTREE_PROFILE` | Active profile name |
|
|
292
|
+
| `CONTREE_HOME` | Data directory (default `$XDG_CONFIG_HOME/contree`, or `~/.config/contree`) |
|
|
293
|
+
| `CONTREE_PROFILE` | Active profile name (selects which profile commands use) |
|
|
295
294
|
| `CONTREE_SESSION` | Explicit session key (for multi-terminal workflows) |
|
|
296
295
|
| `CONTREE_SESSION_DB` | Path to session SQLite database |
|
|
297
296
|
|
|
298
|
-
|
|
297
|
+
Read only by `contree auth` (registration-time fallbacks for omitted flags):
|
|
298
|
+
|
|
299
|
+
| Variable | Used for |
|
|
300
|
+
|---|---|
|
|
301
|
+
| `CONTREE_TOKEN` / `NEBIUS_API_KEY` | `--token` |
|
|
302
|
+
| `CONTREE_URL` | `--url` |
|
|
303
|
+
| `CONTREE_PROJECT` / `NEBIUS_AI_PROJECT` | `--project` |
|
|
304
|
+
|
|
305
|
+
Credentials come strictly from the saved profile at runtime. `--token`, `--url`, `--project` CLI flags override profile fields for a single invocation.
|
|
299
306
|
|
|
300
307
|
## Zero Dependencies
|
|
301
308
|
|
|
@@ -2,6 +2,7 @@ import contextvars
|
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
4
|
from collections.abc import Callable
|
|
5
|
+
from contextlib import suppress
|
|
5
6
|
from dataclasses import replace
|
|
6
7
|
|
|
7
8
|
import contree_cli.config as config_mod
|
|
@@ -12,6 +13,7 @@ from contree_cli.config import SETTINGS, Config
|
|
|
12
13
|
from contree_cli.log import setup_logging
|
|
13
14
|
from contree_cli.output import FORMATTERS
|
|
14
15
|
from contree_cli.session import SessionStore, get_session_key
|
|
16
|
+
from contree_cli.update_check import UpdateChecker
|
|
15
17
|
|
|
16
18
|
log = logging.getLogger(__name__)
|
|
17
19
|
|
|
@@ -27,6 +29,21 @@ def main() -> None:
|
|
|
27
29
|
args = parser.parse_args()
|
|
28
30
|
setup_logging(level=getattr(logging, args.log_level.upper(), logging.INFO))
|
|
29
31
|
|
|
32
|
+
# Update check runs only after argparse so it skips --help / --version
|
|
33
|
+
# / no-command paths and so the warning respects --log-level. refresh()
|
|
34
|
+
# is best-effort; check() is a pure predicate.
|
|
35
|
+
checker = UpdateChecker()
|
|
36
|
+
with suppress(Exception):
|
|
37
|
+
checker.refresh()
|
|
38
|
+
if not checker.is_latest():
|
|
39
|
+
log.warning(
|
|
40
|
+
"A new version of contree-cli is available: %s (installed: %s)."
|
|
41
|
+
" Upgrade with `uv tool install -U contree-cli` or"
|
|
42
|
+
" `pip install -U contree-cli`.",
|
|
43
|
+
checker.state.latest_version,
|
|
44
|
+
checker.current_version,
|
|
45
|
+
)
|
|
46
|
+
|
|
30
47
|
config_mod.CONFIG_FILE = args.config_path
|
|
31
48
|
config_mod.CONFIG_DIR = args.config_path.parent
|
|
32
49
|
|
|
@@ -134,7 +134,28 @@ Tag conventions:
|
|
|
134
134
|
Always search before building:
|
|
135
135
|
contree images --prefix=python-dev
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
Building from a Dockerfile:
|
|
138
|
+
When a project already ships a Dockerfile, prefer `contree build`
|
|
139
|
+
over hand-running each step. It executes FROM/RUN/COPY/WORKDIR/ENV
|
|
140
|
+
/ARG/USER against the API and caches every layer as a branch so
|
|
141
|
+
rebuilds are fast.
|
|
142
|
+
|
|
143
|
+
Layer cache is keyed by abspath(context), shared across invocations:
|
|
144
|
+
contree build . build ./Dockerfile, no tag
|
|
145
|
+
contree build . --tag myapp:dev build + tag the final image
|
|
146
|
+
contree build ./app --dockerfile ./app/Dockerfile.prod --tag svc:prod
|
|
147
|
+
contree build . --build-arg VERSION=1.2
|
|
148
|
+
contree build . --no-cache force rebuild
|
|
149
|
+
|
|
150
|
+
Supported directives: FROM, RUN, COPY, ADD (local paths only),
|
|
151
|
+
WORKDIR, ENV, ARG, USER. CMD/ENTRYPOINT/LABEL/EXPOSE/VOLUME/etc.
|
|
152
|
+
are parsed but skipped with a warning. Multi-stage (AS / --from)
|
|
153
|
+
is not yet supported.
|
|
154
|
+
|
|
155
|
+
.dockerignore is applied to every COPY/ADD walk on top of the
|
|
156
|
+
default exclude list (.git, __pycache__, node_modules, etc.).
|
|
157
|
+
|
|
158
|
+
More: contree build --help, contree images --help, contree tag --help
|
|
138
159
|
|
|
139
160
|
Files and directories
|
|
140
161
|
=====================
|
|
@@ -177,6 +198,32 @@ Pending files are injected into the next non-disposable run.
|
|
|
177
198
|
Explicit --file takes priority over pending files at same path.
|
|
178
199
|
Pending files are branch-aware.
|
|
179
200
|
|
|
201
|
+
Listing uploaded files:
|
|
202
|
+
contree file ls list all uploaded files in the project
|
|
203
|
+
contree file ls --since 1d narrow by upload time
|
|
204
|
+
contree file ls -q uuid + sha256 + source only (quiet)
|
|
205
|
+
contree -f json file ls JSON output for jq
|
|
206
|
+
|
|
207
|
+
Output joins remote files (uuid, sha256, size, created_at) with the
|
|
208
|
+
local upload cache. The SOURCE column shows whatever this machine
|
|
209
|
+
used to produce the file:
|
|
210
|
+
- absolute host path for files uploaded via `run --file` / `COPY`;
|
|
211
|
+
- https://... URL for files fetched via `ADD URL`.
|
|
212
|
+
|
|
213
|
+
IMPORTANT: SOURCE is resolved ONLY for files uploaded from this
|
|
214
|
+
specific machine. The mapping lives in the local SQLite cache (per
|
|
215
|
+
profile, under $CONTREE_HOME/cli/sessions/<profile>.db) keyed by
|
|
216
|
+
path+inode+mtime+size (for host paths) or by the URL itself (for
|
|
217
|
+
URL fetches), and is NOT shared between hosts. Rows show empty
|
|
218
|
+
SOURCE when:
|
|
219
|
+
- the file was uploaded from a different machine or by a teammate;
|
|
220
|
+
- the host file has been moved, renamed, or its inode/mtime/size
|
|
221
|
+
changed since upload (the cache key no longer matches);
|
|
222
|
+
- the upload happened before tracking landed (older entries
|
|
223
|
+
backfill on the next match).
|
|
224
|
+
An agent must not assume SOURCE is authoritative across hosts;
|
|
225
|
+
for cross-machine identity always use the remote UUID or sha256.
|
|
226
|
+
|
|
180
227
|
More: contree run --help, contree file --help
|
|
181
228
|
|
|
182
229
|
Execution modes
|
|
@@ -211,10 +258,31 @@ Piped stdin:
|
|
|
211
258
|
Detached mode (-d):
|
|
212
259
|
contree run -d -- long-running-task
|
|
213
260
|
contree ps check status
|
|
261
|
+
contree ps -a -S FAILED --since=1h recent failures
|
|
214
262
|
contree show UUID view result
|
|
215
263
|
contree session wait block until done
|
|
216
264
|
contree session wait UUID1 UUID2 wait for specific ops
|
|
217
265
|
|
|
266
|
+
Monitoring background operations:
|
|
267
|
+
Use the `operation` namespace (alias `op`) when juggling several
|
|
268
|
+
detached runs. `op ls` is `ps`; `op show` and `op cancel` accept
|
|
269
|
+
multiple UUIDs in one call.
|
|
270
|
+
|
|
271
|
+
contree op ls list operations (= ps)
|
|
272
|
+
contree op ls -a -S EXECUTING filter active runs
|
|
273
|
+
contree op show UUID1 UUID2 UUID3 inspect a batch in one call
|
|
274
|
+
contree op cancel UUID1 UUID2 cancel selected operations
|
|
275
|
+
contree op cancel --all cancel every active op (rare)
|
|
276
|
+
|
|
277
|
+
Fan-out + join pattern:
|
|
278
|
+
A=$(contree run -d -- make -C /work/a build | jq -r .uuid)
|
|
279
|
+
B=$(contree run -d -- make -C /work/b build | jq -r .uuid)
|
|
280
|
+
contree session wait "$A" "$B"
|
|
281
|
+
contree op show "$A" "$B"
|
|
282
|
+
|
|
283
|
+
Background checks are cheap: terminal results are cached locally,
|
|
284
|
+
so repeated `op show` / `show` calls do not re-hit the API.
|
|
285
|
+
|
|
218
286
|
Disposable mode (-D) — no image checkpoint:
|
|
219
287
|
contree run -D -- rm -rf /tmp/*
|
|
220
288
|
contree run -D -- cat /etc/passwd
|
|
@@ -328,18 +396,23 @@ Per-command -p is useful for cross-project operations:
|
|
|
328
396
|
contree -p project-a images --prefix=base
|
|
329
397
|
contree -p project-b images import tag:base:latest
|
|
330
398
|
|
|
331
|
-
Data directory: ~/.config/contree
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
399
|
+
Data directory: $XDG_CONFIG_HOME/contree/ (or ~/.config/contree/)
|
|
400
|
+
auth.ini profile credentials (mode 0600)
|
|
401
|
+
cli.ini optional CLI defaults
|
|
402
|
+
cli/sessions/{profile}.db per-profile sessions, history, cache
|
|
403
|
+
cli/skills.db installed agent skill registry
|
|
404
|
+
cli/version_check.json cached PyPI update-check state
|
|
335
405
|
|
|
336
406
|
Environment variables:
|
|
337
407
|
CONTREE_HOME data directory override
|
|
338
|
-
|
|
339
|
-
CONTREE_URL API URL (overrides config)
|
|
340
|
-
CONTREE_PROFILE active profile (overrides config)
|
|
408
|
+
CONTREE_PROFILE active profile (selects which profile commands use)
|
|
341
409
|
CONTREE_SESSION explicit session key
|
|
342
410
|
|
|
411
|
+
Read only by `contree auth` (registration-time fallbacks):
|
|
412
|
+
CONTREE_TOKEN / NEBIUS_API_KEY token when --token is omitted
|
|
413
|
+
CONTREE_URL URL when --url is omitted
|
|
414
|
+
CONTREE_PROJECT / NEBIUS_AI_PROJECT project ID when --project is omitted
|
|
415
|
+
|
|
343
416
|
More: contree auth --help
|
|
344
417
|
|
|
345
418
|
All commands
|
|
@@ -347,11 +420,16 @@ All commands
|
|
|
347
420
|
|
|
348
421
|
use [IMAGE] Set or show session image (aliases: ci)
|
|
349
422
|
run [-- CMD] Spawn sandbox instance (aliases: r)
|
|
423
|
+
build [CONTEXT] Build image from Dockerfile (aliases: bd)
|
|
350
424
|
images List/import images (aliases: i, img)
|
|
351
425
|
tag [IMAGE] TAG Tag image (aliases: t)
|
|
352
426
|
ps List operations
|
|
353
427
|
kill UUID Cancel operation
|
|
354
428
|
show UUID Show operation result
|
|
429
|
+
operation list List operations (aliases: op ls)
|
|
430
|
+
operation show UUID... Show one or more operation results (aliases: op)
|
|
431
|
+
operation cancel UUID...
|
|
432
|
+
Cancel one or more operations (or --all)
|
|
355
433
|
ls [PATH] List files in image (no VM)
|
|
356
434
|
cat PATH Show file content (no VM)
|
|
357
435
|
cp PATH DEST Download file from image
|
|
@@ -359,6 +437,7 @@ All commands
|
|
|
359
437
|
env [KEY=VALUE ...] Session env vars (-d to unset)
|
|
360
438
|
file edit PATH Edit remote file via $EDITOR
|
|
361
439
|
file cp SRC DEST Stage local file for next run
|
|
440
|
+
file ls [-q] List uploaded files + local path (aliases: list)
|
|
362
441
|
session list List sessions (aliases: ls)
|
|
363
442
|
session branch [NAME] Create/list branches (aliases: br)
|
|
364
443
|
session checkout BRANCH Switch branch (aliases: co)
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from contree_cli.cli import (
|
|
7
7
|
agent,
|
|
8
8
|
auth,
|
|
9
|
+
build,
|
|
9
10
|
cat,
|
|
10
11
|
cd,
|
|
11
12
|
cp,
|
|
@@ -14,6 +15,7 @@ from contree_cli.cli import (
|
|
|
14
15
|
images,
|
|
15
16
|
kill,
|
|
16
17
|
ls,
|
|
18
|
+
operation,
|
|
17
19
|
ps,
|
|
18
20
|
run,
|
|
19
21
|
session,
|
|
@@ -44,6 +46,9 @@ examples:
|
|
|
44
46
|
contree run --file ./src:/app/src -- make -C /app/src
|
|
45
47
|
contree images --prefix=ubuntu
|
|
46
48
|
contree ps -q
|
|
49
|
+
contree op ls same as `contree ps`
|
|
50
|
+
contree op show UUID1 UUID2 multi-UUID show
|
|
51
|
+
contree op cancel UUID1 UUID2 multi-UUID cancel (or --all)
|
|
47
52
|
contree show OPERATION_UUID
|
|
48
53
|
contree tag IMAGE_UUID latest
|
|
49
54
|
contree ls /etc list files in session image
|
|
@@ -71,20 +76,19 @@ mutating commands (change remote or local session state):
|
|
|
71
76
|
contree use IMAGE | run -- CMD | file edit PATH | file cp SRC DEST
|
|
72
77
|
contree tag UUID TAG | kill UUID | cd PATH | session checkout BRANCH
|
|
73
78
|
|
|
74
|
-
environment variables
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
NEBIUS_AI_PROJECT Fallback project ID for IAM auth registration
|
|
79
|
+
environment variables:
|
|
80
|
+
CONTREE_PROFILE Active config profile (selects which profile to use)
|
|
81
|
+
CONTREE_SESSION Explicit session name (for multi-terminal workflows).
|
|
82
|
+
If unset, contree auto-generates <cwd>+<8hex> (derived
|
|
83
|
+
from profile+ppid+tty); export your own for stable
|
|
84
|
+
reuse. You can also pass -S/--session instead.
|
|
85
|
+
CONTREE_SESSION_DB Path to session SQLite database
|
|
86
|
+
CONTREE_NO_UPDATE_CHECK Set to any value to disable PyPI update checks
|
|
87
|
+
|
|
88
|
+
registration-time fallbacks (only read by `contree auth`, not at runtime):
|
|
89
|
+
CONTREE_TOKEN / NEBIUS_API_KEY Token used when --token is omitted
|
|
90
|
+
CONTREE_URL URL used when --url is omitted
|
|
91
|
+
CONTREE_PROJECT / NEBIUS_AI_PROJECT Project ID used when --project is omitted
|
|
88
92
|
"""
|
|
89
93
|
|
|
90
94
|
DESCRIPTION = """\
|
|
@@ -121,7 +125,7 @@ parser.add_argument(
|
|
|
121
125
|
parser.add_argument(
|
|
122
126
|
*FLAGS["token"],
|
|
123
127
|
default=None,
|
|
124
|
-
help="API token (overrides
|
|
128
|
+
help="API token (overrides profile for this invocation)",
|
|
125
129
|
)
|
|
126
130
|
|
|
127
131
|
|
|
@@ -133,12 +137,12 @@ parser.add_argument(
|
|
|
133
137
|
*FLAGS["url"],
|
|
134
138
|
default=None,
|
|
135
139
|
type=_strip_trailing_slashes,
|
|
136
|
-
help="API base URL (overrides
|
|
140
|
+
help="API base URL (overrides profile for this invocation)",
|
|
137
141
|
)
|
|
138
142
|
parser.add_argument(
|
|
139
143
|
*FLAGS["project"],
|
|
140
144
|
default=None,
|
|
141
|
-
help="Project ID (overrides
|
|
145
|
+
help="Project ID (overrides profile for this invocation)",
|
|
142
146
|
)
|
|
143
147
|
parser.add_argument(
|
|
144
148
|
*FLAGS["config"],
|
|
@@ -207,11 +211,18 @@ def register(
|
|
|
207
211
|
|
|
208
212
|
register("use", "Set or show current session image", use.setup_parser, aliases=["ci"])
|
|
209
213
|
register("run", "Spawn a sandbox instance", run.setup_parser, aliases=["r"])
|
|
214
|
+
register("build", "Build image from Dockerfile", build.setup_parser, aliases=["bd"])
|
|
210
215
|
register("images", "List and import images", images.setup_parser, aliases=["i", "img"])
|
|
211
216
|
register("tag", "Tag an image", tag.setup_parser, aliases=["t"])
|
|
212
217
|
register("ps", "List operations/instances", ps.setup_parser)
|
|
213
218
|
register("kill", "Cancel an operation", kill.setup_parser)
|
|
214
219
|
register("show", "Show operation result", show.setup_parser)
|
|
220
|
+
register(
|
|
221
|
+
"operation",
|
|
222
|
+
"Manage operations (list/show/cancel)",
|
|
223
|
+
operation.setup_parser,
|
|
224
|
+
aliases=["op"],
|
|
225
|
+
)
|
|
215
226
|
register("ls", "List files in image", ls.setup_parser)
|
|
216
227
|
register("cat", "Show file content from image", cat.setup_parser)
|
|
217
228
|
register("cp", "Copy file from image to local path", cp.setup_parser)
|
|
@@ -8,9 +8,13 @@ Supports two auth types:
|
|
|
8
8
|
iam (default) — bearer token + project ID, default URL provided
|
|
9
9
|
jwt (legacy) — bearer token only, URL must be specified
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
NEBIUS_API_KEY used
|
|
13
|
-
|
|
11
|
+
Environment variable fallbacks during registration:
|
|
12
|
+
CONTREE_TOKEN / NEBIUS_API_KEY used when --token is omitted
|
|
13
|
+
CONTREE_URL used when --url is omitted
|
|
14
|
+
CONTREE_PROJECT / NEBIUS_AI_PROJECT used when --project is omitted (IAM)
|
|
15
|
+
|
|
16
|
+
Other commands ignore these variables; only ``contree auth`` reads
|
|
17
|
+
them. ``CONTREE_PROFILE`` selects the profile for any command.
|
|
14
18
|
|
|
15
19
|
Subcommands:
|
|
16
20
|
profiles List saved profiles (* marks active)
|
|
@@ -22,6 +26,7 @@ from __future__ import annotations
|
|
|
22
26
|
import argparse
|
|
23
27
|
import getpass
|
|
24
28
|
import hashlib
|
|
29
|
+
import json
|
|
25
30
|
import logging
|
|
26
31
|
import os
|
|
27
32
|
from dataclasses import dataclass
|
|
@@ -35,6 +40,7 @@ from contree_cli.types import FLAGS
|
|
|
35
40
|
logger = logging.getLogger(__name__)
|
|
36
41
|
PROFILE_CHECK_TIMEOUT = 2.0
|
|
37
42
|
PROFILE_CHECK_CONCURRENCY = 4
|
|
43
|
+
REQUIRED_PERMISSION = "list"
|
|
38
44
|
|
|
39
45
|
EPILOG = """\
|
|
40
46
|
for coding agents:
|
|
@@ -169,6 +175,24 @@ def setup_parser(p: argparse.ArgumentParser) -> SetupResult:
|
|
|
169
175
|
return cmd_auth, AuthArgs
|
|
170
176
|
|
|
171
177
|
|
|
178
|
+
def env_fallback(names: tuple[str, ...], *, what: str) -> str | None:
|
|
179
|
+
for name in names:
|
|
180
|
+
value = os.environ.get(name)
|
|
181
|
+
if value:
|
|
182
|
+
logger.info("Using %s from %s", what, name)
|
|
183
|
+
return value
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def check_permission(payload: object, permission: str) -> bool:
|
|
188
|
+
if not isinstance(payload, dict):
|
|
189
|
+
return False
|
|
190
|
+
perms = payload.get("permissions")
|
|
191
|
+
if not isinstance(perms, dict):
|
|
192
|
+
return False
|
|
193
|
+
return bool(perms.get(permission))
|
|
194
|
+
|
|
195
|
+
|
|
172
196
|
def cmd_auth(args: AuthArgs) -> int | None:
|
|
173
197
|
cfg = Config()
|
|
174
198
|
exists = args.profile in cfg
|
|
@@ -188,18 +212,16 @@ def cmd_auth(args: AuthArgs) -> int | None:
|
|
|
188
212
|
print("Aborted.")
|
|
189
213
|
return 1
|
|
190
214
|
|
|
191
|
-
# Token: --token > NEBIUS_API_KEY > interactive prompt
|
|
192
|
-
token = args.token
|
|
215
|
+
# Token: --token > CONTREE_TOKEN > NEBIUS_API_KEY > interactive prompt
|
|
216
|
+
token = args.token or env_fallback(
|
|
217
|
+
("CONTREE_TOKEN", "NEBIUS_API_KEY"),
|
|
218
|
+
what="token",
|
|
219
|
+
)
|
|
193
220
|
if token is None:
|
|
194
|
-
|
|
195
|
-
if nebius_key:
|
|
196
|
-
logger.info("Using token from NEBIUS_API_KEY")
|
|
197
|
-
token = nebius_key
|
|
198
|
-
else:
|
|
199
|
-
token = getpass.getpass("Token: ")
|
|
221
|
+
token = getpass.getpass("Token: ")
|
|
200
222
|
|
|
201
|
-
# URL: --url > type-specific default > interactive prompt
|
|
202
|
-
url = args.url
|
|
223
|
+
# URL: --url > CONTREE_URL > type-specific default > interactive prompt
|
|
224
|
+
url = args.url or env_fallback(("CONTREE_URL",), what="URL")
|
|
203
225
|
if url is None:
|
|
204
226
|
if args.auth_type == AuthType.IAM:
|
|
205
227
|
url = Config.DEFAULT_IAM_URL
|
|
@@ -209,17 +231,15 @@ def cmd_auth(args: AuthArgs) -> int | None:
|
|
|
209
231
|
logger.error("URL is required for JWT auth")
|
|
210
232
|
return 1
|
|
211
233
|
|
|
212
|
-
# Project (IAM only): --project > NEBIUS_AI_PROJECT >
|
|
234
|
+
# Project (IAM only): --project > CONTREE_PROJECT > NEBIUS_AI_PROJECT > prompt
|
|
213
235
|
project: str | None = None
|
|
214
236
|
if args.auth_type == AuthType.IAM:
|
|
215
|
-
project = args.project
|
|
237
|
+
project = args.project or env_fallback(
|
|
238
|
+
("CONTREE_PROJECT", "NEBIUS_AI_PROJECT"),
|
|
239
|
+
what="project",
|
|
240
|
+
)
|
|
216
241
|
if project is None:
|
|
217
|
-
|
|
218
|
-
if nebius_project:
|
|
219
|
-
logger.info("Using project from NEBIUS_AI_PROJECT")
|
|
220
|
-
project = nebius_project
|
|
221
|
-
else:
|
|
222
|
-
project = input("Project ID: ").strip()
|
|
242
|
+
project = input("Project ID: ").strip()
|
|
223
243
|
profile = ConfigProfile(
|
|
224
244
|
name=args.profile,
|
|
225
245
|
token=token,
|
|
@@ -235,15 +255,33 @@ def cmd_auth(args: AuthArgs) -> int | None:
|
|
|
235
255
|
return 1
|
|
236
256
|
|
|
237
257
|
try:
|
|
238
|
-
client.get("/v1/whoami")
|
|
258
|
+
resp = client.get("/v1/whoami")
|
|
259
|
+
whoami = json.loads(resp.read() or b"{}")
|
|
239
260
|
except ApiError as exc:
|
|
240
261
|
# Logs the API error message, not the token itself.
|
|
241
262
|
# nosemgrep: python-logger-credential-disclosure
|
|
242
263
|
logger.error("Token verification failed: %s. Profile not changed.", exc)
|
|
243
264
|
return 1
|
|
265
|
+
except ValueError as exc:
|
|
266
|
+
logger.error("Could not parse /v1/whoami response: %s", exc)
|
|
267
|
+
return 1
|
|
268
|
+
|
|
269
|
+
if not check_permission(whoami, REQUIRED_PERMISSION):
|
|
270
|
+
project_label = profile.project or profile.url
|
|
271
|
+
logger.warning(
|
|
272
|
+
"Warning: token is valid but sandboxes are disabled on %s"
|
|
273
|
+
" (no %r permission). The profile will be saved but no commands"
|
|
274
|
+
" will work until the service is enabled.",
|
|
275
|
+
project_label,
|
|
276
|
+
REQUIRED_PERMISSION,
|
|
277
|
+
)
|
|
244
278
|
|
|
245
279
|
cfg[args.profile] = profile
|
|
246
|
-
logger.info(
|
|
280
|
+
logger.info(
|
|
281
|
+
"auth accepted, profile %r saved to -> %s",
|
|
282
|
+
args.profile,
|
|
283
|
+
cfg.path,
|
|
284
|
+
)
|
|
247
285
|
return None
|
|
248
286
|
|
|
249
287
|
|
|
@@ -284,11 +322,17 @@ def cmd_list(args: ProfilesArgs) -> None:
|
|
|
284
322
|
|
|
285
323
|
try:
|
|
286
324
|
resp = client.get("/v1/whoami")
|
|
287
|
-
resp.read()
|
|
325
|
+
payload = resp.read()
|
|
288
326
|
except TimeoutError:
|
|
289
327
|
return profile, "timeout"
|
|
290
328
|
except Exception:
|
|
291
329
|
return profile, "error"
|
|
330
|
+
try:
|
|
331
|
+
whoami = json.loads(payload or b"{}")
|
|
332
|
+
except ValueError:
|
|
333
|
+
return profile, "error"
|
|
334
|
+
if not check_permission(whoami, REQUIRED_PERMISSION):
|
|
335
|
+
return profile, "inactive"
|
|
292
336
|
return profile, "ok"
|
|
293
337
|
|
|
294
338
|
formatter = FORMATTER.get()
|