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.
Files changed (69) hide show
  1. {contree_cli-0.5.0 → contree_cli-0.6.0}/PKG-INFO +16 -9
  2. {contree_cli-0.5.0 → contree_cli-0.6.0}/README.md +15 -8
  3. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/__main__.py +17 -0
  4. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/agent.md +87 -8
  5. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/arguments.py +28 -17
  6. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/auth.py +68 -24
  7. contree_cli-0.6.0/contree_cli/cli/build.py +224 -0
  8. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/file.py +149 -3
  9. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/images.py +57 -14
  10. contree_cli-0.6.0/contree_cli/cli/operation.py +157 -0
  11. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/ps.py +87 -69
  12. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/run.py +29 -12
  13. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/client.py +41 -6
  14. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/config.py +13 -18
  15. contree_cli-0.6.0/contree_cli/docker/__init__.py +42 -0
  16. contree_cli-0.6.0/contree_cli/docker/context.py +161 -0
  17. contree_cli-0.6.0/contree_cli/docker/dockerignore.py +102 -0
  18. contree_cli-0.6.0/contree_cli/docker/keyword.py +94 -0
  19. contree_cli-0.6.0/contree_cli/docker/kw_add.py +132 -0
  20. contree_cli-0.6.0/contree_cli/docker/kw_arg.py +41 -0
  21. contree_cli-0.6.0/contree_cli/docker/kw_copy.py +178 -0
  22. contree_cli-0.6.0/contree_cli/docker/kw_env.py +33 -0
  23. contree_cli-0.6.0/contree_cli/docker/kw_from.py +119 -0
  24. contree_cli-0.6.0/contree_cli/docker/kw_run.py +190 -0
  25. contree_cli-0.6.0/contree_cli/docker/kw_skipped.py +30 -0
  26. contree_cli-0.6.0/contree_cli/docker/kw_user.py +31 -0
  27. contree_cli-0.6.0/contree_cli/docker/kw_workdir.py +37 -0
  28. contree_cli-0.6.0/contree_cli/docker/local_context.py +159 -0
  29. contree_cli-0.6.0/contree_cli/docker/parser.py +93 -0
  30. contree_cli-0.6.0/contree_cli/docker/url_fetch.py +303 -0
  31. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/manual.md +13 -8
  32. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/mapped_file.py +11 -1
  33. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/session.py +63 -3
  34. contree_cli-0.6.0/contree_cli/shell/argmap.py +69 -0
  35. contree_cli-0.6.0/contree_cli/shell/argspec.py +343 -0
  36. contree_cli-0.6.0/contree_cli/shell/cache.py +109 -0
  37. contree_cli-0.6.0/contree_cli/shell/completer.py +371 -0
  38. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/repl.py +256 -54
  39. contree_cli-0.6.0/contree_cli/shell/sources.py +495 -0
  40. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/skill_body.md +92 -4
  41. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/types.py +17 -0
  42. contree_cli-0.6.0/contree_cli/update_check.py +165 -0
  43. {contree_cli-0.5.0 → contree_cli-0.6.0}/pyproject.toml +1 -1
  44. contree_cli-0.5.0/contree_cli/shell/completer.py +0 -485
  45. {contree_cli-0.5.0 → contree_cli-0.6.0}/LICENSE +0 -0
  46. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/__init__.py +0 -0
  47. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/__init__.py +0 -0
  48. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/agent.py +0 -0
  49. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cat.py +0 -0
  50. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cd.py +0 -0
  51. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/cp.py +0 -0
  52. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/env.py +0 -0
  53. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/kill.py +0 -0
  54. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/ls.py +0 -0
  55. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/session.py +0 -0
  56. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/show.py +0 -0
  57. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/skill.py +0 -0
  58. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/tag.py +0 -0
  59. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/cli/use.py +0 -0
  60. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/log.py +0 -0
  61. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/man.py +0 -0
  62. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/migrations.py +0 -0
  63. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/output.py +0 -0
  64. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/py.typed +0 -0
  65. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/__init__.py +0 -0
  66. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/history.py +0 -0
  67. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/parser.py +0 -0
  68. {contree_cli-0.5.0 → contree_cli-0.6.0}/contree_cli/shell/trie.py +0 -0
  69. {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.5.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-cli/config.ini`.
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` environment variables are set and no CLI flags are passed, they are picked up automatically instead of prompting.
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-cli`) |
498
- | `CONTREE_TOKEN` | API bearer token (overrides config) |
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
- Environment variables take precedence over the config file. `--token` and `--url` flags override everything.
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-cli/config.ini`.
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` environment variables are set and no CLI flags are passed, they are picked up automatically instead of prompting.
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-cli`) |
291
- | `CONTREE_TOKEN` | API bearer token (overrides config) |
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
- Environment variables take precedence over the config file. `--token` and `--url` flags override everything.
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
- More: contree images --help, contree tag --help
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-cli/
332
- config.ini profile credentials
333
- sessions-{profile}.db per-profile sessions, history, cache
334
- skills.db installed agent skill registry
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
- CONTREE_TOKEN API token (overrides config)
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 (advanced overrides; most users can ignore):
75
- CONTREE_TOKEN API bearer token (overrides config file)
76
- CONTREE_URL API base URL (overrides config file)
77
- CONTREE_PROJECT Project ID for IAM auth (overrides config file)
78
- CONTREE_PROFILE Active config profile (overrides config file)
79
- CONTREE_SESSION Explicit session name (for multi-terminal workflows).
80
- If unset, contree auto-generates <cwd>+<8hex> (derived from
81
- profile+ppid+tty); export your own for stable reuse.
82
- You can also pass -S/--session instead of exporting env.
83
- CONTREE_SESSION_DB Path to session SQLite database
84
-
85
- nebius shortcuts (used by `contree auth` as fallback when flags are omitted):
86
- NEBIUS_API_KEY Fallback token for auth registration
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 config and env)",
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 config and env)",
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 config and env)",
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
- Nebius environment variable shortcuts:
12
- NEBIUS_API_KEY used as token fallback during registration
13
- NEBIUS_AI_PROJECT used as project fallback during IAM registration
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
- nebius_key = os.environ.get("NEBIUS_API_KEY")
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 > interactive prompt
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
- nebius_project = os.environ.get("NEBIUS_AI_PROJECT")
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("Token verified and saved to profile %r", args.profile)
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()