gulp-cli 1.0.8__tar.gz → 1.0.9__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.
- {gulp_cli-1.0.8/src/gulp_cli.egg-info → gulp_cli-1.0.9}/PKG-INFO +1 -1
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/command-reference.md +44 -1
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/examples.md +28 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/_version.py +3 -3
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/ingest.py +187 -2
- {gulp_cli-1.0.8 → gulp_cli-1.0.9/src/gulp_cli.egg-info}/PKG-INFO +1 -1
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/.github/workflows/python-package.yml +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/.gitignore +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/README.md +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/extensions.md +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/getting-started.md +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/resource-management.md +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/docs/troubleshooting-cli.md +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/pyproject.toml +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/setup.cfg +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/__init__.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/__main__.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/cli.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/client.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/__init__.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/acl.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/auth.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/collab.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/context.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/db.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/enhance_map.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/glyph.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/mapping.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/operations.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/plugin.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/query.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/source.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/stats.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/storage.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/user_group.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/commands/users.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/config.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/extension/__init__.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/extension/query_sigma_zip.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/extension/story.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/extension_helpers.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/extensions.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/output.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli/utils.py +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/SOURCES.txt +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/dependency_links.txt +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/entry_points.txt +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/requires.txt +0 -0
- {gulp_cli-1.0.8 → gulp_cli-1.0.9}/src/gulp_cli.egg-info/top_level.txt +0 -0
|
@@ -520,6 +520,8 @@ gulp-cli ingest file-to-source source123 '/new/*.evtx'
|
|
|
520
520
|
|
|
521
521
|
Ingest from a ZIP archive.
|
|
522
522
|
|
|
523
|
+
> TO BE DEPRECATED...
|
|
524
|
+
|
|
523
525
|
```bash
|
|
524
526
|
gulp-cli ingest zip OPERATION_ID ZIP_FILE [OPTIONS]
|
|
525
527
|
```
|
|
@@ -527,7 +529,7 @@ gulp-cli ingest zip OPERATION_ID ZIP_FILE [OPTIONS]
|
|
|
527
529
|
**Arguments:**
|
|
528
530
|
|
|
529
531
|
- `OPERATION_ID` — Target operation
|
|
530
|
-
- `ZIP_FILE` — Path to ZIP file
|
|
532
|
+
- `ZIP_FILE` — Path to ZIP file containing a `metadata.json` in the root, describing the content as specified in gulp's `ingest_zip` docs.
|
|
531
533
|
|
|
532
534
|
**Options:**
|
|
533
535
|
|
|
@@ -546,6 +548,47 @@ gulp-cli ingest zip my_op /data/evidence.zip --create-operation
|
|
|
546
548
|
|
|
547
549
|
---
|
|
548
550
|
|
|
551
|
+
#### `zip-create`
|
|
552
|
+
|
|
553
|
+
Create a ZIP archive from one or more source path expressions.
|
|
554
|
+
|
|
555
|
+
Path expressions may be:
|
|
556
|
+
|
|
557
|
+
- a single file path
|
|
558
|
+
- a directory path
|
|
559
|
+
- a glob mask (for example `/bla/somef*.txt`, `/bla/*`, `**/*.evtx`)
|
|
560
|
+
|
|
561
|
+
Environment variables and home shortcuts are expanded in all input paths (for example `$EVIDENCE_DIR/*.evtx`, `~/cases/case-01/*`).
|
|
562
|
+
|
|
563
|
+
```bash
|
|
564
|
+
gulp-cli ingest zip-create OUTPUT_ZIP [PATH_PATTERN...] [OPTIONS]
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**Arguments:**
|
|
568
|
+
|
|
569
|
+
- `OUTPUT_ZIP` — Destination ZIP path (supports environment variables and `~`)
|
|
570
|
+
- `PATH_PATTERN` — File, directory, or glob path expression (multiple allowed)
|
|
571
|
+
|
|
572
|
+
**Options:**
|
|
573
|
+
|
|
574
|
+
- `--paths-file TEXT` — Text file with one source path expression per line (supports environment variables and `~` per line)
|
|
575
|
+
- `--overwrite` — Overwrite output ZIP if it already exists
|
|
576
|
+
|
|
577
|
+
**Examples:**
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
# Create a ZIP from mixed path expressions
|
|
581
|
+
gulp-cli ingest zip-create /tmp/evidence.zip /data/host1/*.evtx /data/host2 /data/readme.txt
|
|
582
|
+
|
|
583
|
+
# Use environment variables and home shortcuts
|
|
584
|
+
gulp-cli ingest zip-create '$CASE_DIR/evidence.zip' '$CASE_DIR/raw/*.json' '~/pcaps/*.pcap' --overwrite
|
|
585
|
+
|
|
586
|
+
# Read source path expressions from a file
|
|
587
|
+
gulp-cli ingest zip-create /tmp/evidence.zip --paths-file /tmp/evidence_paths.txt --overwrite
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
549
592
|
#### `raw`
|
|
550
593
|
|
|
551
594
|
Ingest raw payload chunks into an operation.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- [JSON Logs Ingestion](#json-logs-ingestion)
|
|
10
10
|
- [Add More Evidence to Existing Source](#add-more-evidence-to-existing-source)
|
|
11
11
|
- [ZIP Archive Ingestion](#zip-archive-ingestion)
|
|
12
|
+
- [Create ZIP Archive from Paths and Masks](#create-zip-archive-from-paths-and-masks)
|
|
12
13
|
- [Raw Payload Ingestion](#raw-payload-ingestion)
|
|
13
14
|
- [Request Stats Monitoring Workflows](#request-stats-monitoring-workflows)
|
|
14
15
|
- [Monitor Ongoing Requests (Live)](#monitor-ongoing-requests-live)
|
|
@@ -175,6 +176,10 @@ gulp-cli ingest file incident-001 csv /data/access_log.csv \
|
|
|
175
176
|
# pass mapping directly without using a mapping file
|
|
176
177
|
gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters": { "mappings": { "test
|
|
177
178
|
_mapping": { "fields": { "Created0x10": { "ecs": [ "@timestamp" ] } } } } } }' --reset-operation --wait
|
|
179
|
+
|
|
180
|
+
# pass mapping using a gulp mapping file with mapping_id to specify which mapping to use in the file
|
|
181
|
+
gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters"
|
|
182
|
+
: { "mapping_file": "mftecmd_csv.json", "mapping_id": "record" } }' --wait --reset-operation
|
|
178
183
|
```
|
|
179
184
|
|
|
180
185
|
### JSON Logs Ingestion
|
|
@@ -210,6 +215,29 @@ gulp-cli ingest zip incident-001 /evidence/evidence.zip --wait
|
|
|
210
215
|
gulp-cli ingest zip incident-001 /evidence/evidence.zip --create-operation
|
|
211
216
|
```
|
|
212
217
|
|
|
218
|
+
### Create ZIP Archive from Paths and Masks
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Build ZIP from mixed sources: file, directory, and wildcard mask
|
|
222
|
+
gulp-cli ingest zip-create /evidence/evidence.zip /forensic/host1/*.evtx /forensic/host2 /forensic/notes.txt --overwrite
|
|
223
|
+
|
|
224
|
+
# Use environment variables and ~ in source expressions and output path
|
|
225
|
+
export CASE_ROOT=~/cases/incident-001
|
|
226
|
+
gulp-cli ingest zip-create '$CASE_ROOT/evidence.zip' '$CASE_ROOT/windows/*.evtx' '$CASE_ROOT/network/*' --overwrite
|
|
227
|
+
|
|
228
|
+
# Build ZIP from a text file (one path expression per line)
|
|
229
|
+
cat > /tmp/evidence_paths.txt <<'EOF'
|
|
230
|
+
$CASE_ROOT/windows/*.evtx
|
|
231
|
+
$CASE_ROOT/linux/**/*.log
|
|
232
|
+
~/captures/*.pcap
|
|
233
|
+
EOF
|
|
234
|
+
|
|
235
|
+
gulp-cli ingest zip-create '$CASE_ROOT/evidence.zip' --paths-file /tmp/evidence_paths.txt --overwrite
|
|
236
|
+
|
|
237
|
+
# Then ingest the generated ZIP
|
|
238
|
+
gulp-cli ingest zip incident-001 '$CASE_ROOT/evidence.zip' --wait
|
|
239
|
+
```
|
|
240
|
+
|
|
213
241
|
### Raw Payload Ingestion
|
|
214
242
|
|
|
215
243
|
```bash
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.0.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 0,
|
|
21
|
+
__version__ = version = '1.0.9'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 9)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gb7a79dce7'
|
|
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
-
|
|
5
|
+
import os
|
|
6
|
+
import zipfile
|
|
7
|
+
from glob import glob, has_magic
|
|
6
8
|
from pathlib import Path
|
|
7
9
|
from typing import Any
|
|
8
10
|
|
|
@@ -64,6 +66,140 @@ def _expand_file_patterns(file_patterns: list[str]) -> list[str]:
|
|
|
64
66
|
return unique_files
|
|
65
67
|
|
|
66
68
|
|
|
69
|
+
def _resolve_path(raw_path: str) -> Path:
|
|
70
|
+
expanded = _expand_path_expression(raw_path)
|
|
71
|
+
return Path(expanded).resolve()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _expand_path_expression(raw_path: str) -> str:
|
|
75
|
+
"""Expand shell-style path expressions (env vars and '~')."""
|
|
76
|
+
return os.path.expandvars(os.path.expanduser(raw_path.strip()))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _expand_zip_source_patterns(
|
|
80
|
+
path_patterns: list[str],
|
|
81
|
+
paths_file: str | None,
|
|
82
|
+
) -> list[tuple[Path, Path]]:
|
|
83
|
+
if path_patterns and paths_file is not None:
|
|
84
|
+
raise typer.BadParameter(
|
|
85
|
+
"Provide paths either as arguments or via --paths-file, not both"
|
|
86
|
+
)
|
|
87
|
+
if not path_patterns and paths_file is None:
|
|
88
|
+
raise typer.BadParameter(
|
|
89
|
+
"Provide at least one path argument or --paths-file"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
raw_entries = list(path_patterns)
|
|
93
|
+
if paths_file is not None:
|
|
94
|
+
file_path = _resolve_path(paths_file)
|
|
95
|
+
if not file_path.is_file():
|
|
96
|
+
raise typer.BadParameter(f"Paths file not found: {file_path}")
|
|
97
|
+
raw_entries.extend(
|
|
98
|
+
line.strip()
|
|
99
|
+
for line in file_path.read_text(encoding="utf-8").splitlines()
|
|
100
|
+
if line.strip() and not line.lstrip().startswith("#")
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
expanded_paths: list[tuple[Path, Path]] = []
|
|
104
|
+
seen: set[tuple[Path, Path]] = set()
|
|
105
|
+
for raw_entry in raw_entries:
|
|
106
|
+
expanded_entry = _expand_path_expression(raw_entry)
|
|
107
|
+
if not expanded_entry:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
if has_magic(expanded_entry):
|
|
111
|
+
pattern_path = Path(expanded_entry)
|
|
112
|
+
base_dir = _resolve_glob_base(pattern_path)
|
|
113
|
+
matches = [Path(match).resolve() for match in sorted(glob(expanded_entry, recursive=True))]
|
|
114
|
+
if not matches:
|
|
115
|
+
raise typer.BadParameter(f"Path mask did not match any files: {expanded_entry}")
|
|
116
|
+
|
|
117
|
+
for match in matches:
|
|
118
|
+
item = (match, base_dir)
|
|
119
|
+
if item not in seen:
|
|
120
|
+
seen.add(item)
|
|
121
|
+
expanded_paths.append(item)
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
resolved_path = _resolve_path(expanded_entry)
|
|
125
|
+
if not resolved_path.exists():
|
|
126
|
+
raise typer.BadParameter(f"Path not found: {resolved_path}")
|
|
127
|
+
|
|
128
|
+
base_dir = resolved_path.parent
|
|
129
|
+
item = (resolved_path, base_dir)
|
|
130
|
+
if item not in seen:
|
|
131
|
+
seen.add(item)
|
|
132
|
+
expanded_paths.append(item)
|
|
133
|
+
|
|
134
|
+
return expanded_paths
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _resolve_glob_base(pattern_path: Path) -> Path:
|
|
138
|
+
parts = pattern_path.parts
|
|
139
|
+
if pattern_path.is_absolute():
|
|
140
|
+
base_dir = Path(pattern_path.anchor)
|
|
141
|
+
part_index = 1
|
|
142
|
+
else:
|
|
143
|
+
base_dir = Path.cwd()
|
|
144
|
+
part_index = 0
|
|
145
|
+
|
|
146
|
+
for part in parts[part_index:]:
|
|
147
|
+
if has_magic(part):
|
|
148
|
+
break
|
|
149
|
+
base_dir /= part
|
|
150
|
+
|
|
151
|
+
return base_dir.resolve()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _build_zip_from_sources(output_zip: Path, sources: list[tuple[Path, Path]]) -> tuple[int, list[str]]:
|
|
155
|
+
output_zip.parent.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
|
|
157
|
+
archived_entries: list[str] = []
|
|
158
|
+
archived_count = 0
|
|
159
|
+
seen_archive_entries: set[str] = set()
|
|
160
|
+
seen_source_files: set[Path] = set()
|
|
161
|
+
|
|
162
|
+
def _record_directory(archive_name_str: str, archive: zipfile.ZipFile) -> None:
|
|
163
|
+
entry_name = f"{archive_name_str}/"
|
|
164
|
+
if entry_name in seen_archive_entries:
|
|
165
|
+
return
|
|
166
|
+
archive.writestr(entry_name, "")
|
|
167
|
+
seen_archive_entries.add(entry_name)
|
|
168
|
+
archived_entries.append(entry_name)
|
|
169
|
+
|
|
170
|
+
def _record_file(source_path: Path, archive_name_str: str, archive: zipfile.ZipFile) -> None:
|
|
171
|
+
nonlocal archived_count
|
|
172
|
+
if archive_name_str in seen_archive_entries or source_path in seen_source_files:
|
|
173
|
+
return
|
|
174
|
+
archive.write(source_path, arcname=archive_name_str)
|
|
175
|
+
seen_archive_entries.add(archive_name_str)
|
|
176
|
+
seen_source_files.add(source_path)
|
|
177
|
+
archived_entries.append(archive_name_str)
|
|
178
|
+
archived_count += 1
|
|
179
|
+
|
|
180
|
+
with zipfile.ZipFile(output_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as archive:
|
|
181
|
+
for source_path, base_dir in sources:
|
|
182
|
+
archive_root = source_path.relative_to(base_dir)
|
|
183
|
+
|
|
184
|
+
if source_path.is_file():
|
|
185
|
+
_record_file(source_path, archive_root.as_posix(), archive)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
child_paths = sorted(source_path.rglob("*"))
|
|
189
|
+
if not child_paths:
|
|
190
|
+
_record_directory(archive_root.as_posix(), archive)
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
for current_path in child_paths:
|
|
194
|
+
archive_name_str = current_path.relative_to(base_dir).as_posix()
|
|
195
|
+
if current_path.is_dir():
|
|
196
|
+
_record_directory(archive_name_str, archive)
|
|
197
|
+
continue
|
|
198
|
+
_record_file(current_path, archive_name_str, archive)
|
|
199
|
+
|
|
200
|
+
return archived_count, archived_entries
|
|
201
|
+
|
|
202
|
+
|
|
67
203
|
async def _wait_for_stats_create(
|
|
68
204
|
client: Any,
|
|
69
205
|
req_ids: list[str],
|
|
@@ -600,7 +736,10 @@ def ingest_file_to_source(
|
|
|
600
736
|
@app.command("zip")
|
|
601
737
|
def ingest_zip(
|
|
602
738
|
operation_id: str,
|
|
603
|
-
zip_file: str
|
|
739
|
+
zip_file: str = typer.Argument(
|
|
740
|
+
...,
|
|
741
|
+
help="Path to a ZIP file which must contain a `metadata.json` in the root, describing the content as specified in gulp's `ingest_zip` docs.",
|
|
742
|
+
),
|
|
604
743
|
context_name: str = typer.Option("sdk_context", "--context-name"),
|
|
605
744
|
flt: str | None = typer.Option(None, "--flt", help="JSON object for GulpIngestionFilter"),
|
|
606
745
|
reset_operation: bool = typer.Option(
|
|
@@ -691,6 +830,52 @@ def ingest_zip(
|
|
|
691
830
|
asyncio.run(_run())
|
|
692
831
|
|
|
693
832
|
|
|
833
|
+
@app.command("zip-create")
|
|
834
|
+
def ingest_zip_create(
|
|
835
|
+
output_zip: str = typer.Argument(
|
|
836
|
+
...,
|
|
837
|
+
help="Path to the ZIP file to create (supports $VARS and ~)",
|
|
838
|
+
),
|
|
839
|
+
path_patterns: list[str] = typer.Argument(
|
|
840
|
+
None,
|
|
841
|
+
help="One or more files, directories, or glob patterns to archive (supports $VARS and ~)",
|
|
842
|
+
),
|
|
843
|
+
paths_file: str | None = typer.Option(
|
|
844
|
+
None,
|
|
845
|
+
"--paths-file",
|
|
846
|
+
help="Text file with one file, directory, or glob pattern per line (supports $VARS and ~ per line)",
|
|
847
|
+
),
|
|
848
|
+
overwrite: bool = typer.Option(
|
|
849
|
+
False,
|
|
850
|
+
"--overwrite",
|
|
851
|
+
help="Overwrite the ZIP file if it already exists",
|
|
852
|
+
),
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Create a ZIP archive from files, directories, or glob patterns."""
|
|
855
|
+
|
|
856
|
+
output_path = _resolve_path(output_zip)
|
|
857
|
+
source_paths = _expand_zip_source_patterns(path_patterns or [], paths_file)
|
|
858
|
+
|
|
859
|
+
if output_path.exists() and not overwrite:
|
|
860
|
+
raise typer.BadParameter(
|
|
861
|
+
f"Output ZIP already exists: {output_path}. Use --overwrite to replace it"
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
archived_count, archived_entries = _build_zip_from_sources(output_path, source_paths)
|
|
865
|
+
print_result(
|
|
866
|
+
{
|
|
867
|
+
"zip_file": str(output_path),
|
|
868
|
+
"sources": [str(path) for path, _ in source_paths],
|
|
869
|
+
"files_archived": archived_count,
|
|
870
|
+
"entries": archived_entries,
|
|
871
|
+
},
|
|
872
|
+
formatter=lambda data: console.print(
|
|
873
|
+
f"Created ZIP {data['zip_file']} from {len(data['sources'])} source"
|
|
874
|
+
f"{'s' if len(data['sources']) != 1 else ''} with {data['files_archived']} file(s)."
|
|
875
|
+
),
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
|
|
694
879
|
@app.command("raw")
|
|
695
880
|
def ingest_raw(
|
|
696
881
|
operation_id: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|