opensandbox-cli 0.1.0__py3-none-any.whl
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.
- opensandbox_cli/__init__.py +20 -0
- opensandbox_cli/__main__.py +20 -0
- opensandbox_cli/client.py +127 -0
- opensandbox_cli/commands/__init__.py +13 -0
- opensandbox_cli/commands/command.py +359 -0
- opensandbox_cli/commands/config_cmd.py +183 -0
- opensandbox_cli/commands/devops.py +123 -0
- opensandbox_cli/commands/egress.py +98 -0
- opensandbox_cli/commands/file.py +442 -0
- opensandbox_cli/commands/sandbox.py +580 -0
- opensandbox_cli/commands/skills.py +775 -0
- opensandbox_cli/config.py +160 -0
- opensandbox_cli/main.py +138 -0
- opensandbox_cli/output.py +363 -0
- opensandbox_cli/py.typed +1 -0
- opensandbox_cli/skill_registry.py +184 -0
- opensandbox_cli/skills/opensandbox-command-execution.md +215 -0
- opensandbox_cli/skills/opensandbox-file-operations.md +244 -0
- opensandbox_cli/skills/opensandbox-network-egress.md +179 -0
- opensandbox_cli/skills/opensandbox-sandbox-lifecycle.md +305 -0
- opensandbox_cli/skills/opensandbox-sandbox-troubleshooting.md +177 -0
- opensandbox_cli/utils.py +212 -0
- opensandbox_cli-0.1.0.dist-info/METADATA +597 -0
- opensandbox_cli-0.1.0.dist-info/RECORD +27 -0
- opensandbox_cli-0.1.0.dist-info/WHEEL +4 -0
- opensandbox_cli-0.1.0.dist-info/entry_points.txt +3 -0
- opensandbox_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# Copyright 2026 Alibaba Group Holding Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""File operation commands: cat, write, upload, download, rm, mv, mkdir, rmdir, search, info, chmod, replace."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import click
|
|
23
|
+
|
|
24
|
+
from opensandbox_cli.client import ClientContext
|
|
25
|
+
from opensandbox_cli.utils import handle_errors, output_option, prepare_output
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _parse_permission_mode(mode: str) -> int:
|
|
29
|
+
"""Parse a permission mode string like 644 or 755."""
|
|
30
|
+
try:
|
|
31
|
+
return int(mode)
|
|
32
|
+
except ValueError as exc:
|
|
33
|
+
raise click.BadParameter(
|
|
34
|
+
f"Invalid permission mode '{mode}'. Use a permission value like 644 or 755."
|
|
35
|
+
) from exc
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@click.group("file", invoke_without_command=True)
|
|
39
|
+
@click.pass_context
|
|
40
|
+
def file_group(ctx: click.Context) -> None:
|
|
41
|
+
"""📁 File operations on a sandbox."""
|
|
42
|
+
if ctx.invoked_subcommand is None:
|
|
43
|
+
click.echo(ctx.get_help())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ---- cat (read) -----------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
@file_group.command("cat")
|
|
49
|
+
@click.argument("sandbox_id")
|
|
50
|
+
@click.argument("path")
|
|
51
|
+
@click.option("--encoding", default="utf-8", help="File encoding.")
|
|
52
|
+
@output_option("raw", help_text="Output format: raw.")
|
|
53
|
+
@click.pass_obj
|
|
54
|
+
@handle_errors
|
|
55
|
+
def file_cat(
|
|
56
|
+
obj: ClientContext,
|
|
57
|
+
sandbox_id: str,
|
|
58
|
+
path: str,
|
|
59
|
+
encoding: str,
|
|
60
|
+
output_format: str | None,
|
|
61
|
+
) -> None:
|
|
62
|
+
"""Read a file from the sandbox."""
|
|
63
|
+
prepare_output(obj, output_format, allowed=("raw",), fallback="raw")
|
|
64
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
65
|
+
try:
|
|
66
|
+
content = sandbox.files.read_file(path, encoding=encoding)
|
|
67
|
+
click.echo(content, nl=False)
|
|
68
|
+
finally:
|
|
69
|
+
sandbox.close()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---- write ----------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
@file_group.command("write")
|
|
75
|
+
@click.argument("sandbox_id")
|
|
76
|
+
@click.argument("path")
|
|
77
|
+
@click.option("--content", "-c", default=None, help="Content to write. Reads from stdin if not provided.")
|
|
78
|
+
@click.option("--encoding", default="utf-8", help="File encoding.")
|
|
79
|
+
@click.option("--mode", default=None, help="File permission mode (e.g. 0644).")
|
|
80
|
+
@click.option("--owner", default=None, help="File owner.")
|
|
81
|
+
@click.option("--group", default=None, help="File group.")
|
|
82
|
+
@output_option("table", "json", "yaml")
|
|
83
|
+
@click.pass_obj
|
|
84
|
+
@handle_errors
|
|
85
|
+
def file_write(
|
|
86
|
+
obj: ClientContext,
|
|
87
|
+
sandbox_id: str,
|
|
88
|
+
path: str,
|
|
89
|
+
content: str | None,
|
|
90
|
+
encoding: str,
|
|
91
|
+
mode: str | None,
|
|
92
|
+
owner: str | None,
|
|
93
|
+
group: str | None,
|
|
94
|
+
output_format: str | None,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Write content to a file in the sandbox."""
|
|
97
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
98
|
+
file_content = content
|
|
99
|
+
if file_content is None:
|
|
100
|
+
if sys.stdin.isatty():
|
|
101
|
+
click.echo("Reading from stdin (Ctrl+D to finish):", err=True)
|
|
102
|
+
file_content = sys.stdin.read()
|
|
103
|
+
|
|
104
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
105
|
+
try:
|
|
106
|
+
kwargs: dict = {"encoding": encoding}
|
|
107
|
+
if mode is not None:
|
|
108
|
+
kwargs["mode"] = _parse_permission_mode(mode)
|
|
109
|
+
if owner is not None:
|
|
110
|
+
kwargs["owner"] = owner
|
|
111
|
+
if group is not None:
|
|
112
|
+
kwargs["group"] = group
|
|
113
|
+
sandbox.files.write_file(path, file_content, **kwargs)
|
|
114
|
+
obj.output.success(f"Written: {path}")
|
|
115
|
+
finally:
|
|
116
|
+
sandbox.close()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---- upload ---------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
@file_group.command("upload")
|
|
122
|
+
@click.argument("sandbox_id")
|
|
123
|
+
@click.argument("local_path", type=click.Path(exists=True))
|
|
124
|
+
@click.argument("remote_path")
|
|
125
|
+
@output_option("table", "json", "yaml")
|
|
126
|
+
@click.pass_obj
|
|
127
|
+
@handle_errors
|
|
128
|
+
def file_upload(
|
|
129
|
+
obj: ClientContext,
|
|
130
|
+
sandbox_id: str,
|
|
131
|
+
local_path: str,
|
|
132
|
+
remote_path: str,
|
|
133
|
+
output_format: str | None,
|
|
134
|
+
) -> None:
|
|
135
|
+
"""Upload a local file to the sandbox."""
|
|
136
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
137
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
138
|
+
try:
|
|
139
|
+
with Path(local_path).open("rb") as data:
|
|
140
|
+
sandbox.files.write_file(remote_path, data)
|
|
141
|
+
obj.output.success(f"Uploaded: {local_path} → {remote_path}")
|
|
142
|
+
finally:
|
|
143
|
+
sandbox.close()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ---- download -------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
@file_group.command("download")
|
|
149
|
+
@click.argument("sandbox_id")
|
|
150
|
+
@click.argument("remote_path")
|
|
151
|
+
@click.argument("local_path", type=click.Path())
|
|
152
|
+
@output_option("table", "json", "yaml")
|
|
153
|
+
@click.pass_obj
|
|
154
|
+
@handle_errors
|
|
155
|
+
def file_download(
|
|
156
|
+
obj: ClientContext,
|
|
157
|
+
sandbox_id: str,
|
|
158
|
+
remote_path: str,
|
|
159
|
+
local_path: str,
|
|
160
|
+
output_format: str | None,
|
|
161
|
+
) -> None:
|
|
162
|
+
"""Download a file from the sandbox to local disk."""
|
|
163
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
164
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
165
|
+
try:
|
|
166
|
+
destination = Path(local_path)
|
|
167
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
with destination.open("wb") as out:
|
|
169
|
+
for chunk in sandbox.files.read_bytes_stream(remote_path):
|
|
170
|
+
out.write(chunk)
|
|
171
|
+
obj.output.success(f"Downloaded: {remote_path} → {local_path}")
|
|
172
|
+
finally:
|
|
173
|
+
sandbox.close()
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---- rm (delete) ----------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
@file_group.command("rm")
|
|
179
|
+
@click.argument("sandbox_id")
|
|
180
|
+
@click.argument("paths", nargs=-1, required=True)
|
|
181
|
+
@output_option("table", "json", "yaml")
|
|
182
|
+
@click.pass_obj
|
|
183
|
+
@handle_errors
|
|
184
|
+
def file_rm(
|
|
185
|
+
obj: ClientContext,
|
|
186
|
+
sandbox_id: str,
|
|
187
|
+
paths: tuple[str, ...],
|
|
188
|
+
output_format: str | None,
|
|
189
|
+
) -> None:
|
|
190
|
+
"""Delete files from the sandbox."""
|
|
191
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
192
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
193
|
+
try:
|
|
194
|
+
sandbox.files.delete_files(list(paths))
|
|
195
|
+
obj.output.print_rows(
|
|
196
|
+
[{"path": p, "status": "deleted"} for p in paths],
|
|
197
|
+
columns=["path", "status"],
|
|
198
|
+
title="Deleted Files",
|
|
199
|
+
)
|
|
200
|
+
finally:
|
|
201
|
+
sandbox.close()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# ---- mv (move) ------------------------------------------------------------
|
|
205
|
+
|
|
206
|
+
@file_group.command("mv")
|
|
207
|
+
@click.argument("sandbox_id")
|
|
208
|
+
@click.argument("source")
|
|
209
|
+
@click.argument("destination")
|
|
210
|
+
@output_option("table", "json", "yaml")
|
|
211
|
+
@click.pass_obj
|
|
212
|
+
@handle_errors
|
|
213
|
+
def file_mv(
|
|
214
|
+
obj: ClientContext,
|
|
215
|
+
sandbox_id: str,
|
|
216
|
+
source: str,
|
|
217
|
+
destination: str,
|
|
218
|
+
output_format: str | None,
|
|
219
|
+
) -> None:
|
|
220
|
+
"""Move/rename a file in the sandbox."""
|
|
221
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
222
|
+
from opensandbox.models.filesystem import MoveEntry
|
|
223
|
+
|
|
224
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
225
|
+
try:
|
|
226
|
+
sandbox.files.move_files([MoveEntry(source=source, destination=destination)])
|
|
227
|
+
obj.output.success(f"Moved: {source} → {destination}")
|
|
228
|
+
finally:
|
|
229
|
+
sandbox.close()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# ---- mkdir ----------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
@file_group.command("mkdir")
|
|
235
|
+
@click.argument("sandbox_id")
|
|
236
|
+
@click.argument("paths", nargs=-1, required=True)
|
|
237
|
+
@click.option("--mode", default=None, help="Directory permission mode.")
|
|
238
|
+
@click.option("--owner", default=None, help="Directory owner.")
|
|
239
|
+
@click.option("--group", default=None, help="Directory group.")
|
|
240
|
+
@output_option("table", "json", "yaml")
|
|
241
|
+
@click.pass_obj
|
|
242
|
+
@handle_errors
|
|
243
|
+
def file_mkdir(
|
|
244
|
+
obj: ClientContext,
|
|
245
|
+
sandbox_id: str,
|
|
246
|
+
paths: tuple[str, ...],
|
|
247
|
+
mode: str | None,
|
|
248
|
+
owner: str | None,
|
|
249
|
+
group: str | None,
|
|
250
|
+
output_format: str | None,
|
|
251
|
+
) -> None:
|
|
252
|
+
"""Create directories in the sandbox."""
|
|
253
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
254
|
+
from opensandbox.models.filesystem import WriteEntry
|
|
255
|
+
|
|
256
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
257
|
+
try:
|
|
258
|
+
entries = []
|
|
259
|
+
for p in paths:
|
|
260
|
+
kwargs: dict = {"path": p}
|
|
261
|
+
if mode is not None:
|
|
262
|
+
kwargs["mode"] = _parse_permission_mode(mode)
|
|
263
|
+
if owner is not None:
|
|
264
|
+
kwargs["owner"] = owner
|
|
265
|
+
if group is not None:
|
|
266
|
+
kwargs["group"] = group
|
|
267
|
+
entries.append(WriteEntry(**kwargs))
|
|
268
|
+
sandbox.files.create_directories(entries)
|
|
269
|
+
obj.output.print_rows(
|
|
270
|
+
[{"path": p, "status": "created"} for p in paths],
|
|
271
|
+
columns=["path", "status"],
|
|
272
|
+
title="Created Directories",
|
|
273
|
+
)
|
|
274
|
+
finally:
|
|
275
|
+
sandbox.close()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ---- rmdir ----------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
@file_group.command("rmdir")
|
|
281
|
+
@click.argument("sandbox_id")
|
|
282
|
+
@click.argument("paths", nargs=-1, required=True)
|
|
283
|
+
@output_option("table", "json", "yaml")
|
|
284
|
+
@click.pass_obj
|
|
285
|
+
@handle_errors
|
|
286
|
+
def file_rmdir(
|
|
287
|
+
obj: ClientContext,
|
|
288
|
+
sandbox_id: str,
|
|
289
|
+
paths: tuple[str, ...],
|
|
290
|
+
output_format: str | None,
|
|
291
|
+
) -> None:
|
|
292
|
+
"""Delete directories from the sandbox."""
|
|
293
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
294
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
295
|
+
try:
|
|
296
|
+
sandbox.files.delete_directories(list(paths))
|
|
297
|
+
obj.output.print_rows(
|
|
298
|
+
[{"path": p, "status": "removed"} for p in paths],
|
|
299
|
+
columns=["path", "status"],
|
|
300
|
+
title="Removed Directories",
|
|
301
|
+
)
|
|
302
|
+
finally:
|
|
303
|
+
sandbox.close()
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
# ---- search ---------------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
@file_group.command("search")
|
|
309
|
+
@click.argument("sandbox_id")
|
|
310
|
+
@click.argument("path")
|
|
311
|
+
@click.option("--pattern", "-p", required=True, help="Glob pattern to search for.")
|
|
312
|
+
@output_option("table", "json", "yaml")
|
|
313
|
+
@click.pass_obj
|
|
314
|
+
@handle_errors
|
|
315
|
+
def file_search(
|
|
316
|
+
obj: ClientContext,
|
|
317
|
+
sandbox_id: str,
|
|
318
|
+
path: str,
|
|
319
|
+
pattern: str,
|
|
320
|
+
output_format: str | None,
|
|
321
|
+
) -> None:
|
|
322
|
+
"""Search for files in the sandbox."""
|
|
323
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
324
|
+
from opensandbox.models.filesystem import SearchEntry
|
|
325
|
+
|
|
326
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
327
|
+
try:
|
|
328
|
+
results = sandbox.files.search(SearchEntry(path=path, pattern=pattern))
|
|
329
|
+
if not results:
|
|
330
|
+
if obj.output.fmt in ("json", "yaml"):
|
|
331
|
+
obj.output.print_models([], columns=[])
|
|
332
|
+
else:
|
|
333
|
+
obj.output.info("No files found.")
|
|
334
|
+
return
|
|
335
|
+
if obj.output.fmt in ("json", "yaml"):
|
|
336
|
+
obj.output.print_models(results, columns=["path", "size", "mode", "owner", "modified_at"])
|
|
337
|
+
else:
|
|
338
|
+
obj.output.print_models(results, columns=["path", "size", "owner"], title="Search Results")
|
|
339
|
+
finally:
|
|
340
|
+
sandbox.close()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ---- info (stat) ----------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
@file_group.command("info")
|
|
346
|
+
@click.argument("sandbox_id")
|
|
347
|
+
@click.argument("paths", nargs=-1, required=True)
|
|
348
|
+
@output_option("table", "json", "yaml")
|
|
349
|
+
@click.pass_obj
|
|
350
|
+
@handle_errors
|
|
351
|
+
def file_info(
|
|
352
|
+
obj: ClientContext,
|
|
353
|
+
sandbox_id: str,
|
|
354
|
+
paths: tuple[str, ...],
|
|
355
|
+
output_format: str | None,
|
|
356
|
+
) -> None:
|
|
357
|
+
"""Get file/directory info."""
|
|
358
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
359
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
360
|
+
try:
|
|
361
|
+
info_map = sandbox.files.get_file_info(list(paths))
|
|
362
|
+
rows = [{"path": path, **entry.model_dump(mode="json")} for path, entry in info_map.items()]
|
|
363
|
+
obj.output.print_rows(
|
|
364
|
+
rows,
|
|
365
|
+
columns=["path", "size", "mode", "owner", "group", "created_at", "modified_at"],
|
|
366
|
+
title="Path Info",
|
|
367
|
+
)
|
|
368
|
+
finally:
|
|
369
|
+
sandbox.close()
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# ---- chmod ----------------------------------------------------------------
|
|
373
|
+
|
|
374
|
+
@file_group.command("chmod")
|
|
375
|
+
@click.argument("sandbox_id")
|
|
376
|
+
@click.argument("path")
|
|
377
|
+
@click.option("--mode", required=True, help="Permission mode (e.g. 0755).")
|
|
378
|
+
@click.option("--owner", default=None, help="File owner.")
|
|
379
|
+
@click.option("--group", default=None, help="File group.")
|
|
380
|
+
@output_option("table", "json", "yaml")
|
|
381
|
+
@click.pass_obj
|
|
382
|
+
@handle_errors
|
|
383
|
+
def file_chmod(
|
|
384
|
+
obj: ClientContext,
|
|
385
|
+
sandbox_id: str,
|
|
386
|
+
path: str,
|
|
387
|
+
mode: str,
|
|
388
|
+
owner: str | None,
|
|
389
|
+
group: str | None,
|
|
390
|
+
output_format: str | None,
|
|
391
|
+
) -> None:
|
|
392
|
+
"""Set file permissions."""
|
|
393
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
394
|
+
from opensandbox.models.filesystem import SetPermissionEntry
|
|
395
|
+
|
|
396
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
397
|
+
try:
|
|
398
|
+
sandbox.files.set_permissions(
|
|
399
|
+
[
|
|
400
|
+
SetPermissionEntry(
|
|
401
|
+
path=path,
|
|
402
|
+
mode=_parse_permission_mode(mode),
|
|
403
|
+
owner=owner,
|
|
404
|
+
group=group,
|
|
405
|
+
)
|
|
406
|
+
]
|
|
407
|
+
)
|
|
408
|
+
obj.output.success(f"Permissions set: {path}")
|
|
409
|
+
finally:
|
|
410
|
+
sandbox.close()
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# ---- replace --------------------------------------------------------------
|
|
414
|
+
|
|
415
|
+
@file_group.command("replace")
|
|
416
|
+
@click.argument("sandbox_id")
|
|
417
|
+
@click.argument("path")
|
|
418
|
+
@click.option("--old", required=True, help="Text to search for.")
|
|
419
|
+
@click.option("--new", required=True, help="Replacement text.")
|
|
420
|
+
@output_option("table", "json", "yaml")
|
|
421
|
+
@click.pass_obj
|
|
422
|
+
@handle_errors
|
|
423
|
+
def file_replace(
|
|
424
|
+
obj: ClientContext,
|
|
425
|
+
sandbox_id: str,
|
|
426
|
+
path: str,
|
|
427
|
+
old: str,
|
|
428
|
+
new: str,
|
|
429
|
+
output_format: str | None,
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Replace content in a file."""
|
|
432
|
+
prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
|
|
433
|
+
from opensandbox.models.filesystem import ContentReplaceEntry
|
|
434
|
+
|
|
435
|
+
sandbox = obj.connect_sandbox(sandbox_id)
|
|
436
|
+
try:
|
|
437
|
+
sandbox.files.replace_contents(
|
|
438
|
+
[ContentReplaceEntry(path=path, old_content=old, new_content=new)]
|
|
439
|
+
)
|
|
440
|
+
obj.output.success(f"Replaced in: {path}")
|
|
441
|
+
finally:
|
|
442
|
+
sandbox.close()
|