veris-cli 2.25.2__tar.gz → 2.27.1__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 (35) hide show
  1. {veris_cli-2.25.2 → veris_cli-2.27.1}/PKG-INFO +30 -15
  2. {veris_cli-2.25.2 → veris_cli-2.27.1}/README.md +28 -14
  3. {veris_cli-2.25.2 → veris_cli-2.27.1}/pyproject.toml +3 -1
  4. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/api.py +141 -9
  5. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/build_context.py +31 -0
  6. veris_cli-2.27.1/src/veris_cli/commands/env.py +2426 -0
  7. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/scripts/docker_build.sh +11 -8
  8. veris_cli-2.27.1/src/veris_cli/stacks/__init__.py +125 -0
  9. veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/__init__.py +143 -0
  10. veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/fetch.py +158 -0
  11. veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/repo_shape.py +62 -0
  12. veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/scripts/snapshot.sh +64 -0
  13. veris_cli-2.27.1/src/veris_cli/stacks/nemoclaw/snapshot.py +118 -0
  14. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/templates.py +4 -4
  15. veris_cli-2.25.2/src/veris_cli/commands/env.py +0 -1295
  16. {veris_cli-2.25.2 → veris_cli-2.27.1}/.gitignore +0 -0
  17. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/__init__.py +0 -0
  18. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/cli.py +0 -0
  19. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/__init__.py +0 -0
  20. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/_helpers.py +0 -0
  21. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/auth.py +0 -0
  22. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/evaluations.py +0 -0
  23. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/profile.py +0 -0
  24. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/reports.py +0 -0
  25. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/run.py +0 -0
  26. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/scenarios.py +0 -0
  27. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/commands/simulations.py +0 -0
  28. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/config.py +0 -0
  29. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/output.py +0 -0
  30. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/prompts.py +0 -0
  31. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/run_output.py +0 -0
  32. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/scripts/__init__.py +0 -0
  33. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/scripts/docker_push.sh +0 -0
  34. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/searchable_checkbox.py +0 -0
  35. {veris_cli-2.25.2 → veris_cli-2.27.1}/src/veris_cli/veris_yaml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-cli
3
- Version: 2.25.2
3
+ Version: 2.27.1
4
4
  Summary: CLI to connect local agents to the Veris backend
5
5
  Project-URL: Homepage, https://github.com/veris-ai/veris-cli
6
6
  Project-URL: Bug Tracker, https://github.com/veris-ai/veris-cli/issues
@@ -8,6 +8,7 @@ Author: Veris
8
8
  Requires-Python: >=3.11
9
9
  Requires-Dist: click>=8.1.7
10
10
  Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: jinja2>=3.1.0
11
12
  Requires-Dist: pathspec>=0.12.0
12
13
  Requires-Dist: pydantic>=2.6
13
14
  Requires-Dist: pyyaml>=6.0.1
@@ -57,34 +58,48 @@ veris login YOUR_API_KEY
57
58
 
58
59
  This saves your credentials to `~/.veris/config.yaml`.
59
60
 
60
- ### 2. Create an Environment
61
+ ### 2. Submit Your Agent (Managed Onboarding)
61
62
 
62
63
  ```bash
63
- veris env create --name my-agent-env --agent-name "My Agent"
64
+ cd ~/my-agent
65
+ veris env submit
64
66
  ```
65
67
 
66
- This scaffolds a `.veris/` directory and registers your environment on Veris:
67
- - **`Dockerfile.sandbox`** Agent image definition
68
- - **`veris.yaml`** Simulation configuration (services, persona, agent settings)
69
- - **`.dockerignore`**Files excluded from image build
68
+ This is the **only command you need** to onboard a new agent. It packages
69
+ your repo, uploads it to Veris, and lets the platform generate
70
+ `Dockerfile.sandbox` and `veris.yaml` for you. You'll receive an email
71
+ when your environment is `ready` typically the same business day.
70
72
 
71
- The `--name` you pass is the agent's display name. The CLI slugifies it to create a target/env name: `"My Agent"` becomes `my-agent-env`. This name is used as the top-level key in `veris.yaml` and as the backend environment name.
73
+ No prior `veris env create` is required. If there is no env yet, `env
74
+ submit` creates one (using `--name`, `--target`, or the current directory
75
+ name).
72
76
 
73
- ### 3. Configure Your Agent
74
-
75
- Edit `.veris/Dockerfile.sandbox` and `.veris/veris.yaml` to match your agent. Set secrets:
77
+ ### 3. Set Any Secrets
76
78
 
77
79
  ```bash
78
80
  veris env vars set OPENAI_API_KEY=sk-your-key --secret
79
81
  ```
80
82
 
81
- ### 4. Build and Push
83
+ You can set secrets at any point — before or after the env reaches `ready`.
84
+
85
+ ### 4. Iterate After Release
86
+
87
+ Once your env is `ready`, pull the generated config and iterate locally:
82
88
 
83
89
  ```bash
84
- veris env push
90
+ veris env config pull # Pull generated .veris/Dockerfile.sandbox + veris.yaml
91
+ veris env push # Build and push a new image tag
85
92
  ```
86
93
 
87
- When only one target is defined, the CLI uses it automatically — no flags needed.
94
+ Re-running `veris env push` after each code change is fast. Only re-run
95
+ `veris env submit` when something structural changes (new agent entry
96
+ point, new service dependency, etc.).
97
+
98
+ > **Stack flows** — agents with non-trivial integration requirements
99
+ > (e.g. nemoclaw) use `veris env create --stack <name>`. Run `veris env
100
+ > create` without `--stack` to see the picker, which lets you choose a
101
+ > registered stack or route into `veris env submit` (the default for
102
+ > plain agents).
88
103
 
89
104
  ### 5. Generate Scenarios
90
105
 
@@ -356,7 +371,7 @@ my-cool-agent-env:
356
371
  entry_point: uv run app
357
372
  port: 8008
358
373
  environment:
359
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/SIMULATION_ID
374
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veris
360
375
  ```
361
376
 
362
377
  When only one target is defined, all commands auto-select it.
@@ -33,34 +33,48 @@ veris login YOUR_API_KEY
33
33
 
34
34
  This saves your credentials to `~/.veris/config.yaml`.
35
35
 
36
- ### 2. Create an Environment
36
+ ### 2. Submit Your Agent (Managed Onboarding)
37
37
 
38
38
  ```bash
39
- veris env create --name my-agent-env --agent-name "My Agent"
39
+ cd ~/my-agent
40
+ veris env submit
40
41
  ```
41
42
 
42
- This scaffolds a `.veris/` directory and registers your environment on Veris:
43
- - **`Dockerfile.sandbox`** Agent image definition
44
- - **`veris.yaml`** Simulation configuration (services, persona, agent settings)
45
- - **`.dockerignore`**Files excluded from image build
43
+ This is the **only command you need** to onboard a new agent. It packages
44
+ your repo, uploads it to Veris, and lets the platform generate
45
+ `Dockerfile.sandbox` and `veris.yaml` for you. You'll receive an email
46
+ when your environment is `ready` typically the same business day.
46
47
 
47
- The `--name` you pass is the agent's display name. The CLI slugifies it to create a target/env name: `"My Agent"` becomes `my-agent-env`. This name is used as the top-level key in `veris.yaml` and as the backend environment name.
48
+ No prior `veris env create` is required. If there is no env yet, `env
49
+ submit` creates one (using `--name`, `--target`, or the current directory
50
+ name).
48
51
 
49
- ### 3. Configure Your Agent
50
-
51
- Edit `.veris/Dockerfile.sandbox` and `.veris/veris.yaml` to match your agent. Set secrets:
52
+ ### 3. Set Any Secrets
52
53
 
53
54
  ```bash
54
55
  veris env vars set OPENAI_API_KEY=sk-your-key --secret
55
56
  ```
56
57
 
57
- ### 4. Build and Push
58
+ You can set secrets at any point — before or after the env reaches `ready`.
59
+
60
+ ### 4. Iterate After Release
61
+
62
+ Once your env is `ready`, pull the generated config and iterate locally:
58
63
 
59
64
  ```bash
60
- veris env push
65
+ veris env config pull # Pull generated .veris/Dockerfile.sandbox + veris.yaml
66
+ veris env push # Build and push a new image tag
61
67
  ```
62
68
 
63
- When only one target is defined, the CLI uses it automatically — no flags needed.
69
+ Re-running `veris env push` after each code change is fast. Only re-run
70
+ `veris env submit` when something structural changes (new agent entry
71
+ point, new service dependency, etc.).
72
+
73
+ > **Stack flows** — agents with non-trivial integration requirements
74
+ > (e.g. nemoclaw) use `veris env create --stack <name>`. Run `veris env
75
+ > create` without `--stack` to see the picker, which lets you choose a
76
+ > registered stack or route into `veris env submit` (the default for
77
+ > plain agents).
64
78
 
65
79
  ### 5. Generate Scenarios
66
80
 
@@ -332,7 +346,7 @@ my-cool-agent-env:
332
346
  entry_point: uv run app
333
347
  port: 8008
334
348
  environment:
335
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/SIMULATION_ID
349
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/veris
336
350
  ```
337
351
 
338
352
  When only one target is defined, all commands auto-select it.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "veris-cli"
3
- version = "2.25.2"
3
+ version = "2.27.1"
4
4
  description = "CLI to connect local agents to the Veris backend"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -16,6 +16,7 @@ dependencies = [
16
16
  "pyyaml>=6.0.1",
17
17
  "pydantic>=2.6",
18
18
  "tenacity>=8.2.0",
19
+ "jinja2>=3.1.0",
19
20
  ]
20
21
 
21
22
  [project.scripts]
@@ -57,6 +58,7 @@ asyncio_mode = "auto"
57
58
  include = [
58
59
  "src/veris_cli/**",
59
60
  "src/veris_cli/scripts/*.sh",
61
+ "src/veris_cli/stacks/nemoclaw/scripts/*.sh",
60
62
  "README.md",
61
63
  "pyproject.toml",
62
64
  ]
@@ -17,13 +17,29 @@ from veris_cli.config import Config
17
17
  class APIError(Exception):
18
18
  """Raised when the backend returns an error response with details."""
19
19
 
20
- def __init__(self, status_code: int, detail: str, url: str):
20
+ def __init__(
21
+ self,
22
+ status_code: int,
23
+ detail: str,
24
+ url: str,
25
+ retry_after: float | None = None,
26
+ ):
21
27
  self.status_code = status_code
22
28
  self.detail = detail
23
29
  self.url = url
30
+ self.retry_after = retry_after
24
31
  super().__init__(f"[{status_code}] {detail} ({url})")
25
32
 
26
33
 
34
+ def _parse_retry_after(value: object) -> float | None:
35
+ if not isinstance(value, str):
36
+ return None
37
+ try:
38
+ return float(value)
39
+ except ValueError:
40
+ return None
41
+
42
+
27
43
  def _raise_for_status(response: httpx.Response) -> None:
28
44
  """Like response.raise_for_status() but includes the response body."""
29
45
  if response.is_success:
@@ -33,23 +49,36 @@ def _raise_for_status(response: httpx.Response) -> None:
33
49
  detail = body.get("detail", response.text)
34
50
  except Exception:
35
51
  detail = response.text or response.reason_phrase
36
- raise APIError(response.status_code, detail, str(response.url))
52
+ raise APIError(
53
+ response.status_code,
54
+ detail,
55
+ str(response.url),
56
+ retry_after=_parse_retry_after(response.headers.get("Retry-After")),
57
+ )
37
58
 
38
59
 
39
- _RETRYABLE_STATUSES = frozenset({502, 503, 504})
60
+ _RETRYABLE_STATUSES = frozenset({429, 502, 503, 504})
61
+ _RETRY_AFTER_CAP_SECONDS = 60.0
62
+ _exp_wait = wait_exponential(multiplier=1, min=1, max=10)
40
63
 
41
64
 
42
65
  def _is_transient_api_error(exc: BaseException) -> bool:
43
66
  return isinstance(exc, APIError) and exc.status_code in _RETRYABLE_STATUSES
44
67
 
45
68
 
46
- # Retries transient failures (network errors, 502/503/504). Only applied to
47
- # idempotent reads — mutating calls (POST/PUT/DELETE) are left alone to avoid
48
- # duplicate side effects on retry.
69
+ def _wait_with_retry_after(retry_state) -> float:
70
+ exc = retry_state.outcome.exception() if retry_state.outcome else None
71
+ if isinstance(exc, APIError) and exc.retry_after is not None:
72
+ return min(exc.retry_after, _RETRY_AFTER_CAP_SECONDS)
73
+ return _exp_wait(retry_state)
74
+
75
+
76
+ # Retries transient failures on idempotent reads. Mutating calls
77
+ # (POST/PUT/DELETE) are left alone to avoid duplicate side effects.
49
78
  _retry_transient = retry(
50
79
  reraise=True,
51
80
  stop=stop_after_attempt(5),
52
- wait=wait_exponential(multiplier=1, min=1, max=10),
81
+ wait=_wait_with_retry_after,
53
82
  retry=(
54
83
  retry_if_exception_type(httpx.TransportError) | retry_if_exception(_is_transient_api_error)
55
84
  ),
@@ -79,13 +108,35 @@ class VerisAPI:
79
108
  return {"Authorization": f"Bearer {self.api_key}"}
80
109
 
81
110
  # Environments
82
- def create_environment(self, name: str, description: Optional[str] = None) -> dict[str, Any]:
83
- """Create a new environment (one-time setup)."""
111
+ def create_environment(
112
+ self,
113
+ name: str,
114
+ description: Optional[str] = None,
115
+ stack: Optional[str] = None,
116
+ skip_managed_onboarding: bool = False,
117
+ ) -> dict[str, Any]:
118
+ """Create a new environment (one-time setup).
119
+
120
+ `stack` records which `veris env create --stack <name>` scaffold
121
+ flow produced this env (e.g. "nemoclaw"). Backend stores it on
122
+ the environment row for analytics + per-stack base-image hints.
123
+
124
+ `skip_managed_onboarding=True` opts this env out of managed
125
+ onboarding — backend stamps it directly into the released state
126
+ (status='ready', onboard_status='completed') and the build gate
127
+ doesn't apply. Used by `veris env create --self-serve`. Default
128
+ False keeps SDK / scripted callers on the managed-onboarding
129
+ path so they stay protected.
130
+ """
84
131
  payload: dict[str, Any] = {"name": name, "description": description}
85
132
  if self.organization_id:
86
133
  payload["organization_id"] = self.organization_id
87
134
  else:
88
135
  payload["personal"] = True
136
+ if stack is not None:
137
+ payload["stack"] = stack
138
+ if skip_managed_onboarding:
139
+ payload["skip_managed_onboarding"] = True
89
140
  with httpx.Client(
90
141
  base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
91
142
  ) as client:
@@ -93,6 +144,87 @@ class VerisAPI:
93
144
  _raise_for_status(response)
94
145
  return response.json()
95
146
 
147
+ # Recipes — stack scaffold templates
148
+ @_retry_transient
149
+ def get_recipe_bundle(
150
+ self,
151
+ stack: str,
152
+ environment_name: Optional[str] = None,
153
+ agent_name: Optional[str] = None,
154
+ provider_key: Optional[str] = None,
155
+ provider_base_url: Optional[str] = None,
156
+ ) -> dict[str, Any]:
157
+ """Fetch the full recipe bundle for `stack` (file contents + metadata).
158
+
159
+ Backend renders Jinja templates server-side with the supplied query
160
+ params. sandbox-base digest is resolved later by `snapshot.sh` and
161
+ patched into the rendered Dockerfile directly — not picked here.
162
+
163
+ `provider_key` selects which LLM provider's apiKey + baseUrl gets
164
+ patched at agent boot (via veris-agent-entrypoint.sh reading the
165
+ rendered NEMOCLAW_PROVIDER_* env vars). `provider_base_url` is the
166
+ public hostname Veris's DNS aliases route to llm-proxy; omit to use
167
+ the manifest's default for `provider_key`.
168
+
169
+ Used by `veris env create --stack <name>` to scaffold `.veris/`.
170
+ """
171
+ params: dict[str, str] = {}
172
+ if environment_name:
173
+ params["environment_name"] = environment_name
174
+ if agent_name:
175
+ params["agent_name"] = agent_name
176
+ if provider_key:
177
+ params["provider_key"] = provider_key
178
+ if provider_base_url:
179
+ params["provider_base_url"] = provider_base_url
180
+ with httpx.Client(
181
+ base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
182
+ ) as client:
183
+ response = client.get(f"/v1/recipes/{stack}", params=params)
184
+ _raise_for_status(response)
185
+ return response.json()
186
+
187
+ @_retry_transient
188
+ def get_recipe_metadata(self, stack: str) -> dict[str, Any]:
189
+ """Fetch just the recipe metadata for `stack`.
190
+
191
+ Used by `veris env push` to surface an upgrade advisory when the
192
+ customer's pinned template version drifts behind the current one.
193
+ """
194
+ with httpx.Client(
195
+ base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
196
+ ) as client:
197
+ response = client.get(f"/v1/recipes/{stack}/latest")
198
+ _raise_for_status(response)
199
+ return response.json()
200
+
201
+ @_retry_transient
202
+ def get_recipe_providers(self, stack: str) -> dict[str, Any]:
203
+ """Fetch the supported LLM providers for `stack`.
204
+
205
+ Used by `veris env create --stack <name>` to show choices in the
206
+ provider prompt. Customer can also pass `--provider custom
207
+ --provider-base-url <url>` to bypass the list.
208
+ """
209
+ with httpx.Client(
210
+ base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
211
+ ) as client:
212
+ response = client.get(f"/v1/recipes/{stack}/providers")
213
+ _raise_for_status(response)
214
+ return response.json()
215
+
216
+ def get_llm_providers(self) -> dict[str, Any]:
217
+ """Fetch the canonical list of LLM providers Veris's llm-proxy
218
+ intercepts. Stack-agnostic — used by ``veris env submit`` and the
219
+ stack-less ``veris env create`` to populate the API-key picker.
220
+ """
221
+ with httpx.Client(
222
+ base_url=self.base_url, headers=self._headers(), timeout=self.DEFAULT_TIMEOUT
223
+ ) as client:
224
+ response = client.get("/v1/llm-providers")
225
+ _raise_for_status(response)
226
+ return response.json()
227
+
96
228
  @_retry_transient
97
229
  def list_environments(
98
230
  self, status: Optional[str] = None, limit: int = 20, offset: int = 0
@@ -3,9 +3,11 @@
3
3
  Packages the project root into a .tar.gz, respecting .dockerignore patterns.
4
4
  """
5
5
 
6
+ import io
6
7
  import os
7
8
  import tarfile
8
9
  import tempfile
10
+ import time
9
11
  from pathlib import Path
10
12
 
11
13
  import pathspec
@@ -32,6 +34,7 @@ def create_build_context(
32
34
  output_path: Path | None = None,
33
35
  dockerfile: Path | str | None = None,
34
36
  require_dockerfile: bool = True,
37
+ file_overrides: dict[str, str] | None = None,
35
38
  ) -> tuple[Path, int]:
36
39
  """Create a tar.gz build context from the project root.
37
40
 
@@ -46,6 +49,13 @@ def create_build_context(
46
49
  to `.veris/Dockerfile.sandbox` when None.
47
50
  require_dockerfile: If False, skip Dockerfile validation and inclusion.
48
51
  Used by ``env submit`` where no Dockerfile exists yet.
52
+ file_overrides: Optional arcname → content map. For each entry the
53
+ given content is written into the tarball at the arcname,
54
+ replacing any on-disk file at the same path and bypassing
55
+ `.dockerignore`. The customer's working tree is never modified.
56
+ Used by ``env submit`` to inject the per-target single-target
57
+ hint into ``.veris/veris.yaml`` without rewriting the on-disk
58
+ (possibly multi-target) file.
49
59
 
50
60
  Returns:
51
61
  Tuple of (tarball_path, size_bytes).
@@ -54,6 +64,7 @@ def create_build_context(
54
64
  ValueError: If tarball exceeds the size cap or the Dockerfile is missing.
55
65
  """
56
66
  spec = _load_dockerignore(project_root)
67
+ overrides = file_overrides or {}
57
68
 
58
69
  if output_path is None:
59
70
  fd, tmp = tempfile.mkstemp(suffix=".tar.gz", prefix="veris-build-")
@@ -97,6 +108,12 @@ def create_build_context(
97
108
  if rel_path == ".":
98
109
  rel_path = f
99
110
 
111
+ # Overrides win and bypass dockerignore — defer to the
112
+ # post-walk loop so they're written exactly once with the
113
+ # override content.
114
+ if rel_path in overrides:
115
+ continue
116
+
100
117
  # Skip ignored files (but always include the selected Dockerfile)
101
118
  if spec.match_file(rel_path) and rel_path != dockerfile_rel:
102
119
  continue
@@ -112,6 +129,20 @@ def create_build_context(
112
129
  tar.add(str(dockerfile_path), arcname=dockerfile_rel)
113
130
  file_count += 1
114
131
 
132
+ # Write overrides last so they always land regardless of ignore rules.
133
+ # We build a TarInfo by hand instead of going through ``tar.add`` —
134
+ # the override content lives only in memory, not on disk.
135
+ now = int(time.time())
136
+ for arcname, content in overrides.items():
137
+ data = content.encode("utf-8")
138
+ info = tarfile.TarInfo(name=arcname)
139
+ info.size = len(data)
140
+ info.mode = 0o644
141
+ info.mtime = now
142
+ info.type = tarfile.REGTYPE
143
+ tar.addfile(info, io.BytesIO(data))
144
+ file_count += 1
145
+
115
146
  size = output_path.stat().st_size
116
147
  if size > MAX_TARBALL_SIZE:
117
148
  output_path.unlink()