gulp-cli 1.1.4__tar.gz → 1.1.6__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 (66) hide show
  1. {gulp_cli-1.1.4/src/gulp_cli.egg-info → gulp_cli-1.1.6}/PKG-INFO +2 -1
  2. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/README.md +1 -0
  3. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/command-reference.md +40 -12
  4. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/examples.md +14 -4
  5. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/_version.py +3 -3
  6. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/cli.py +14 -16
  7. gulp_cli-1.1.6/src/gulp_cli/commands/enrich.py +187 -0
  8. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/ingest.py +696 -138
  9. gulp_cli-1.1.6/src/gulp_cli/commands/utility.py +21 -0
  10. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/config.py +4 -0
  11. gulp_cli-1.1.6/src/gulp_cli/version.py +20 -0
  12. {gulp_cli-1.1.4 → gulp_cli-1.1.6/src/gulp_cli.egg-info}/PKG-INFO +2 -1
  13. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli.egg-info/SOURCES.txt +9 -1
  14. gulp_cli-1.1.6/tests/test_enrich_live.py +302 -0
  15. gulp_cli-1.1.6/tests/test_ingest_paths_file.py +78 -0
  16. gulp_cli-1.1.6/tests/test_ingest_wait_log.py +258 -0
  17. gulp_cli-1.1.6/tests/test_version.py +48 -0
  18. gulp_cli-1.1.6/tests/test_zip_create.py +272 -0
  19. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/.github/workflows/portable-bundles.yml +0 -0
  20. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/.github/workflows/python-package.yml +0 -0
  21. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/.gitignore +0 -0
  22. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/GULP_DUAL_LICENSE.txt +0 -0
  23. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/LICENSE.AGPL-3.0.txt +0 -0
  24. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/LICENSE.md +0 -0
  25. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/extensions.md +0 -0
  26. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/getting-started.md +0 -0
  27. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/portable.md +0 -0
  28. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/resource-management.md +0 -0
  29. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/docs/troubleshooting-cli.md +0 -0
  30. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/gulp-cli.spec +0 -0
  31. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/portable/launch-linux.sh +0 -0
  32. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/portable/launch-macos.sh +0 -0
  33. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/portable/launch-windows.bat +0 -0
  34. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/pyproject.toml +0 -0
  35. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/setup.cfg +0 -0
  36. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/__init__.py +0 -0
  37. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/__main__.py +0 -0
  38. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/client.py +0 -0
  39. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/__init__.py +0 -0
  40. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/acl.py +0 -0
  41. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/auth.py +0 -0
  42. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/collab.py +0 -0
  43. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/context.py +0 -0
  44. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/db.py +0 -0
  45. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/enhance_map.py +0 -0
  46. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/glyph.py +0 -0
  47. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/mapping.py +0 -0
  48. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/operations.py +0 -0
  49. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/plugin.py +0 -0
  50. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/query.py +0 -0
  51. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/source.py +0 -0
  52. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/stats.py +0 -0
  53. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/storage.py +0 -0
  54. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/user_group.py +0 -0
  55. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/commands/users.py +0 -0
  56. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/extension/__init__.py +0 -0
  57. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/extension/query_sigma_zip.py +0 -0
  58. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/extension/story.py +0 -0
  59. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/extension_helpers.py +0 -0
  60. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/extensions.py +0 -0
  61. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/output.py +0 -0
  62. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli/utils.py +0 -0
  63. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli.egg-info/dependency_links.txt +0 -0
  64. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli.egg-info/entry_points.txt +0 -0
  65. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli.egg-info/requires.txt +0 -0
  66. {gulp_cli-1.1.4 → gulp_cli-1.1.6}/src/gulp_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gulp-cli
3
- Version: 1.1.4
3
+ Version: 1.1.6
4
4
  Summary: Command-line client for gULP
5
5
  Author-email: Mentat <info@mentat.is>
6
6
  Requires-Python: >=3.12
@@ -57,6 +57,7 @@ cd gulp-cli && pip install -e .
57
57
 
58
58
  # Verify installation
59
59
  gulp-cli --help
60
+ gulp-version
60
61
  ```
61
62
 
62
63
  ### Portable Bundles
@@ -40,6 +40,7 @@ cd gulp-cli && pip install -e .
40
40
 
41
41
  # Verify installation
42
42
  gulp-cli --help
43
+ gulp-version
43
44
  ```
44
45
 
45
46
  ### Portable Bundles
@@ -25,6 +25,18 @@ gulp-cli --verbose --as-user USER_NAME <group> <command> ...
25
25
 
26
26
  ## Core Commands
27
27
 
28
+ ### `utility`
29
+
30
+ Utility commands for gULP server metadata and similar endpoints.
31
+
32
+ ```bash
33
+ gulp-cli utility gulp-version
34
+ ```
35
+
36
+ `utility gulp-version` prints only the gULP version and uses the saved CLI session, so run `gulp-cli auth login` first.
37
+
38
+ ---
39
+
28
40
  ### Authentication (`auth`)
29
41
 
30
42
  #### `login`
@@ -437,20 +449,21 @@ gulp-cli operation revoke-user my_investigation alice
437
449
  Ingest one or more files into an operation.
438
450
 
439
451
  ```bash
440
- gulp-cli ingest file OPERATION_ID PLUGIN FILE [FILE...] [OPTIONS]
452
+ gulp-cli ingest file OPERATION_ID PLUGIN [FILE...] [OPTIONS]
441
453
  ```
442
454
 
443
455
  **Arguments:**
444
456
 
445
457
  - `OPERATION_ID` — Target operation
446
458
  - `PLUGIN` — Plugin name (e.g., `win_evtx`, `syslog`, `csv`)
447
- - `FILE` — File path or glob pattern (multiple allowed)
459
+ - `FILE` — File path or glob pattern (multiple allowed; optional when `--paths-file` is set)
448
460
 
449
461
  **Options:**
450
462
 
451
- - `--context-name TEXT` — Context name used for source grouping (default: `sdk_context`)
463
+ - `--context TEXT` — Context for ingestion, specify a name to create a new context if it doesn't exist, or an existing `context_id` (default: `sdk_context`)
452
464
  - `--plugin-params TEXT` — JSON string with plugin parameters
453
465
  - `--flt TEXT` — JSON object for `GulpIngestionFilter`
466
+ - `--paths-file PATH` — Text file with one file or glob pattern per line. When set, positional `FILE` arguments are optional and ignored if also provided.
454
467
  - `--reset-operation` — Delete and recreate the operation before ingest starts
455
468
  - `--create-operation` — Create operation automatically when it does not exist
456
469
  - `--preview` — Run preview-only ingestion (no persistence)
@@ -474,6 +487,9 @@ gulp-cli ingest file my_op win_evtx 'samples/win_evtx/*.evtx'
474
487
  # Multiple patterns
475
488
  gulp-cli ingest file my_op win_evtx '**/*.evtx' '/logs/*.json'
476
489
 
490
+ # Paths file
491
+ gulp-cli ingest file my_op win_evtx --paths-file /tmp/file_paths.txt
492
+
477
493
  # With plugin parameters
478
494
  gulp-cli ingest file my_op csv file.csv --plugin-params '{"delimiter":";","encoding":"utf-8"}'
479
495
 
@@ -494,18 +510,20 @@ gulp-cli ingest file my_op win_evtx 'samples/win_evtx/*.evtx' --wait
494
510
  Add files to an existing source.
495
511
 
496
512
  ```bash
497
- gulp-cli ingest file-to-source SOURCE_ID FILE [FILE...] [OPTIONS]
513
+ gulp-cli ingest file-to-source SOURCE_ID [FILE...] [OPTIONS]
498
514
  ```
499
515
 
500
516
  **Arguments:**
501
517
 
502
518
  - `SOURCE_ID` — Source ID
503
- - `FILE` — File path or glob pattern (multiple allowed)
519
+ - `FILE` — File path or glob pattern (multiple allowed; optional when `--paths-file` is set)
504
520
 
505
521
  **Options:**
506
522
 
507
- - `--plugin-params TEXT` — JSON object for plugin_params (overrides source defaults)
523
+ - `--plugin TEXT` — Override the plugin associated with the source (requires `--plugin-params`)
524
+ - `--plugin-params TEXT` — JSON object for plugin_params (overrides source defaults; required when `--plugin` is set, `{}` is allowed)
508
525
  - `--flt TEXT` — JSON object for GulpIngestionFilter
526
+ - `--paths-file PATH` — Text file with one file or glob pattern per line. When set, positional `FILE` arguments are optional and ignored if also provided.
509
527
  - `--wait` — Wait for completion with progress
510
528
  - `--show-per-file-progress / --no-show-per-file-progress` — Show one line per completed file request in `--wait` mode (default: on)
511
529
  - `--timeout INTEGER` — Timeout in seconds for `--wait` mode
@@ -516,6 +534,8 @@ gulp-cli ingest file-to-source SOURCE_ID FILE [FILE...] [OPTIONS]
516
534
  ```bash
517
535
  gulp-cli ingest file-to-source source123 /new/System.evtx --wait
518
536
  gulp-cli ingest file-to-source source123 '/new/*.evtx'
537
+ gulp-cli ingest file-to-source source123 --paths-file /tmp/file_paths.txt
538
+ gulp-cli ingest file-to-source source123 /new/events.json --plugin json --plugin-params '{"mapping_parameters": {}}'
519
539
  ```
520
540
 
521
541
  ---
@@ -537,7 +557,7 @@ gulp-cli ingest zip OPERATION_ID ZIP_FILE [OPTIONS]
537
557
 
538
558
  **Options:**
539
559
 
540
- - `--context-name TEXT` — Context name used for source grouping (default: `sdk_context`)
560
+ - `--context TEXT` — Context for ingestion, specify a name to create a new context if it doesn't exist, or an existing `context_id` (default: `sdk_context`)
541
561
  - `--flt TEXT` — JSON object for `GulpIngestionFilter`
542
562
  - `--create-operation` — Create operation automatically when it does not exist
543
563
  - `--wait` — Wait for completion
@@ -576,7 +596,12 @@ gulp-cli ingest zip-create OUTPUT_ZIP [PATH_PATTERN...] [OPTIONS]
576
596
  **Options:**
577
597
 
578
598
  - `--paths-file TEXT` — Text file with one source path expression per line (supports environment variables and `~` per line)
579
- - `--overwrite` — Overwrite output ZIP if it already exists
599
+ - `--no-overwrite` — Do not overwrite output ZIP if it already exists (default: overwrite enabled)
600
+ - `--no-preserve-path` — Do not preserve the source path hierarchy inside archive entries (default: preserve path enabled)
601
+ - `--split-size TEXT` — Create multipart ZIP volumes at this size (bytes, `kb`, `mb`, `gb`; default: disabled)
602
+
603
+ When `--split-size` is set, `zip-create` writes multipart ZIP volumes such as `evidence.zip.001`, `evidence.zip.002`, etc. Extract those volumes with `7z` or another tool that supports split ZIP files.
604
+ When `--paths-file` is set, positional `PATH_PATTERN` values are ignored. Without `--paths-file`, positional `PATH_PATTERN` values are used.
580
605
 
581
606
  **Examples:**
582
607
 
@@ -585,10 +610,10 @@ gulp-cli ingest zip-create OUTPUT_ZIP [PATH_PATTERN...] [OPTIONS]
585
610
  gulp-cli ingest zip-create /tmp/evidence.zip /data/host1/*.evtx /data/host2 /data/readme.txt
586
611
 
587
612
  # Use environment variables and home shortcuts
588
- gulp-cli ingest zip-create '$CASE_DIR/evidence.zip' '$CASE_DIR/raw/*.json' '~/pcaps/*.pcap' --overwrite
613
+ gulp-cli ingest zip-create '$CASE_DIR/evidence.zip' '$CASE_DIR/raw/*.json' '~/pcaps/*.pcap'
589
614
 
590
615
  # Read source path expressions from a file
591
- gulp-cli ingest zip-create /tmp/evidence.zip --paths-file /tmp/evidence_paths.txt --overwrite
616
+ gulp-cli ingest zip-create /tmp/evidence.zip --paths-file /tmp/evidence_paths.txt
592
617
  ```
593
618
 
594
619
  ---
@@ -1298,6 +1323,7 @@ gulp-cli enrich documents OPERATION_ID [OPTIONS]
1298
1323
  - `--plugin-params TEXT` — Plugin parameters JSON
1299
1324
  - `--flt TEXT` — Filter JSON
1300
1325
  - `--wait` — Wait for completion
1326
+ - `--fields TEXT` — { "field": "value" } pairs to set on enriched documents (JSON string): if "value" is `null` the enrichment is made on field's value in the document. either, enriched "value" is used.
1301
1327
 
1302
1328
  **Examples:**
1303
1329
 
@@ -1305,7 +1331,8 @@ gulp-cli enrich documents OPERATION_ID [OPTIONS]
1305
1331
  gulp-cli enrich documents my_op \
1306
1332
  --plugin my_enricher \
1307
1333
  --plugin-params '{"api_key":"..."}' \
1308
- --flt '{"enriched":false}'
1334
+ --flt '{"operation_ids":["my_op"],"source_ids":["security"]}' \
1335
+ --fields '{"ip.destination": null}'
1309
1336
  ```
1310
1337
 
1311
1338
  ---
@@ -1327,12 +1354,13 @@ gulp-cli enrich single-id OPERATION_ID DOCUMENT_ID [OPTIONS]
1327
1354
 
1328
1355
  - `--plugin TEXT` — Plugin name (required)
1329
1356
  - `--plugin-params TEXT` — Plugin parameters JSON
1357
+ - `--fields TEXT` — { "field": "value" } pairs to set on enriched documents (JSON string): if "value" is `null` the enrichment is made on field's value in the document. either, enriched "value" is used.
1330
1358
  - `--wait` — Wait for completion
1331
1359
 
1332
1360
  **Examples:**
1333
1361
 
1334
1362
  ```bash
1335
- gulp-cli enrich single-id my_op doc123 --plugin my_enricher
1363
+ gulp-cli enrich single-id my_op doc123 --plugin my_enricher --fields '{"host.os": null}' --wait
1336
1364
  ```
1337
1365
 
1338
1366
  ---
@@ -45,6 +45,7 @@
45
45
  - [Run Rule on Specific Source](#run-rule-on-specific-source)
46
46
  - [Multiple Sigma Rules in Batch](#multiple-sigma-rules-in-batch)
47
47
  - [Enrichment \& Tagging Workflows](#enrichment--tagging-workflows)
48
+ - [use whois to enrich IP addresses with domain information](#use-whois-to-enrich-ip-addresses-with-domain-information)
48
49
  - [Tag Suspicious Events](#tag-suspicious-events)
49
50
  - [Apply Threat Level Classification](#apply-threat-level-classification)
50
51
  - [Track Analysis Progress](#track-analysis-progress)
@@ -174,8 +175,7 @@ gulp-cli ingest file incident-001 csv /data/access_log.csv \
174
175
  --plugin-params '{"delimiter":";","encoding":"iso-8859-1","has_header":true}'
175
176
 
176
177
  # pass mapping directly without using a mapping file
177
- gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters": { "mappings": { "test
178
- _mapping": { "fields": { "Created0x10": { "ecs": [ "@timestamp" ] } } } } } }' --reset-operation --wait
178
+ gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters": { "mappings": { "test_mapping": { "fields": { "Created0x10": { "ecs": ["@timestamp" ] } } } } } }' --reset-operation --wait
179
179
 
180
180
  # pass mapping using a gulp mapping file with mapping_id to specify which mapping to use in the file
181
181
  gulp-cli ingest file test_operation csv ./samples/mftecmd/sample_record.csv --plugin-params '{ "mapping_parameters"
@@ -219,11 +219,11 @@ gulp-cli ingest zip incident-001 /evidence/evidence.zip --create-operation
219
219
 
220
220
  ```bash
221
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
222
+ gulp-cli ingest zip-create /evidence/evidence.zip /forensic/host1/*.evtx /forensic/host2 /forensic/notes.txt --no-overwrite
223
223
 
224
224
  # Use environment variables and ~ in source expressions and output path
225
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
226
+ gulp-cli ingest zip-create '$CASE_ROOT/evidence.zip' '$CASE_ROOT/windows/*.evtx' '$CASE_ROOT/network/*' --no-overwrite
227
227
 
228
228
  # Build ZIP from a text file (one path expression per line)
229
229
  cat > /tmp/evidence_paths.txt <<'EOF'
@@ -613,6 +613,16 @@ done
613
613
 
614
614
  ## Enrichment & Tagging Workflows
615
615
 
616
+ ### use whois to enrich IP addresses with domain information
617
+
618
+ ```bash
619
+ gulp-cli enrich documents incident-001 \
620
+ --plugin enrich_whois \
621
+ --flt '{"operation_ids":["network"]}' \
622
+ --fields '{"ip.destination": null}' \
623
+ --wait
624
+ ```
625
+
616
626
  ### Tag Suspicious Events
617
627
 
618
628
  ```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.1.4'
22
- __version_tuple__ = version_tuple = (1, 1, 4)
21
+ __version__ = version = '1.1.6'
22
+ __version_tuple__ = version_tuple = (1, 1, 6)
23
23
 
24
- __commit_id__ = commit_id = 'g8cbdcc79a'
24
+ __commit_id__ = commit_id = 'g8d034cc8b'
@@ -7,6 +7,7 @@ from gulp_cli.commands.auth import app as auth_app
7
7
  from gulp_cli.commands.collab import app as collab_app
8
8
  from gulp_cli.commands.context import app as context_app
9
9
  from gulp_cli.commands.db import app as db_app
10
+ from gulp_cli.commands.enrich import app as enrich_app
10
11
  from gulp_cli.commands.enhance_map import app as enhance_map_app
11
12
  from gulp_cli.commands.glyph import app as glyph_app
12
13
  from gulp_cli.commands.ingest import app as ingest_app
@@ -14,12 +15,12 @@ from gulp_cli.commands.mapping import app as mapping_app
14
15
  from gulp_cli.commands.operations import app as operation_app
15
16
  from gulp_cli.commands.plugin import app as plugin_app
16
17
  from gulp_cli.commands.query import app as query_app
18
+ from gulp_cli.commands.utility import app as utility_app
17
19
  from gulp_cli.commands.source import app as source_app
18
20
  from gulp_cli.commands.stats import app as stats_app
19
21
  from gulp_cli.commands.storage import app as storage_app
20
22
  from gulp_cli.commands.user_group import app as user_group_app
21
23
  from gulp_cli.commands.users import app as user_app
22
- from gulp_sdk.client import GulpClient
23
24
 
24
25
  from gulp_cli.config import (
25
26
  set_runtime_as_user,
@@ -27,28 +28,21 @@ from gulp_cli.config import (
27
28
  set_runtime_verbose,
28
29
  )
29
30
  from gulp_cli.extensions import load_extensions
30
-
31
- app = typer.Typer(
32
- no_args_is_help=True,
33
- help="Modern CLI for gULP, powered by gulp-sdk",
34
- )
31
+ from gulp_cli.version import print_version
35
32
 
36
33
 
37
34
  def _show_versions(value: bool) -> None:
38
35
  if not value:
39
36
  return
40
37
 
41
- from gulp_cli._version import __version__, __commit_id__
42
-
43
- sdk_version = GulpClient("http://localhost:8080").version()
44
- print(
45
- f"gulp-cli version: {__version__} ({__commit_id__})"
46
- if __commit_id__
47
- else __version__
48
- )
49
- print(f"gulp-sdk version: {sdk_version}")
38
+ print_version()
50
39
  raise typer.Exit()
51
40
 
41
+ app = typer.Typer(
42
+ no_args_is_help=True,
43
+ help="Modern CLI for gULP, powered by gulp-sdk",
44
+ )
45
+
52
46
 
53
47
  @app.callback(invoke_without_command=True)
54
48
  def main(
@@ -73,7 +67,7 @@ def main(
73
67
  "--version",
74
68
  callback=_show_versions,
75
69
  is_eager=True,
76
- help="Show CLI and SDK versions and exit.",
70
+ help="Show the CLI version and exit.",
77
71
  ),
78
72
  ) -> None:
79
73
  set_runtime_config_dir(config_dir)
@@ -93,11 +87,13 @@ app.add_typer(stats_app, name="stats")
93
87
  app.add_typer(storage_app, name="storage")
94
88
  app.add_typer(collab_app, name="collab")
95
89
  app.add_typer(db_app, name="db")
90
+ app.add_typer(enrich_app, name="enrich")
96
91
  app.add_typer(acl_app, name="acl")
97
92
  app.add_typer(plugin_app, name="plugin")
98
93
  app.add_typer(mapping_app, name="mapping")
99
94
  app.add_typer(enhance_map_app, name="enhance-map")
100
95
  app.add_typer(glyph_app, name="glyph")
96
+ app.add_typer(utility_app, name="utility")
101
97
 
102
98
  load_extensions(
103
99
  app,
@@ -114,10 +110,12 @@ load_extensions(
114
110
  "storage": storage_app,
115
111
  "collab": collab_app,
116
112
  "db": db_app,
113
+ "enrich": enrich_app,
117
114
  "acl": acl_app,
118
115
  "plugin": plugin_app,
119
116
  "mapping": mapping_app,
120
117
  "enhance-map": enhance_map_app,
121
118
  "glyph": glyph_app,
119
+ "utility": utility_app,
122
120
  },
123
121
  )
@@ -0,0 +1,187 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ import typer
7
+
8
+ from gulp_cli.client import get_client
9
+ from gulp_cli.output import print_result
10
+ from gulp_cli.utils import comma_split, parse_json_option
11
+
12
+ app = typer.Typer(help="Enrichment and tagging commands")
13
+
14
+
15
+ @app.command("tag")
16
+ def enrich_tag(
17
+ operation_id: str,
18
+ tags: list[str] = typer.Option(..., "--tag", help="Tag to add (repeatable)"),
19
+ flt: str | None = typer.Option(
20
+ None, "--flt", help="JSON object for GulpQueryFilter"
21
+ ),
22
+ wait: bool = typer.Option(False, "--wait", help="Wait for the async job to finish"),
23
+ ) -> None:
24
+ """Add tags to documents matching a filter."""
25
+
26
+ async def _run() -> None:
27
+ flt_parsed = parse_json_option(flt, field_name="flt")
28
+ async with get_client() as client:
29
+ result = await client.enrich.tag_documents(
30
+ operation_id=operation_id,
31
+ tags=tags,
32
+ flt=flt_parsed,
33
+ wait=wait,
34
+ )
35
+ print_result(result)
36
+
37
+ asyncio.run(_run())
38
+
39
+
40
+ @app.command("untag")
41
+ def enrich_untag(
42
+ operation_id: str,
43
+ tags: list[str] = typer.Option(..., "--tag", help="Tag to remove (repeatable)"),
44
+ flt: str | None = typer.Option(
45
+ None, "--flt", help="JSON object for GulpQueryFilter"
46
+ ),
47
+ wait: bool = typer.Option(False, "--wait", help="Wait for the async job to finish"),
48
+ ) -> None:
49
+ """Remove tags from documents matching a filter."""
50
+
51
+ async def _run() -> None:
52
+ flt_parsed = parse_json_option(flt, field_name="flt")
53
+ async with get_client() as client:
54
+ result = await client.enrich.untag_documents(
55
+ operation_id=operation_id,
56
+ tags=tags,
57
+ flt=flt_parsed,
58
+ wait=wait,
59
+ )
60
+ print_result(result)
61
+
62
+ asyncio.run(_run())
63
+
64
+
65
+ @app.command("update")
66
+ def enrich_update(
67
+ operation_id: str,
68
+ fields: str = typer.Option(..., "--fields", help="JSON object of field updates"),
69
+ flt: str | None = typer.Option(
70
+ None, "--flt", help="JSON object for GulpQueryFilter"
71
+ ),
72
+ wait: bool = typer.Option(False, "--wait", help="Wait for the async job to finish"),
73
+ ) -> None:
74
+ """Update fields on documents matching a filter."""
75
+
76
+ async def _run() -> None:
77
+ fields_parsed = parse_json_option(fields, field_name="fields")
78
+ if fields_parsed is None:
79
+ raise typer.BadParameter("--fields is required")
80
+ flt_parsed = parse_json_option(flt, field_name="flt")
81
+ async with get_client() as client:
82
+ result = await client.enrich.update_documents(
83
+ operation_id=operation_id,
84
+ fields=fields_parsed,
85
+ flt=flt_parsed,
86
+ wait=wait,
87
+ )
88
+ print_result(result)
89
+
90
+ asyncio.run(_run())
91
+
92
+
93
+ @app.command("remove")
94
+ def enrich_remove(
95
+ operation_id: str,
96
+ fields: str = typer.Option(
97
+ ..., "--fields", help="Comma-separated field names to remove"
98
+ ),
99
+ flt: str | None = typer.Option(
100
+ None, "--flt", help="JSON object for GulpQueryFilter"
101
+ ),
102
+ wait: bool = typer.Option(False, "--wait", help="Wait for the async job to finish"),
103
+ ) -> None:
104
+ """Remove enrichment markers or specific fields from documents matching a filter."""
105
+
106
+ async def _run() -> None:
107
+ flt_parsed = parse_json_option(flt, field_name="flt")
108
+ field_names = comma_split(fields)
109
+ async with get_client() as client:
110
+ result = await client.enrich.enrich_remove(
111
+ operation_id=operation_id,
112
+ fields=field_names or None,
113
+ flt=flt_parsed,
114
+ wait=wait,
115
+ )
116
+ print_result(result)
117
+
118
+ asyncio.run(_run())
119
+
120
+
121
+ @app.command("documents")
122
+ def enrich_documents(
123
+ operation_id: str,
124
+ plugin: str = typer.Option(..., "--plugin", help="Enrichment plugin name"),
125
+ fields: str | None = typer.Option(
126
+ None, "--fields", help="JSON object of fields to enrich"
127
+ ),
128
+ flt: str | None = typer.Option(
129
+ None, "--flt", help="JSON object for GulpQueryFilter"
130
+ ),
131
+ plugin_params: str | None = typer.Option(
132
+ None, "--plugin-params", help="JSON object for GulpPluginParameters"
133
+ ),
134
+ wait: bool = typer.Option(False, "--wait", help="Wait for the async job to finish"),
135
+ ) -> None:
136
+ """Run an enrichment plugin on documents matching a filter."""
137
+
138
+ async def _run() -> None:
139
+ fields_parsed = parse_json_option(fields, field_name="fields")
140
+ flt_parsed = parse_json_option(flt, field_name="flt")
141
+ plugin_params_parsed = parse_json_option(
142
+ plugin_params, field_name="plugin-params"
143
+ )
144
+ async with get_client() as client:
145
+ result = await client.enrich.enrich_documents(
146
+ operation_id=operation_id,
147
+ plugin=plugin,
148
+ fields=fields_parsed,
149
+ flt=flt_parsed,
150
+ plugin_params=plugin_params_parsed,
151
+ wait=wait,
152
+ )
153
+ print_result(result)
154
+
155
+ asyncio.run(_run())
156
+
157
+
158
+ @app.command("single-id")
159
+ def enrich_single_id(
160
+ operation_id: str,
161
+ doc_id: str,
162
+ plugin: str = typer.Option(..., "--plugin", help="Enrichment plugin name"),
163
+ fields: str | None = typer.Option(
164
+ None, "--fields", help="JSON object of fields to enrich"
165
+ ),
166
+ plugin_params: str | None = typer.Option(
167
+ None, "--plugin-params", help="JSON object for GulpPluginParameters"
168
+ ),
169
+ ) -> None:
170
+ """Enrich a single document by OpenSearch document ID."""
171
+
172
+ async def _run() -> None:
173
+ fields_parsed = parse_json_option(fields, field_name="fields")
174
+ plugin_params_parsed = parse_json_option(
175
+ plugin_params, field_name="plugin-params"
176
+ )
177
+ async with get_client() as client:
178
+ result = await client.enrich.enrich_single_id(
179
+ operation_id=operation_id,
180
+ doc_id=doc_id,
181
+ plugin=plugin,
182
+ fields=fields_parsed,
183
+ plugin_params=plugin_params_parsed,
184
+ )
185
+ print_result(result)
186
+
187
+ asyncio.run(_run())