gulp-cli 1.0.8__tar.gz → 1.1.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 (56) hide show
  1. gulp_cli-1.1.1/.github/workflows/portable-bundles.yml +127 -0
  2. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/PKG-INFO +19 -2
  3. gulp_cli-1.0.8/src/gulp_cli.egg-info/PKG-INFO → gulp_cli-1.1.1/README.md +16 -13
  4. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/command-reference.md +44 -1
  5. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/examples.md +28 -0
  6. gulp_cli-1.1.1/docs/portable.md +76 -0
  7. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/troubleshooting-cli.md +91 -4
  8. gulp_cli-1.1.1/gulp-cli.spec +83 -0
  9. gulp_cli-1.1.1/portable/launch-linux.sh +8 -0
  10. gulp_cli-1.1.1/portable/launch-macos.sh +8 -0
  11. gulp_cli-1.1.1/portable/launch-windows.bat +9 -0
  12. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/pyproject.toml +4 -6
  13. gulp_cli-1.1.1/src/gulp_cli/__main__.py +28 -0
  14. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/_version.py +3 -3
  15. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/cli.py +18 -2
  16. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/context.py +2 -6
  17. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/ingest.py +339 -45
  18. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/source.py +56 -16
  19. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/config.py +78 -16
  20. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/extensions.py +5 -6
  21. gulp_cli-1.0.8/README.md → gulp_cli-1.1.1/src/gulp_cli.egg-info/PKG-INFO +30 -1
  22. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli.egg-info/SOURCES.txt +6 -0
  23. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli.egg-info/requires.txt +3 -0
  24. gulp_cli-1.0.8/src/gulp_cli/__main__.py +0 -11
  25. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/.github/workflows/python-package.yml +0 -0
  26. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/.gitignore +0 -0
  27. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/extensions.md +0 -0
  28. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/getting-started.md +0 -0
  29. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/docs/resource-management.md +0 -0
  30. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/setup.cfg +0 -0
  31. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/__init__.py +0 -0
  32. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/client.py +0 -0
  33. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/__init__.py +0 -0
  34. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/acl.py +0 -0
  35. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/auth.py +0 -0
  36. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/collab.py +0 -0
  37. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/db.py +0 -0
  38. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/enhance_map.py +0 -0
  39. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/glyph.py +0 -0
  40. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/mapping.py +0 -0
  41. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/operations.py +0 -0
  42. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/plugin.py +0 -0
  43. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/query.py +0 -0
  44. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/stats.py +0 -0
  45. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/storage.py +0 -0
  46. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/user_group.py +0 -0
  47. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/commands/users.py +0 -0
  48. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/extension/__init__.py +0 -0
  49. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/extension/query_sigma_zip.py +0 -0
  50. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/extension/story.py +0 -0
  51. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/extension_helpers.py +0 -0
  52. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/output.py +0 -0
  53. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli/utils.py +0 -0
  54. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli.egg-info/dependency_links.txt +0 -0
  55. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli.egg-info/entry_points.txt +0 -0
  56. {gulp_cli-1.0.8 → gulp_cli-1.1.1}/src/gulp_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,127 @@
1
+ name: Portable bundles
2
+
3
+ "on":
4
+ push:
5
+ tags: ['v*', 'test-v*']
6
+ workflow_dispatch: {}
7
+
8
+ jobs:
9
+ build-portable:
10
+ name: Build portable bundle (${{ matrix.bundle_name }})
11
+ runs-on: ${{ matrix.os }}
12
+ timeout-minutes: 90
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ include:
17
+ - os: ubuntu-latest
18
+ bundle_name: gulp-cli-portable-linux-x64
19
+ launcher_path: portable/launch-linux.sh
20
+ - os: windows-latest
21
+ bundle_name: gulp-cli-portable-windows-x64
22
+ launcher_path: portable/launch-windows.bat
23
+ - os: macos-14
24
+ bundle_name: gulp-cli-portable-macos-arm64
25
+ launcher_path: portable/launch-macos.sh
26
+
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: '3.13'
35
+
36
+ - name: Install dependencies
37
+ run: |
38
+ python -m pip install --upgrade pip
39
+ python -m pip install . pyinstaller
40
+
41
+ - name: Build executable
42
+ run: |
43
+ pyinstaller --noconfirm --clean gulp-cli.spec
44
+
45
+ - name: Assemble portable layout
46
+ shell: python
47
+ env:
48
+ BUNDLE_NAME: ${{ matrix.bundle_name }}
49
+ LAUNCHER_PATH: ${{ matrix.launcher_path }}
50
+ run: |
51
+ import os
52
+ import shutil
53
+ from pathlib import Path
54
+
55
+ dist_dir = Path("dist")
56
+ source_dir = dist_dir / "gulp-cli"
57
+ bundle_dir = dist_dir / os.environ["BUNDLE_NAME"]
58
+ launcher_path = Path(os.environ["LAUNCHER_PATH"])
59
+
60
+ if bundle_dir.exists():
61
+ shutil.rmtree(bundle_dir)
62
+
63
+ shutil.copytree(source_dir, bundle_dir)
64
+ (bundle_dir / "data" / "extension").mkdir(parents=True, exist_ok=True)
65
+ shutil.copy2(launcher_path, bundle_dir / launcher_path.name)
66
+ shutil.copy2("README.md", bundle_dir / "README.md")
67
+
68
+ - name: Upload portable artifact
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: ${{ matrix.bundle_name }}
72
+ path: dist/${{ matrix.bundle_name }}
73
+ if-no-files-found: error
74
+
75
+ build-portable-macos-x64:
76
+ name: Build portable bundle (gulp-cli-portable-macos-x64)
77
+ if: github.event_name == 'workflow_dispatch'
78
+ runs-on: macos-15-intel
79
+ timeout-minutes: 90
80
+
81
+ steps:
82
+ - name: Checkout repository
83
+ uses: actions/checkout@v4
84
+
85
+ - name: Set up Python
86
+ uses: actions/setup-python@v5
87
+ with:
88
+ python-version: '3.13'
89
+
90
+ - name: Install dependencies
91
+ run: |
92
+ python -m pip install --upgrade pip
93
+ python -m pip install . pyinstaller
94
+
95
+ - name: Build executable
96
+ run: |
97
+ pyinstaller --noconfirm --clean gulp-cli.spec
98
+
99
+ - name: Assemble portable layout
100
+ shell: python
101
+ env:
102
+ BUNDLE_NAME: gulp-cli-portable-macos-x64
103
+ LAUNCHER_PATH: portable/launch-macos.sh
104
+ run: |
105
+ import os
106
+ import shutil
107
+ from pathlib import Path
108
+
109
+ dist_dir = Path("dist")
110
+ source_dir = dist_dir / "gulp-cli"
111
+ bundle_dir = dist_dir / os.environ["BUNDLE_NAME"]
112
+ launcher_path = Path(os.environ["LAUNCHER_PATH"])
113
+
114
+ if bundle_dir.exists():
115
+ shutil.rmtree(bundle_dir)
116
+
117
+ shutil.copytree(source_dir, bundle_dir)
118
+ (bundle_dir / "data" / "extension").mkdir(parents=True, exist_ok=True)
119
+ shutil.copy2(launcher_path, bundle_dir / launcher_path.name)
120
+ shutil.copy2("README.md", bundle_dir / "README.md")
121
+
122
+ - name: Upload portable artifact
123
+ uses: actions/upload-artifact@v4
124
+ with:
125
+ name: gulp-cli-portable-macos-x64
126
+ path: dist/gulp-cli-portable-macos-x64
127
+ if-no-files-found: error
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gulp-cli
3
- Version: 1.0.8
3
+ Version: 1.1.1
4
4
  Summary: Command-line client for gULP
5
5
  Author-email: Mentat <info@mentat.is>
6
6
  Requires-Python: >=3.12
@@ -9,6 +9,8 @@ Requires-Dist: typer==0.23.1
9
9
  Requires-Dist: rich==15.0.0
10
10
  Requires-Dist: click==8.3.3
11
11
  Requires-Dist: gulp-sdk
12
+ Provides-Extra: portable
13
+ Requires-Dist: pyinstaller<7,>=6.11; extra == "portable"
12
14
 
13
15
  # 🚀 gulp-cli
14
16
 
@@ -41,6 +43,9 @@ All with **beautiful terminal output**, **automatic tab completion**, and **asyn
41
43
  # from pip
42
44
  pip install gulp-cli
43
45
 
46
+ # or install local portable-build tooling
47
+ pip install 'gulp-cli[portable]'
48
+
44
49
  # or, for the latest development version:
45
50
  python3 -m venv ./.venv
46
51
  source ./.venv/bin/activate
@@ -51,10 +56,20 @@ cd gulp-cli && pip install -e .
51
56
  gulp-cli --help
52
57
  ```
53
58
 
59
+ ### Portable Bundles
60
+
61
+ For offline use from a USB stick, prefer the OS-specific portable bundles built with PyInstaller instead of `pip install`.
62
+
63
+ - Each target OS needs its own bundle: Linux, Windows, macOS Intel, macOS Apple Silicon.
64
+ - Portable bundles keep config and external extensions in a local `data/` directory next to the executable.
65
+ - You can override that location with `GULP_CLI_HOME` or `--config-dir`.
66
+
67
+ See **[Portable Usage](./docs/portable.md)** for the layout, local build command, and CI artifact details.
68
+
54
69
  ### Basic Usage
55
70
 
56
71
  > for the cli to work, set `"ws_ignore_missing": true` (should be default in the v1.6.51 backend, though ...) in your `gulp_cfg.json` to prevent the backend from halting operations when the CLI disconnects its websocket after sending an async request!
57
-
72
+
58
73
  ```bash
59
74
  # Login to your gULP instance
60
75
  gulp-cli auth login --url http://localhost:8080 --username admin --password admin
@@ -71,6 +86,7 @@ gulp-cli ingest file my_operation win_evtx 'samples/win_evtx/*.evtx'
71
86
  # Query documents
72
87
  gulp-cli query raw my_operation --q '{"query":{"match_all":{}}}'
73
88
  ```
89
+
74
90
  ---
75
91
 
76
92
  ## 📚 Documentation
@@ -78,6 +94,7 @@ gulp-cli query raw my_operation --q '{"query":{"match_all":{}}}'
78
94
  - **[Getting Started Guide](./docs/getting-started.md)** — auth, first operation, first ingest
79
95
  - **[Command Reference](./docs/command-reference.md)** — all available commands and options
80
96
  - **[Extensions Guide](./docs/extensions.md)** — dynamic extension loading and custom command contract
97
+ - **[Portable Usage](./docs/portable.md)** — offline bundles and USB-friendly layout
81
98
  - **[Resource Management Commands](./docs/resource-management.md)** — context, source, plugin, mapping, enhance-map, glyph
82
99
  - **[Practical Examples](./docs/examples.md)** — real-world workflows and recipes
83
100
  - **[Troubleshooting](./docs/troubleshooting-cli.md)** — common issues and solutions
@@ -1,15 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: gulp-cli
3
- Version: 1.0.8
4
- Summary: Command-line client for gULP
5
- Author-email: Mentat <info@mentat.is>
6
- Requires-Python: >=3.12
7
- Description-Content-Type: text/markdown
8
- Requires-Dist: typer==0.23.1
9
- Requires-Dist: rich==15.0.0
10
- Requires-Dist: click==8.3.3
11
- Requires-Dist: gulp-sdk
12
-
13
1
  # 🚀 gulp-cli
14
2
 
15
3
  **A modern, powerful command-line interface for gULP** — manage forensic document ingestion, querying, enrichment, and collaboration entirely from your terminal.
@@ -41,6 +29,9 @@ All with **beautiful terminal output**, **automatic tab completion**, and **asyn
41
29
  # from pip
42
30
  pip install gulp-cli
43
31
 
32
+ # or install local portable-build tooling
33
+ pip install 'gulp-cli[portable]'
34
+
44
35
  # or, for the latest development version:
45
36
  python3 -m venv ./.venv
46
37
  source ./.venv/bin/activate
@@ -51,10 +42,20 @@ cd gulp-cli && pip install -e .
51
42
  gulp-cli --help
52
43
  ```
53
44
 
45
+ ### Portable Bundles
46
+
47
+ For offline use from a USB stick, prefer the OS-specific portable bundles built with PyInstaller instead of `pip install`.
48
+
49
+ - Each target OS needs its own bundle: Linux, Windows, macOS Intel, macOS Apple Silicon.
50
+ - Portable bundles keep config and external extensions in a local `data/` directory next to the executable.
51
+ - You can override that location with `GULP_CLI_HOME` or `--config-dir`.
52
+
53
+ See **[Portable Usage](./docs/portable.md)** for the layout, local build command, and CI artifact details.
54
+
54
55
  ### Basic Usage
55
56
 
56
57
  > for the cli to work, set `"ws_ignore_missing": true` (should be default in the v1.6.51 backend, though ...) in your `gulp_cfg.json` to prevent the backend from halting operations when the CLI disconnects its websocket after sending an async request!
57
-
58
+
58
59
  ```bash
59
60
  # Login to your gULP instance
60
61
  gulp-cli auth login --url http://localhost:8080 --username admin --password admin
@@ -71,6 +72,7 @@ gulp-cli ingest file my_operation win_evtx 'samples/win_evtx/*.evtx'
71
72
  # Query documents
72
73
  gulp-cli query raw my_operation --q '{"query":{"match_all":{}}}'
73
74
  ```
75
+
74
76
  ---
75
77
 
76
78
  ## 📚 Documentation
@@ -78,6 +80,7 @@ gulp-cli query raw my_operation --q '{"query":{"match_all":{}}}'
78
80
  - **[Getting Started Guide](./docs/getting-started.md)** — auth, first operation, first ingest
79
81
  - **[Command Reference](./docs/command-reference.md)** — all available commands and options
80
82
  - **[Extensions Guide](./docs/extensions.md)** — dynamic extension loading and custom command contract
83
+ - **[Portable Usage](./docs/portable.md)** — offline bundles and USB-friendly layout
81
84
  - **[Resource Management Commands](./docs/resource-management.md)** — context, source, plugin, mapping, enhance-map, glyph
82
85
  - **[Practical Examples](./docs/examples.md)** — real-world workflows and recipes
83
86
  - **[Troubleshooting](./docs/troubleshooting-cli.md)** — common issues and solutions
@@ -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
@@ -0,0 +1,76 @@
1
+ # Portable gulp-cli
2
+
3
+ Portable bundles are the recommended way to run `gulp-cli` from removable media or on machines without internet access.
4
+
5
+ ## What portable means here
6
+
7
+ - You build one bundle per target OS and architecture.
8
+ - The bundle already contains Python, `gulp-cli`, `gulp-sdk`, and runtime dependencies.
9
+ - User state lives in a local `data/` folder instead of the user profile.
10
+
11
+ There is no single executable that runs unchanged on Linux, Windows, and macOS. Build and ship one artifact for each platform:
12
+
13
+ - Linux `x86_64`
14
+ - Windows `x86_64`
15
+ - macOS `x86_64`
16
+ - macOS `arm64`
17
+
18
+ ## Portable layout
19
+
20
+ The CI workflow produces a directory like this:
21
+
22
+ ```text
23
+ gulp-cli-portable-<platform>/
24
+ gulp-cli[.exe]
25
+ _internal/
26
+ launch-linux.sh | launch-macos.sh | launch-windows.bat
27
+ data/
28
+ extension/
29
+ README.md
30
+ ```
31
+
32
+ `gulp-cli` automatically uses `./data` when running from a frozen bundle and that directory exists.
33
+
34
+ ## Runtime overrides
35
+
36
+ The CLI supports both of these overrides:
37
+
38
+ ```bash
39
+ GULP_CLI_HOME=/path/to/portable-data gulp-cli auth whoami
40
+ gulp-cli --config-dir /path/to/portable-data auth whoami
41
+ ```
42
+
43
+ The overridden directory stores:
44
+
45
+ - `config.json`
46
+ - `extension/`
47
+
48
+ ## Local build
49
+
50
+ Build a portable bundle locally from the repo root:
51
+
52
+ ```bash
53
+ python -m pip install --upgrade pip
54
+ python -m pip install '.[portable]'
55
+ pyinstaller --noconfirm --clean gulp-cli.spec
56
+ ```
57
+
58
+ The output is written under `dist/gulp-cli/`.
59
+
60
+ ## Notes for offline USB use
61
+
62
+ - Build bundles on CI or on a machine with internet access first.
63
+ - Copy the resulting platform-specific bundle folders to the USB stick.
64
+ - The target machine does not need Python or internet access.
65
+ - The target machine still needs network connectivity to the gULP server unless that server is local.
66
+
67
+ ## GitHub Actions
68
+
69
+ The repository includes a matrix workflow that builds portable artifacts for:
70
+
71
+ - Linux `x86_64`
72
+ - Windows `x86_64`
73
+ - macOS `x86_64`
74
+ - macOS `arm64`
75
+
76
+ Artifacts are uploaded from `.github/workflows/portable-bundles.yml`.