emby-cli 0.3.0__tar.gz → 0.4.0__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 (51) hide show
  1. {emby_cli-0.3.0 → emby_cli-0.4.0}/CHANGELOG.md +7 -0
  2. {emby_cli-0.3.0 → emby_cli-0.4.0}/PKG-INFO +15 -11
  3. {emby_cli-0.3.0 → emby_cli-0.4.0}/README.md +14 -10
  4. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/_version.py +2 -2
  5. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/cli.py +63 -10
  6. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/client.py +9 -5
  7. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/download.py +20 -15
  8. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/help.py +1 -0
  9. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/search.py +17 -11
  10. emby_cli-0.4.0/src/emby_cli/commands/show.py +273 -0
  11. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/constants.py +11 -0
  12. emby_cli-0.4.0/src/emby_cli/mode_args.py +31 -0
  13. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_client.py +13 -7
  14. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_parser.py +79 -17
  15. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_search.py +7 -7
  16. emby_cli-0.4.0/tests/test_show.py +142 -0
  17. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_validate_args.py +58 -19
  18. {emby_cli-0.3.0 → emby_cli-0.4.0}/.env.example +0 -0
  19. {emby_cli-0.3.0 → emby_cli-0.4.0}/.github/workflows/ci.yml +0 -0
  20. {emby_cli-0.3.0 → emby_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  21. {emby_cli-0.3.0 → emby_cli-0.4.0}/.gitignore +0 -0
  22. {emby_cli-0.3.0 → emby_cli-0.4.0}/LICENSE +0 -0
  23. {emby_cli-0.3.0 → emby_cli-0.4.0}/pyproject.toml +0 -0
  24. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/__init__.py +0 -0
  25. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/__main__.py +0 -0
  26. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/auth_cache.py +0 -0
  27. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/__init__.py +0 -0
  28. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/config.py +0 -0
  29. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/info.py +0 -0
  30. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/login.py +0 -0
  31. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/logout.py +0 -0
  32. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/play.py +0 -0
  33. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/commands/version.py +0 -0
  34. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/credentials.py +0 -0
  35. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/download_ops.py +0 -0
  36. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/output.py +0 -0
  37. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/resolve.py +0 -0
  38. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/util.py +0 -0
  39. {emby_cli-0.3.0 → emby_cli-0.4.0}/src/emby_cli/version.py +0 -0
  40. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_auth_cache.py +0 -0
  41. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_config.py +0 -0
  42. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_credentials.py +0 -0
  43. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_download_ops.py +0 -0
  44. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_info.py +0 -0
  45. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_logout.py +0 -0
  46. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_output.py +0 -0
  47. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_resolve.py +0 -0
  48. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_resolve_table.py +0 -0
  49. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_session_auth.py +0 -0
  50. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_util.py +0 -0
  51. {emby_cli-0.3.0 → emby_cli-0.4.0}/tests/test_version.py +0 -0
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.4.0
6
+
7
+ Added:
8
+
9
+ - `show`: detail view for `--item` / `--library` (QUERY or `--id`); multiple matches print a disambiguation table with IDs. Libraries include total item count and the last 10 added (`DateCreated`), limited to Movie/Episode/Audio (no Studio, etc.).
10
+ - `search` / `download`: `--item QUERY` / `--library QUERY` (query on the mode flag); `--media-item` is an alias of `--item`. `--search` still works as an alternative selector.
11
+
5
12
  ## 0.3.0
6
13
 
7
14
  Breaking:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: emby-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: CLI to list, search, play, and download/backup original media from an Emby server via its REST API.
5
5
  Project-URL: Homepage, https://github.com/soukron/emby-cli
6
6
  Project-URL: Repository, https://github.com/soukron/emby-cli
@@ -84,18 +84,22 @@ emby-cli config current-server
84
84
  emby-cli config view
85
85
  emby-cli version
86
86
  emby-cli info
87
- emby-cli search --media-item --search "Title"
88
- emby-cli search --media-item --search "Title" --count 50
89
- emby-cli search --media-item --id 123456
90
- emby-cli search --media-item --all
87
+ emby-cli search --item "Title"
88
+ emby-cli search --item "Title" --count 50
89
+ emby-cli search --item --id 123456
90
+ emby-cli search --item --all
91
91
  emby-cli search --library --all
92
92
  emby-cli search --library --id 614156
93
- emby-cli search --library --search "PELICULAS"
94
-
95
- emby-cli download --media-item --id 123456
96
- emby-cli download --media-item --search "californication S01E01" --pick-best-item
93
+ emby-cli search --library "PELICULAS"
94
+ emby-cli show --item "Title"
95
+ emby-cli show --item --id 123456
96
+ emby-cli show --library "PELICULAS"
97
+ emby-cli show --library --id 614156
98
+
99
+ emby-cli download --item --id 123456
100
+ emby-cli download --item "californication S01E01" --pick-best-item
97
101
  emby-cli download --library --id 12345
98
- emby-cli download --library --search "PELICULAS 4K"
102
+ emby-cli download --library "PELICULAS 4K"
99
103
  emby-cli download --from-file titles.txt --dry-run
100
104
 
101
105
  emby-cli play --id 123456
@@ -104,7 +108,7 @@ emby-cli play --search "Pelicula (1980)" --pick-best-item
104
108
 
105
109
  `info` reports libraries and totals from `GET /Items/Counts` (totals may include multiple versions of the same title).
106
110
 
107
- Title resolution (`play --search`, `download --media-item --search`, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` / `--media-item --search`, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library `--search` requires an exact name match (case-insensitive); no pick-best.
111
+ Title resolution (`play --search`, `download --item` with a query, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` or `--item` + query, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library query requires an exact name match (case-insensitive); no pick-best.
108
112
 
109
113
  `--dry-run` / `-n` works for all `download` modes (no files written).
110
114
 
@@ -50,18 +50,22 @@ emby-cli config current-server
50
50
  emby-cli config view
51
51
  emby-cli version
52
52
  emby-cli info
53
- emby-cli search --media-item --search "Title"
54
- emby-cli search --media-item --search "Title" --count 50
55
- emby-cli search --media-item --id 123456
56
- emby-cli search --media-item --all
53
+ emby-cli search --item "Title"
54
+ emby-cli search --item "Title" --count 50
55
+ emby-cli search --item --id 123456
56
+ emby-cli search --item --all
57
57
  emby-cli search --library --all
58
58
  emby-cli search --library --id 614156
59
- emby-cli search --library --search "PELICULAS"
60
-
61
- emby-cli download --media-item --id 123456
62
- emby-cli download --media-item --search "californication S01E01" --pick-best-item
59
+ emby-cli search --library "PELICULAS"
60
+ emby-cli show --item "Title"
61
+ emby-cli show --item --id 123456
62
+ emby-cli show --library "PELICULAS"
63
+ emby-cli show --library --id 614156
64
+
65
+ emby-cli download --item --id 123456
66
+ emby-cli download --item "californication S01E01" --pick-best-item
63
67
  emby-cli download --library --id 12345
64
- emby-cli download --library --search "PELICULAS 4K"
68
+ emby-cli download --library "PELICULAS 4K"
65
69
  emby-cli download --from-file titles.txt --dry-run
66
70
 
67
71
  emby-cli play --id 123456
@@ -70,7 +74,7 @@ emby-cli play --search "Pelicula (1980)" --pick-best-item
70
74
 
71
75
  `info` reports libraries and totals from `GET /Items/Counts` (totals may include multiple versions of the same title).
72
76
 
73
- Title resolution (`play --search`, `download --media-item --search`, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` / `--media-item --search`, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library `--search` requires an exact name match (case-insensitive); no pick-best.
77
+ Title resolution (`play --search`, `download --item` with a query, `download --from-file`) is **strict** by default (year mismatch or multiple matches fail). Pass `--pick-best-item` to auto-select the best ≤1080p version. With `--from-file` or `--item` + query, a season line like `Show S01` downloads the whole season (same pick-best rule per episode version). Library query requires an exact name match (case-insensitive); no pick-best.
74
78
 
75
79
  `--dry-run` / `-n` works for all `download` modes (no files written).
76
80
 
@@ -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 = '0.3.0'
22
- __version_tuple__ = version_tuple = (0, 3, 0)
21
+ __version__ = version = '0.4.0'
22
+ __version_tuple__ = version_tuple = (0, 4, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -21,6 +21,7 @@ from emby_cli.commands.login import cmd_login
21
21
  from emby_cli.commands.logout import cmd_logout
22
22
  from emby_cli.commands.play import cmd_play, validate_play_args
23
23
  from emby_cli.commands.search import cmd_search, validate_search_args
24
+ from emby_cli.commands.show import cmd_show, validate_show_args
24
25
  from emby_cli.commands.version import cmd_version
25
26
  from emby_cli.constants import DEFAULT_OUTPUT, SEARCH_COUNT_DEFAULT
26
27
  from emby_cli.credentials import (
@@ -82,14 +83,23 @@ def build_parser() -> argparse.ArgumentParser:
82
83
  )
83
84
  mode = dl.add_mutually_exclusive_group(required=True)
84
85
  mode.add_argument(
86
+ "--item",
85
87
  "--media-item",
86
- action="store_true",
87
- help="Download media items; use with --id or --search",
88
+ nargs="?",
89
+ const="",
90
+ default=None,
91
+ metavar="QUERY",
92
+ dest="item",
93
+ help="Download media items; optional QUERY, or use with --id "
94
+ "(--media-item is an alias)",
88
95
  )
89
96
  mode.add_argument(
90
97
  "--library",
91
- action="store_true",
92
- help="Download a library; use with --id or --search",
98
+ nargs="?",
99
+ const="",
100
+ default=None,
101
+ metavar="QUERY",
102
+ help="Download a library; optional QUERY, or use with --id",
93
103
  )
94
104
  mode.add_argument(
95
105
  "--from-file",
@@ -105,7 +115,7 @@ def build_parser() -> argparse.ArgumentParser:
105
115
  )
106
116
  dl.add_argument(
107
117
  "--search",
108
- help="Search query for media item or library",
118
+ help="Search query (alternative to QUERY on --item / --library)",
109
119
  )
110
120
  dl.add_argument(
111
121
  "--dry-run",
@@ -146,14 +156,23 @@ def build_parser() -> argparse.ArgumentParser:
146
156
  sr = sub.add_parser("search", help=_help_by_name["search"])
147
157
  sr_mode = sr.add_mutually_exclusive_group(required=True)
148
158
  sr_mode.add_argument(
159
+ "--item",
149
160
  "--media-item",
150
- action="store_true",
151
- help="Search media items; use with --id, --search, or --all",
161
+ nargs="?",
162
+ const="",
163
+ default=None,
164
+ metavar="QUERY",
165
+ dest="item",
166
+ help="Search media items; optional QUERY, or use with --id / --all "
167
+ "(--media-item is an alias)",
152
168
  )
153
169
  sr_mode.add_argument(
154
170
  "--library",
155
- action="store_true",
156
- help="Search libraries; use with --id, --search, or --all",
171
+ nargs="?",
172
+ const="",
173
+ default=None,
174
+ metavar="QUERY",
175
+ help="Search libraries; optional QUERY, or use with --id / --all",
157
176
  )
158
177
  sr.add_argument(
159
178
  "--id",
@@ -162,7 +181,7 @@ def build_parser() -> argparse.ArgumentParser:
162
181
  )
163
182
  sr.add_argument(
164
183
  "--search",
165
- help="Search query for media item or library",
184
+ help="Search query (alternative to QUERY on --item / --library)",
166
185
  )
167
186
  sr.add_argument(
168
187
  "--all",
@@ -206,6 +225,37 @@ def build_parser() -> argparse.ArgumentParser:
206
225
  "(default: list matches and fail)",
207
226
  )
208
227
 
228
+ sh = sub.add_parser("show", help=_help_by_name["show"])
229
+ sh_mode = sh.add_mutually_exclusive_group(required=True)
230
+ sh_mode.add_argument(
231
+ "--item",
232
+ "--media-item",
233
+ nargs="?",
234
+ const="",
235
+ default=None,
236
+ metavar="QUERY",
237
+ dest="item",
238
+ help="Show a media item; optional QUERY, or use with --id "
239
+ "(--media-item is an alias)",
240
+ )
241
+ sh_mode.add_argument(
242
+ "--library",
243
+ nargs="?",
244
+ const="",
245
+ default=None,
246
+ metavar="QUERY",
247
+ help="Show a library; optional QUERY, or use with --id",
248
+ )
249
+ sh.add_argument(
250
+ "--id",
251
+ default=None,
252
+ help="Media item or library ID",
253
+ )
254
+ sh.add_argument(
255
+ "--search",
256
+ help="Search query (alternative to QUERY on --item / --library)",
257
+ )
258
+
209
259
  sub.add_parser("version", help=_help_by_name["version"])
210
260
  sub.add_parser("info", help=_help_by_name["info"])
211
261
 
@@ -249,6 +299,8 @@ def _validate_command_args(command: str, args: argparse.Namespace) -> str | None
249
299
  return validate_download_args(args)
250
300
  if command == "play":
251
301
  return validate_play_args(args)
302
+ if command == "show":
303
+ return validate_show_args(args)
252
304
  return None
253
305
 
254
306
 
@@ -293,6 +345,7 @@ def main() -> None:
293
345
  "download": cmd_download,
294
346
  "search": cmd_search,
295
347
  "play": cmd_play,
348
+ "show": cmd_show,
296
349
  }
297
350
  commands[args.command](client, args)
298
351
 
@@ -458,15 +458,19 @@ class EmbyClient:
458
458
  recursive: bool = True,
459
459
  start: int = 0,
460
460
  limit: int = 200,
461
+ *,
462
+ sort_by: str = "SortName",
463
+ sort_order: str = "Ascending",
464
+ fields: str | None = None,
461
465
  ) -> dict:
462
466
  uid = self.resolve_user_id()
463
467
  params: dict = {
464
468
  "StartIndex": start,
465
469
  "Limit": limit,
466
470
  "Recursive": str(recursive).lower(),
467
- "Fields": ITEM_FIELDS,
468
- "SortBy": "SortName",
469
- "SortOrder": "Ascending",
471
+ "Fields": fields or ITEM_FIELDS,
472
+ "SortBy": sort_by,
473
+ "SortOrder": sort_order,
470
474
  }
471
475
  if parent_id:
472
476
  params["ParentId"] = parent_id
@@ -493,11 +497,11 @@ class EmbyClient:
493
497
  break
494
498
  return items
495
499
 
496
- def get_item_info(self, item_id: str) -> dict:
500
+ def get_item_info(self, item_id: str, *, fields: str | None = None) -> dict:
497
501
  uid = self.resolve_user_id()
498
502
  resp = self._get(
499
503
  f"/Users/{uid}/Items/{item_id}",
500
- params={"Fields": ITEM_FIELDS},
504
+ params={"Fields": fields or ITEM_FIELDS},
501
505
  )
502
506
  return resp.json()
503
507
 
@@ -15,6 +15,7 @@ from emby_cli.download_ops import (
15
15
  download_one_item,
16
16
  find_library,
17
17
  )
18
+ from emby_cli.mode_args import mode_is_item, mode_is_library, resolve_query
18
19
  from emby_cli.output import Stats, print_done, print_error
19
20
  from emby_cli.resolve import resolve_title_items
20
21
  from emby_cli.util import format_duration
@@ -60,22 +61,26 @@ def _download_items(
60
61
 
61
62
  def validate_download_args(args: argparse.Namespace) -> str | None:
62
63
  """Return an error message if mode/selectors are invalid; else ``None``."""
63
- if getattr(args, "media_item", False):
64
- search = (getattr(args, "search", None) or "").strip() or None
64
+ if mode_is_item(args):
65
+ query, err = resolve_query(args)
66
+ if err:
67
+ return err
65
68
  item_id = (getattr(args, "id", None) or "").strip() or None
66
- if not item_id and not search:
69
+ if not item_id and not query:
67
70
  item_id = (os.environ.get("EMBY_ITEM_ID") or "").strip() or None
68
- if bool(item_id) == bool(search):
69
- return "With --media-item, provide exactly one of --id or --search"
71
+ if bool(item_id) == bool(query):
72
+ return "With --item, provide exactly one of --id or QUERY/--search"
70
73
  return None
71
74
 
72
- if getattr(args, "library", False):
75
+ if mode_is_library(args):
73
76
  if bool(getattr(args, "pick_best_item", False)):
74
77
  return "--pick-best-item cannot be used with --library"
78
+ query, err = resolve_query(args)
79
+ if err:
80
+ return err
75
81
  library_id = (getattr(args, "id", None) or "").strip() or None
76
- search = (getattr(args, "search", None) or "").strip() or None
77
- if bool(library_id) == bool(search):
78
- return "With --library, provide exactly one of --id or --search"
82
+ if bool(library_id) == bool(query):
83
+ return "With --library, provide exactly one of --id or QUERY/--search"
79
84
  return None
80
85
 
81
86
  if getattr(args, "from_file", None):
@@ -85,7 +90,7 @@ def validate_download_args(args: argparse.Namespace) -> str | None:
85
90
  return "With --from-file, do not pass --id or --search"
86
91
  return None
87
92
 
88
- return "Specify --media-item, --library, or --from-file"
93
+ return "Specify --item, --library, or --from-file"
89
94
 
90
95
 
91
96
  def _cmd_download_item(client: EmbyClient, args: argparse.Namespace) -> None:
@@ -94,7 +99,7 @@ def _cmd_download_item(client: EmbyClient, args: argparse.Namespace) -> None:
94
99
  method = getattr(args, "method", "download")
95
100
  dry_run = bool(getattr(args, "dry_run", False))
96
101
  pick_best = bool(getattr(args, "pick_best_item", False))
97
- search = (getattr(args, "search", None) or "").strip() or None
102
+ search, _ = resolve_query(args)
98
103
  item_id = (getattr(args, "id", None) or "").strip() or None
99
104
  if not item_id and not search:
100
105
  item_id = (os.environ.get("EMBY_ITEM_ID") or "").strip() or None
@@ -157,7 +162,7 @@ def _cmd_download_library(client: EmbyClient, args: argparse.Namespace) -> None:
157
162
  method = getattr(args, "method", "download")
158
163
  dry_run = bool(getattr(args, "dry_run", False))
159
164
  library_id = (getattr(args, "id", None) or "").strip() or None
160
- search = (getattr(args, "search", None) or "").strip() or None
165
+ search, _ = resolve_query(args)
161
166
 
162
167
  if dry_run:
163
168
  print("*** DRY RUN — no files will be downloaded ***\n")
@@ -335,14 +340,14 @@ def cmd_download(client: EmbyClient, args: argparse.Namespace) -> None:
335
340
  print(err)
336
341
  sys.exit(1)
337
342
 
338
- if getattr(args, "media_item", False):
343
+ if mode_is_item(args):
339
344
  _cmd_download_item(client, args)
340
345
  return
341
- if getattr(args, "library", False):
346
+ if mode_is_library(args):
342
347
  _cmd_download_library(client, args)
343
348
  return
344
349
  if getattr(args, "from_file", None):
345
350
  _cmd_download_from_file(client, args)
346
351
  return
347
- print("Specify --media-item, --library, or --from-file")
352
+ print("Specify --item, --library, or --from-file")
348
353
  sys.exit(1)
@@ -11,6 +11,7 @@ COMMAND_SUMMARIES: tuple[tuple[str, str], ...] = (
11
11
  ("version", "Show emby-cli version"),
12
12
  ("info", "Show session user, server details, libraries, and item counts"),
13
13
  ("search", "Search media items or libraries"),
14
+ ("show", "Show details for a media item or library"),
14
15
  ("download", "Download media items or libraries"),
15
16
  ("play", "Play a media item using an external player"),
16
17
  )
@@ -1,4 +1,4 @@
1
- """search command — media items or libraries (--media-item/--library + --id/--search/--all)."""
1
+ """search command — media items or libraries (--item/--library + QUERY/--id/--all)."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -8,6 +8,7 @@ import sys
8
8
  from emby_cli.client import EmbyClient
9
9
  from emby_cli.constants import SEARCH_COUNT_DEFAULT, SEARCH_ITEM_TYPES
10
10
  from emby_cli.download_ops import find_library
11
+ from emby_cli.mode_args import mode_is_library, resolve_query
11
12
  from emby_cli.resolve import print_item_choices, print_library_choices
12
13
 
13
14
 
@@ -39,9 +40,8 @@ def _print_libraries(
39
40
  print()
40
41
 
41
42
 
42
- def _selector_count(args: argparse.Namespace) -> int:
43
+ def _selector_count(args: argparse.Namespace, query: str | None) -> int:
43
44
  item_id = (getattr(args, "id", None) or "").strip()
44
- query = (getattr(args, "search", None) or "").strip()
45
45
  use_all = bool(getattr(args, "all", False))
46
46
  return sum(bool(x) for x in (item_id, query, use_all))
47
47
 
@@ -52,8 +52,14 @@ def validate_search_args(args: argparse.Namespace) -> str | None:
52
52
  count = SEARCH_COUNT_DEFAULT if raw_count is None else int(raw_count)
53
53
  if count < 1:
54
54
  return "error: --count must be >= 1"
55
- if _selector_count(args) != 1:
56
- return "Provide exactly one of --id, --search, or --all"
55
+ query, err = resolve_query(args)
56
+ if err:
57
+ return err
58
+ if _selector_count(args, query) != 1:
59
+ return (
60
+ "Provide exactly one of QUERY on --item/--library, "
61
+ "--search, --id, or --all"
62
+ )
57
63
  return None
58
64
 
59
65
 
@@ -69,10 +75,10 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
69
75
  raw_count = getattr(args, "count", SEARCH_COUNT_DEFAULT)
70
76
  count = SEARCH_COUNT_DEFAULT if raw_count is None else int(raw_count)
71
77
  item_id = (getattr(args, "id", None) or "").strip() or None
72
- query = (getattr(args, "search", None) or "").strip() or None
78
+ query, _ = resolve_query(args)
73
79
  use_all = bool(getattr(args, "all", False))
74
80
 
75
- if getattr(args, "library", False):
81
+ if mode_is_library(args):
76
82
  libraries = client.get_libraries()
77
83
  if use_all:
78
84
  _print_libraries(client, libraries, count=count)
@@ -87,7 +93,7 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
87
93
  _print_libraries(client, [lib], count=count)
88
94
  return
89
95
 
90
- needle = query.lower()
96
+ needle = (query or "").lower()
91
97
  matches = [
92
98
  lib for lib in libraries
93
99
  if needle in (lib.get("Name") or "").lower()
@@ -95,15 +101,15 @@ def cmd_search(client: EmbyClient, args: argparse.Namespace) -> None:
95
101
  _print_libraries(client, matches, count=count)
96
102
  return
97
103
 
98
- # --media-item
104
+ # --item / --media-item
99
105
  if use_all:
100
106
  probe = client.get_items(item_type=SEARCH_ITEM_TYPES, limit=0)
101
107
  total = int(probe.get("TotalRecordCount") or 0)
102
108
  if total > count:
103
109
  print(
104
110
  f"There are {total} media items on this server. "
105
- "Please narrow the results with --search, for example:\n"
106
- ' emby-cli search --media-item --search "title"'
111
+ "Please narrow the results with a query, for example:\n"
112
+ ' emby-cli search --item "title"'
107
113
  )
108
114
  sys.exit(1)
109
115
  if total == 0: