content-cli 0.4.0__tar.gz → 0.6.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.
- {content_cli-0.4.0 → content_cli-0.6.0}/PKG-INFO +18 -2
- {content_cli-0.4.0 → content_cli-0.6.0}/README.md +16 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/content_cli/cli.py +47 -3
- {content_cli-0.4.0 → content_cli-0.6.0}/pyproject.toml +2 -2
- {content_cli-0.4.0 → content_cli-0.6.0}/.gitignore +0 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/LICENSE +0 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/NOTICE +0 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/content_cli/__init__.py +0 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/content_cli/__main__.py +0 -0
- {content_cli-0.4.0 → content_cli-0.6.0}/content_cli/builders.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: content-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Command-line client for the Content engine (talks to /api/v1).
|
|
5
5
|
Project-URL: Homepage, https://github.com/LatentNoise/content
|
|
6
6
|
Project-URL: Source, https://github.com/LatentNoise/content
|
|
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
21
|
Classifier: Topic :: Multimedia :: Video
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.11
|
|
24
|
-
Requires-Dist: content-sdk==0.
|
|
24
|
+
Requires-Dist: content-sdk==0.6.0
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# `content` — Content CLI
|
|
@@ -95,3 +95,19 @@ content cancel <job_id> ; content retry <job_id>
|
|
|
95
95
|
|
|
96
96
|
Global flags: `--api-url URL` and `--json` (raw JSON output) go before the
|
|
97
97
|
subcommand. Exit code is non-zero on API errors or a failed watched job.
|
|
98
|
+
|
|
99
|
+
## Exit codes when watching a job
|
|
100
|
+
|
|
101
|
+
`content ... --watch` and `content watch <job>` return the outcome, so a script
|
|
102
|
+
can chain on it (ADR 0021):
|
|
103
|
+
|
|
104
|
+
| Code | Meaning |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `0` | `succeeded` — everything asked for was produced |
|
|
107
|
+
| `2` | `partially_succeeded` — some of it was, and at least one step failed |
|
|
108
|
+
| `1` | `failed` or `cancelled` |
|
|
109
|
+
|
|
110
|
+
`2` is deliberately distinct from `1`. A playlist that yielded five videos of
|
|
111
|
+
six, or a download whose delivery into your library was refused, is not a
|
|
112
|
+
failure — but it is not a success either, and a script that treats it as one
|
|
113
|
+
will quietly move on with missing files.
|
|
@@ -69,3 +69,19 @@ content cancel <job_id> ; content retry <job_id>
|
|
|
69
69
|
|
|
70
70
|
Global flags: `--api-url URL` and `--json` (raw JSON output) go before the
|
|
71
71
|
subcommand. Exit code is non-zero on API errors or a failed watched job.
|
|
72
|
+
|
|
73
|
+
## Exit codes when watching a job
|
|
74
|
+
|
|
75
|
+
`content ... --watch` and `content watch <job>` return the outcome, so a script
|
|
76
|
+
can chain on it (ADR 0021):
|
|
77
|
+
|
|
78
|
+
| Code | Meaning |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `0` | `succeeded` — everything asked for was produced |
|
|
81
|
+
| `2` | `partially_succeeded` — some of it was, and at least one step failed |
|
|
82
|
+
| `1` | `failed` or `cancelled` |
|
|
83
|
+
|
|
84
|
+
`2` is deliberately distinct from `1`. A playlist that yielded five videos of
|
|
85
|
+
six, or a download whose delivery into your library was refused, is not a
|
|
86
|
+
failure — but it is not a success either, and a script that treats it as one
|
|
87
|
+
will quietly move on with missing files.
|
|
@@ -16,6 +16,21 @@ from content_sdk.resources import TERMINAL_STATUSES
|
|
|
16
16
|
from content_cli import __version__
|
|
17
17
|
from content_cli.builders import audio_request, video_request
|
|
18
18
|
|
|
19
|
+
# Exit codes for a watched job (ADR 0021). Three outcomes, three codes: a
|
|
20
|
+
# script chaining on success must not proceed as if everything arrived, and it
|
|
21
|
+
# should still be able to tell "some of it worked" from "none of it did".
|
|
22
|
+
EXIT_OK = 0
|
|
23
|
+
EXIT_FAILED = 1
|
|
24
|
+
EXIT_PARTIAL = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _exit_code_for(status: str) -> int:
|
|
28
|
+
if status == "succeeded":
|
|
29
|
+
return EXIT_OK
|
|
30
|
+
if status == "partially_succeeded":
|
|
31
|
+
return EXIT_PARTIAL
|
|
32
|
+
return EXIT_FAILED
|
|
33
|
+
|
|
19
34
|
|
|
20
35
|
def _out(obj, as_json: bool) -> None:
|
|
21
36
|
print(json.dumps(obj, indent=2, ensure_ascii=False) if as_json else obj)
|
|
@@ -29,8 +44,32 @@ def _print_job(job: dict) -> None:
|
|
|
29
44
|
|
|
30
45
|
|
|
31
46
|
def _watch(client: ContentClient, job_id: str) -> str:
|
|
32
|
-
"""
|
|
47
|
+
"""Follow a job's events until it reaches a terminal state.
|
|
48
|
+
|
|
49
|
+
Uses the engine's event stream rather than asking again on a timer: a
|
|
50
|
+
forty-minute download deserves a progress line that moves when something
|
|
51
|
+
moves. The stream ends by itself when the job does.
|
|
52
|
+
|
|
53
|
+
Falls back to polling if the stream cannot be established — an old engine,
|
|
54
|
+
or a proxy that buffers SSE — because watching a job must not depend on it.
|
|
55
|
+
"""
|
|
33
56
|
seen = 0
|
|
57
|
+
try:
|
|
58
|
+
for event in client.stream_events(job_id):
|
|
59
|
+
seen = event["sequence"]
|
|
60
|
+
print(f" {seen:>3} {event['type']} {event['data'] or ''}")
|
|
61
|
+
except Exception as exc: # noqa: BLE001 — any stream failure falls back below
|
|
62
|
+
# Say so rather than degrade silently: a user who sees updates arrive
|
|
63
|
+
# every two seconds instead of instantly deserves to know the stream
|
|
64
|
+
# was refused, and by what.
|
|
65
|
+
print(
|
|
66
|
+
f"note: event stream unavailable ({exc}); polling instead", file=sys.stderr
|
|
67
|
+
)
|
|
68
|
+
return _watch_by_polling(client, job_id, seen)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _watch_by_polling(client: ContentClient, job_id: str, seen: int = 0) -> str:
|
|
72
|
+
"""The original loop, kept as the fallback and as the terminal read."""
|
|
34
73
|
while True:
|
|
35
74
|
for event in client.events(job_id, after_sequence=seen):
|
|
36
75
|
seen = event.sequence
|
|
@@ -38,6 +77,11 @@ def _watch(client: ContentClient, job_id: str) -> str:
|
|
|
38
77
|
status = client.get_job(job_id).status
|
|
39
78
|
if status in TERMINAL_STATUSES:
|
|
40
79
|
print(f"→ {status}")
|
|
80
|
+
if status == "partially_succeeded":
|
|
81
|
+
print(
|
|
82
|
+
f" some steps failed — inspect them with: content job {job_id}",
|
|
83
|
+
file=sys.stderr,
|
|
84
|
+
)
|
|
41
85
|
return status
|
|
42
86
|
time.sleep(2.0)
|
|
43
87
|
|
|
@@ -49,7 +93,7 @@ def _submit_and_maybe_watch(client: ContentClient, request: dict, args) -> int:
|
|
|
49
93
|
print(job.id)
|
|
50
94
|
if getattr(args, "watch", False):
|
|
51
95
|
status = _watch(client, job.id)
|
|
52
|
-
return
|
|
96
|
+
return _exit_code_for(status)
|
|
53
97
|
return 0
|
|
54
98
|
|
|
55
99
|
|
|
@@ -239,7 +283,7 @@ def run(argv: list[str], client: ContentClient) -> int:
|
|
|
239
283
|
_out(job, True) if args.json else _print_job(job)
|
|
240
284
|
elif cmd == "watch":
|
|
241
285
|
status = _watch(client, args.job_id)
|
|
242
|
-
return
|
|
286
|
+
return _exit_code_for(status)
|
|
243
287
|
elif cmd == "artifacts":
|
|
244
288
|
arts = client.artifacts(args.job_id)
|
|
245
289
|
if args.json:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "content-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Command-line client for the Content engine (talks to /api/v1)."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-or-later"
|
|
@@ -24,7 +24,7 @@ classifiers = [
|
|
|
24
24
|
# Pinned to the exact SDK release, not a floor: the monorepo ships one version
|
|
25
25
|
# for everything (`make version`), and a CLI resolving to a different SDK than
|
|
26
26
|
# the one it was tested against is precisely the drift that guarantees buys.
|
|
27
|
-
dependencies = ["content-sdk==0.
|
|
27
|
+
dependencies = ["content-sdk==0.6.0"]
|
|
28
28
|
|
|
29
29
|
[project.scripts]
|
|
30
30
|
content = "content_cli.cli:main"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|