cdx-manager 0.12.3 → 0.14.0
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.
- package/README.md +135 -6
- package/changelogs/CHANGELOGS_0_12_3.md +5 -0
- package/changelogs/CHANGELOGS_0_12_4.md +18 -0
- package/changelogs/CHANGELOGS_0_14_0.md +146 -0
- package/package.json +3 -3
- package/pyproject.toml +1 -1
- package/src/cli.py +65 -5
- package/src/cli_args.py +274 -33
- package/src/cli_commands.py +251 -3049
- package/src/cli_helpers.py +177 -2
- package/src/commands/__init__.py +6 -0
- package/src/commands/auth.py +224 -0
- package/src/commands/backup.py +112 -0
- package/src/commands/context_memory.py +341 -0
- package/src/commands/launch.py +218 -0
- package/src/commands/lifecycle.py +154 -0
- package/src/commands/maintenance.py +723 -0
- package/src/commands/runs.py +562 -0
- package/src/commands/settings.py +107 -0
- package/src/commands/status.py +681 -0
- package/src/config.py +41 -0
- package/src/errors.py +17 -0
- package/src/health.py +151 -3
- package/src/notify.py +11 -5
- package/src/provider_runtime.py +46 -11
- package/src/run_command.py +112 -4
- package/src/run_registry.py +53 -2
- package/src/session_backup.py +343 -0
- package/src/session_helpers.py +179 -0
- package/src/session_ranking.py +259 -0
- package/src/session_service.py +574 -1402
- package/src/session_status.py +534 -0
- package/src/status_view.py +15 -34
package/src/cli_args.py
CHANGED
|
@@ -7,12 +7,20 @@ imported back there and used by the handlers.
|
|
|
7
7
|
|
|
8
8
|
from datetime import datetime, timedelta
|
|
9
9
|
|
|
10
|
-
from .config import
|
|
11
|
-
|
|
10
|
+
from .config import (
|
|
11
|
+
PERMISSION_ALIASES,
|
|
12
|
+
PERMISSION_INPUT_VALUES,
|
|
13
|
+
PERMISSION_VALUES,
|
|
14
|
+
PROVIDER_CODEX,
|
|
15
|
+
PROVIDERS,
|
|
16
|
+
REASONING_EFFORT_VALUES,
|
|
17
|
+
normalize_permission,
|
|
18
|
+
)
|
|
19
|
+
from .errors import CdxArgumentError, CdxError
|
|
12
20
|
from .provider_runtime import _normalize_reasoning_effort
|
|
13
21
|
|
|
14
22
|
STATUS_USAGE = "Usage: cdx status [--json] [--refresh|--cached] [--timeout SECONDS] | cdx status --small|-s [--refresh|--cached] [--timeout SECONDS] | cdx status <name> [--json] [--refresh|--cached] [--timeout SECONDS]"
|
|
15
|
-
DOCTOR_USAGE = "Usage: cdx doctor [--severity OK|WARN|FAIL[,OK|WARN|FAIL...]] [--json]"
|
|
23
|
+
DOCTOR_USAGE = "Usage: cdx doctor [--severity OK|WARN|FAIL[,OK|WARN|FAIL...]] [--check-provider-flags] [--json]"
|
|
16
24
|
DISK_USAGE = "Usage: cdx disk [profiles] [--candidates] [--json]"
|
|
17
25
|
REPAIR_USAGE = "Usage: cdx repair [--dry-run] [--force] [--json]"
|
|
18
26
|
UPDATE_USAGE = "Usage: cdx update [all] [--check] [--yes] [--json] [--version TAG]"
|
|
@@ -34,17 +42,36 @@ RESUME_USAGE = "Usage: cdx resume <name> [--json]"
|
|
|
34
42
|
CAN_RESUME_USAGE = "Usage: cdx can-resume <name> [--json]"
|
|
35
43
|
SELECT_USAGE = "Usage: cdx select --provider PROVIDER [--min-reasoning-effort minimal|low|medium|high|xhigh] [--min-power minimal|low|medium|high|xhigh] [--require-ready] [--refresh] --json"
|
|
36
44
|
NEXT_USAGE = "Usage: cdx next [--json] [--refresh]"
|
|
37
|
-
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] --json"
|
|
45
|
+
RUN_USAGE = "Usage: cdx run [session] --cwd PATH (--prompt-file PATH|--prompt TEXT|--prompt-file -) [--provider PROVIDER] [--model MODEL] [--kind assistant|code-review] [--reasoning-effort minimal|low|medium|high|xhigh] [--power minimal|low|medium|high|xhigh] [--permission review|default|auto|full|workspace-write|read-only|danger-full-access] [--timeout-seconds N] [--detach] [--refresh] --json"
|
|
38
46
|
RUN_JSON_REQUIRED = "cdx run: --json is required."
|
|
39
47
|
RUN_TARGET_REQUIRED = "cdx run: specify a session name or --provider PROVIDER."
|
|
40
48
|
RUN_SESSION_PROVIDER_CONFLICT = "cdx run: cannot specify both a session name and --provider."
|
|
41
49
|
RUN_CWD_REQUIRED = "cdx run: --cwd PATH is required."
|
|
42
50
|
RUN_PROMPT_SOURCE_REQUIRED = "cdx run: specify exactly one prompt source: --prompt TEXT or --prompt-file PATH."
|
|
43
51
|
RUN_KIND_VALUES = ("assistant", "code-review")
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
# Aliases of the shared definitions in config, kept only so the existing
|
|
53
|
+
# RUN_* spellings in this module keep reading naturally. They are references,
|
|
54
|
+
# not copies: config.py is the single owner.
|
|
55
|
+
RUN_PERMISSION_VALUES = PERMISSION_INPUT_VALUES
|
|
56
|
+
RUN_PERMISSION_CANONICAL_VALUES = PERMISSION_VALUES
|
|
57
|
+
RUN_PERMISSION_ALIASES = PERMISSION_ALIASES
|
|
58
|
+
RUN_EFFORT_VALUES = REASONING_EFFORT_VALUES
|
|
59
|
+
RUNS_USAGE = "Usage: cdx runs [--limit N] [--since 7d|today|DATE] --json"
|
|
46
60
|
RUN_STATUS_USAGE = "Usage: cdx run-status <run_id> --json"
|
|
47
61
|
RUN_REPORT_USAGE = "Usage: cdx run-report <run_id> --json"
|
|
62
|
+
RUN_TAIL_USAGE = "Usage: cdx run-tail <run_id> [--lines N] --json"
|
|
63
|
+
SCHEMA_USAGE = "Usage: cdx schema --json"
|
|
64
|
+
|
|
65
|
+
RUN_TAIL_DEFAULT_LINES = 50
|
|
66
|
+
RUN_TAIL_MAX_LINES = 2000
|
|
67
|
+
|
|
68
|
+
# Argument-failure codes emitted in `error.code`. Specific enough that a caller
|
|
69
|
+
# branches on the code alone, never on the human message.
|
|
70
|
+
ARG_CODE_MISSING = "missing_required_argument"
|
|
71
|
+
ARG_CODE_MUTUALLY_EXCLUSIVE = "mutually_exclusive_arguments"
|
|
72
|
+
ARG_CODE_INVALID_VALUE = "invalid_argument_value"
|
|
73
|
+
ARG_CODE_OUT_OF_RANGE = "argument_value_out_of_range"
|
|
74
|
+
ARG_CODE_UNKNOWN = "unknown_argument"
|
|
48
75
|
|
|
49
76
|
|
|
50
77
|
def _parse_json_flag(args):
|
|
@@ -54,7 +81,7 @@ def _parse_json_flag(args):
|
|
|
54
81
|
|
|
55
82
|
|
|
56
83
|
def _parse_doctor_args(args):
|
|
57
|
-
parsed = {"json": False, "severity": None}
|
|
84
|
+
parsed = {"json": False, "severity": None, "check_provider_flags": False}
|
|
58
85
|
index = 0
|
|
59
86
|
while index < len(args):
|
|
60
87
|
arg = args[index]
|
|
@@ -62,6 +89,10 @@ def _parse_doctor_args(args):
|
|
|
62
89
|
parsed["json"] = True
|
|
63
90
|
index += 1
|
|
64
91
|
continue
|
|
92
|
+
if arg == "--check-provider-flags":
|
|
93
|
+
parsed["check_provider_flags"] = True
|
|
94
|
+
index += 1
|
|
95
|
+
continue
|
|
65
96
|
if arg == "--severity":
|
|
66
97
|
if parsed["severity"] is not None or index + 1 >= len(args):
|
|
67
98
|
raise CdxError(DOCTOR_USAGE)
|
|
@@ -338,7 +369,11 @@ def _parse_run_timeout_seconds(value):
|
|
|
338
369
|
try:
|
|
339
370
|
return _parse_timeout_seconds(value, RUN_USAGE)
|
|
340
371
|
except CdxError as error:
|
|
341
|
-
raise
|
|
372
|
+
raise CdxArgumentError(
|
|
373
|
+
f"cdx run: --timeout-seconds must be a positive number; got {value!r}.",
|
|
374
|
+
code=ARG_CODE_OUT_OF_RANGE,
|
|
375
|
+
arguments=["--timeout-seconds"],
|
|
376
|
+
) from error
|
|
342
377
|
|
|
343
378
|
|
|
344
379
|
def _parse_run_provider(value):
|
|
@@ -346,27 +381,168 @@ def _parse_run_provider(value):
|
|
|
346
381
|
return _parse_provider_filter(value, RUN_USAGE)
|
|
347
382
|
except CdxError as error:
|
|
348
383
|
allowed = "|".join(PROVIDERS)
|
|
349
|
-
raise
|
|
384
|
+
raise CdxArgumentError(
|
|
385
|
+
f"cdx run: invalid --provider {value!r}; allowed values: {allowed}.",
|
|
386
|
+
code=ARG_CODE_INVALID_VALUE,
|
|
387
|
+
arguments=["--provider"],
|
|
388
|
+
allowed=PROVIDERS,
|
|
389
|
+
) from error
|
|
350
390
|
|
|
351
391
|
|
|
352
392
|
def _normalize_run_permission(value):
|
|
353
393
|
if value is None:
|
|
354
394
|
return None
|
|
355
|
-
text =
|
|
356
|
-
|
|
357
|
-
"workspace-write": "default",
|
|
358
|
-
"read-only": "review",
|
|
359
|
-
"danger-full-access": "full",
|
|
360
|
-
}
|
|
361
|
-
text = aliases.get(text, text)
|
|
362
|
-
if text not in ("review", "default", "auto", "full"):
|
|
395
|
+
text = normalize_permission(value)
|
|
396
|
+
if text is None:
|
|
363
397
|
allowed = "|".join(RUN_PERMISSION_VALUES)
|
|
364
|
-
raise
|
|
398
|
+
raise CdxArgumentError(
|
|
399
|
+
f"cdx run: invalid --permission {value!r}; allowed values: {allowed}.",
|
|
400
|
+
code=ARG_CODE_INVALID_VALUE,
|
|
401
|
+
arguments=["--permission"],
|
|
402
|
+
allowed=RUN_PERMISSION_VALUES,
|
|
403
|
+
)
|
|
365
404
|
return text
|
|
366
405
|
|
|
367
406
|
|
|
368
|
-
def
|
|
407
|
+
def cdx_schema():
|
|
408
|
+
"""The machine-readable contract for programmatic callers.
|
|
409
|
+
|
|
410
|
+
Every value here is the same object the parser validates against, not a
|
|
411
|
+
parallel copy — `cdx schema --json` cannot advertise a value `cdx run`
|
|
412
|
+
rejects, which is exactly how the ollama `--experimental-yolo` mapping
|
|
413
|
+
survived unexercised (GitHub issue #8).
|
|
414
|
+
"""
|
|
415
|
+
# No `schema_version` here: the JSON envelope already carries it, and two
|
|
416
|
+
# keys of that name in one payload would be one too many.
|
|
417
|
+
return {
|
|
418
|
+
"enums": {
|
|
419
|
+
"permission": {
|
|
420
|
+
"accepted": list(RUN_PERMISSION_VALUES),
|
|
421
|
+
"canonical": list(RUN_PERMISSION_CANONICAL_VALUES),
|
|
422
|
+
"aliases": dict(RUN_PERMISSION_ALIASES),
|
|
423
|
+
},
|
|
424
|
+
"reasoning_effort": {"accepted": list(RUN_EFFORT_VALUES)},
|
|
425
|
+
"power": {"accepted": list(RUN_EFFORT_VALUES)},
|
|
426
|
+
"kind": {"accepted": list(RUN_KIND_VALUES)},
|
|
427
|
+
"provider": {"accepted": list(PROVIDERS)},
|
|
428
|
+
},
|
|
429
|
+
"mutually_exclusive": [
|
|
430
|
+
{
|
|
431
|
+
"command": "run",
|
|
432
|
+
"arguments": ["session", "--provider"],
|
|
433
|
+
"reason": "A session already fixes its provider; pass one or the other.",
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"command": "run",
|
|
437
|
+
"arguments": ["--prompt-file", "--prompt"],
|
|
438
|
+
"reason": "Exactly one prompt source.",
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
# Not mutually exclusive: both may be given, and cdx accepts them when
|
|
442
|
+
# they agree. A caller generating validation from `mutually_exclusive`
|
|
443
|
+
# would otherwise reject a command line cdx would have run.
|
|
444
|
+
"must_match": [
|
|
445
|
+
{
|
|
446
|
+
"command": "run",
|
|
447
|
+
"arguments": ["--reasoning-effort", "--power"],
|
|
448
|
+
"reason": "Aliases of one setting; supplying both with different values fails.",
|
|
449
|
+
},
|
|
450
|
+
],
|
|
451
|
+
# Every code a run-family command can put in `error.code`, not just the
|
|
452
|
+
# argument ones — a caller matching exhaustively over this list must not
|
|
453
|
+
# fall through on a code it will certainly see.
|
|
454
|
+
"error_codes": {
|
|
455
|
+
"arguments": [
|
|
456
|
+
ARG_CODE_MISSING,
|
|
457
|
+
ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
458
|
+
ARG_CODE_INVALID_VALUE,
|
|
459
|
+
ARG_CODE_OUT_OF_RANGE,
|
|
460
|
+
ARG_CODE_UNKNOWN,
|
|
461
|
+
"invalid_reasoning_effort",
|
|
462
|
+
],
|
|
463
|
+
"run": [
|
|
464
|
+
"invalid_cwd",
|
|
465
|
+
"session_disabled",
|
|
466
|
+
"no_suitable_session",
|
|
467
|
+
"provider_cli_not_found",
|
|
468
|
+
"provider_start_failed",
|
|
469
|
+
"provider_failed",
|
|
470
|
+
"provider_timeout",
|
|
471
|
+
"cdx_error",
|
|
472
|
+
],
|
|
473
|
+
"run_lookup": [
|
|
474
|
+
"run_not_found",
|
|
475
|
+
"run_output_unavailable",
|
|
476
|
+
"run_output_unreadable",
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
"warning_codes": [
|
|
480
|
+
"network_disabled_by_permission",
|
|
481
|
+
"session_selected_without_status",
|
|
482
|
+
"limit_ignored_with_since",
|
|
483
|
+
],
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _parse_schema_args(args):
|
|
369
488
|
parsed = _parse_flag_args(args, {
|
|
489
|
+
"--json": {"key": "json", "type": "bool", "default": False},
|
|
490
|
+
}, SCHEMA_USAGE)
|
|
491
|
+
if not parsed["json"]:
|
|
492
|
+
raise CdxError(SCHEMA_USAGE)
|
|
493
|
+
return parsed
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _parse_run_flag_args(args, schema, usage, positionals_key=None, max_positionals=0):
|
|
497
|
+
"""`_parse_flag_args` is shared by every command and raises a bare usage
|
|
498
|
+
error for an unrecognized flag or a value-less option. Only `run` promises a
|
|
499
|
+
specific code for those, so translate here rather than teaching the shared
|
|
500
|
+
parser about codes it does not need. Transforms that already raised a
|
|
501
|
+
`CdxArgumentError` keep their own, more precise code."""
|
|
502
|
+
try:
|
|
503
|
+
return _parse_flag_args(args, schema, usage, positionals_key, max_positionals)
|
|
504
|
+
except CdxArgumentError:
|
|
505
|
+
raise
|
|
506
|
+
except CdxError as error:
|
|
507
|
+
raise CdxArgumentError(str(error), code=ARG_CODE_UNKNOWN) from error
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _parse_run_effort(reasoning_effort, power):
|
|
511
|
+
try:
|
|
512
|
+
return _normalize_reasoning_effort(
|
|
513
|
+
reasoning_effort=reasoning_effort,
|
|
514
|
+
power=power,
|
|
515
|
+
usage=RUN_USAGE,
|
|
516
|
+
)
|
|
517
|
+
except CdxError as error:
|
|
518
|
+
message = str(error)
|
|
519
|
+
if "must match" in message:
|
|
520
|
+
raise CdxArgumentError(
|
|
521
|
+
message,
|
|
522
|
+
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
523
|
+
arguments=["--reasoning-effort", "--power"],
|
|
524
|
+
) from error
|
|
525
|
+
# An empty value raises the bare usage line, which names neither flag.
|
|
526
|
+
# Blame the one actually supplied rather than guessing, since naming the
|
|
527
|
+
# offending argument is the entire point of this error.
|
|
528
|
+
if message.startswith("Unsupported power:") or (power is not None and reasoning_effort is None):
|
|
529
|
+
argument = "--power"
|
|
530
|
+
else:
|
|
531
|
+
argument = "--reasoning-effort"
|
|
532
|
+
# Keeps the pre-existing `invalid_reasoning_effort` code rather than
|
|
533
|
+
# flattening it into the generic invalid-value one: it was already
|
|
534
|
+
# specific, and callers may already branch on it. The new codes exist to
|
|
535
|
+
# split what used to be indistinguishable, not to rename what wasn't.
|
|
536
|
+
raise CdxArgumentError(
|
|
537
|
+
message,
|
|
538
|
+
code="invalid_reasoning_effort",
|
|
539
|
+
arguments=[argument],
|
|
540
|
+
allowed=RUN_EFFORT_VALUES,
|
|
541
|
+
) from error
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _parse_run_args(args):
|
|
545
|
+
parsed = _parse_run_flag_args(args, {
|
|
370
546
|
"--cwd": {"key": "cwd", "type": "str", "default": None},
|
|
371
547
|
"--prompt-file": {"key": "prompt_file", "type": "str", "default": None},
|
|
372
548
|
"--prompt": {"key": "prompt", "type": "str", "default": None},
|
|
@@ -377,27 +553,44 @@ def _parse_run_args(args):
|
|
|
377
553
|
"--power": {"key": "power", "type": "str", "default": None},
|
|
378
554
|
"--permission": {"key": "permission", "type": "str", "default": None, "transform": _normalize_run_permission},
|
|
379
555
|
"--timeout-seconds": {"key": "timeout_seconds", "type": "str", "default": None, "transform": _parse_run_timeout_seconds},
|
|
556
|
+
"--detach": {"key": "detach", "type": "bool", "default": False},
|
|
557
|
+
"--refresh": {"key": "refresh", "type": "bool", "default": False},
|
|
380
558
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
381
559
|
}, RUN_USAGE, positionals_key="names", max_positionals=1)
|
|
382
560
|
if not parsed["json"]:
|
|
383
|
-
raise
|
|
561
|
+
raise CdxArgumentError(RUN_JSON_REQUIRED, code=ARG_CODE_MISSING, arguments=["--json"])
|
|
384
562
|
name = parsed["names"][0] if parsed["names"] else None
|
|
385
563
|
if name and parsed["provider"]:
|
|
386
|
-
raise
|
|
564
|
+
raise CdxArgumentError(
|
|
565
|
+
RUN_SESSION_PROVIDER_CONFLICT,
|
|
566
|
+
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
567
|
+
arguments=["session", "--provider"],
|
|
568
|
+
)
|
|
387
569
|
if not name and not parsed["provider"]:
|
|
388
|
-
raise
|
|
570
|
+
raise CdxArgumentError(RUN_TARGET_REQUIRED, code=ARG_CODE_MISSING, arguments=["session", "--provider"])
|
|
389
571
|
if not parsed["cwd"]:
|
|
390
|
-
raise
|
|
391
|
-
if
|
|
392
|
-
raise
|
|
572
|
+
raise CdxArgumentError(RUN_CWD_REQUIRED, code=ARG_CODE_MISSING, arguments=["--cwd"])
|
|
573
|
+
if parsed["prompt_file"] and parsed["prompt"]:
|
|
574
|
+
raise CdxArgumentError(
|
|
575
|
+
RUN_PROMPT_SOURCE_REQUIRED,
|
|
576
|
+
code=ARG_CODE_MUTUALLY_EXCLUSIVE,
|
|
577
|
+
arguments=["--prompt-file", "--prompt"],
|
|
578
|
+
)
|
|
579
|
+
if not parsed["prompt_file"] and not parsed["prompt"]:
|
|
580
|
+
raise CdxArgumentError(
|
|
581
|
+
RUN_PROMPT_SOURCE_REQUIRED,
|
|
582
|
+
code=ARG_CODE_MISSING,
|
|
583
|
+
arguments=["--prompt-file", "--prompt"],
|
|
584
|
+
)
|
|
393
585
|
if parsed["kind"] not in RUN_KIND_VALUES:
|
|
394
586
|
allowed = "|".join(RUN_KIND_VALUES)
|
|
395
|
-
raise
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
587
|
+
raise CdxArgumentError(
|
|
588
|
+
f"cdx run: invalid --kind {parsed['kind']!r}; allowed values: {allowed}.",
|
|
589
|
+
code=ARG_CODE_INVALID_VALUE,
|
|
590
|
+
arguments=["--kind"],
|
|
591
|
+
allowed=RUN_KIND_VALUES,
|
|
592
|
+
)
|
|
593
|
+
effort = _parse_run_effort(parsed["reasoning_effort"], parsed["power"])
|
|
401
594
|
return {
|
|
402
595
|
"name": name,
|
|
403
596
|
"provider": parsed["provider"],
|
|
@@ -408,6 +601,8 @@ def _parse_run_args(args):
|
|
|
408
601
|
"model": parsed["model"],
|
|
409
602
|
"permission": parsed["permission"],
|
|
410
603
|
"timeout_seconds": parsed["timeout_seconds"],
|
|
604
|
+
"detach": parsed["detach"],
|
|
605
|
+
"refresh": parsed["refresh"],
|
|
411
606
|
"reasoning_effort": effort.get("reasoning_effort"),
|
|
412
607
|
"power": effort.get("power"),
|
|
413
608
|
}
|
|
@@ -709,11 +904,28 @@ def _parse_import_args(args):
|
|
|
709
904
|
return parsed
|
|
710
905
|
|
|
711
906
|
|
|
712
|
-
def _parse_runs_args(rest):
|
|
713
|
-
|
|
907
|
+
def _parse_runs_args(rest, now=None):
|
|
908
|
+
now = now or datetime.now().astimezone()
|
|
909
|
+
parsed = _parse_flag_args(rest, {
|
|
714
910
|
"--limit": {"key": "limit", "type": "str", "default": 20, "transform": _parse_positive_int},
|
|
911
|
+
"--since": {"key": "since", "type": "str", "default": None},
|
|
715
912
|
"--json": {"key": "json", "type": "bool", "default": False},
|
|
716
913
|
}, RUNS_USAGE, max_positionals=0)
|
|
914
|
+
since = None
|
|
915
|
+
if parsed["since"] is not None:
|
|
916
|
+
# Same cursor forms as `cdx history --since`, parsed by the same code:
|
|
917
|
+
# a caller that learned one syntax does not learn a second one.
|
|
918
|
+
since = _parse_since_value(parsed["since"], now, RUNS_USAGE)
|
|
919
|
+
return {
|
|
920
|
+
"limit": parsed["limit"],
|
|
921
|
+
"since": since,
|
|
922
|
+
# A cursor selects by completion time, not by row count. Keeping the
|
|
923
|
+
# caller's own --limit would silently reintroduce the miss it removes:
|
|
924
|
+
# a watchdog behind by more than `limit` completions would never see
|
|
925
|
+
# the older ones at all.
|
|
926
|
+
"limit_given": any(arg == "--limit" or arg.startswith("--limit=") for arg in rest),
|
|
927
|
+
"json": parsed["json"],
|
|
928
|
+
}
|
|
717
929
|
|
|
718
930
|
|
|
719
931
|
def _parse_run_id_json_args(rest, usage):
|
|
@@ -723,3 +935,32 @@ def _parse_run_id_json_args(rest, usage):
|
|
|
723
935
|
if not parsed["json"] or len(parsed["ids"]) != 1:
|
|
724
936
|
raise CdxError(usage)
|
|
725
937
|
return {"run_id": parsed["ids"][0], "json": True}
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _parse_run_tail_args(rest):
|
|
941
|
+
parsed = _parse_flag_args(rest, {
|
|
942
|
+
"--lines": {"key": "lines", "type": "str", "default": RUN_TAIL_DEFAULT_LINES,
|
|
943
|
+
"transform": _parse_run_tail_lines},
|
|
944
|
+
"--json": {"key": "json", "type": "bool", "default": False},
|
|
945
|
+
}, RUN_TAIL_USAGE, positionals_key="ids", max_positionals=1)
|
|
946
|
+
if not parsed["json"] or len(parsed["ids"]) != 1:
|
|
947
|
+
raise CdxError(RUN_TAIL_USAGE)
|
|
948
|
+
return {"run_id": parsed["ids"][0], "lines": parsed["lines"], "json": True}
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
def _parse_run_tail_lines(value):
|
|
952
|
+
try:
|
|
953
|
+
parsed = int(value)
|
|
954
|
+
except (TypeError, ValueError) as error:
|
|
955
|
+
raise CdxArgumentError(
|
|
956
|
+
f"cdx run-tail: --lines must be an integer; got {value!r}.",
|
|
957
|
+
code=ARG_CODE_INVALID_VALUE,
|
|
958
|
+
arguments=["--lines"],
|
|
959
|
+
) from error
|
|
960
|
+
if not 1 <= parsed <= RUN_TAIL_MAX_LINES:
|
|
961
|
+
raise CdxArgumentError(
|
|
962
|
+
f"cdx run-tail: --lines must be between 1 and {RUN_TAIL_MAX_LINES}; got {parsed}.",
|
|
963
|
+
code=ARG_CODE_OUT_OF_RANGE,
|
|
964
|
+
arguments=["--lines"],
|
|
965
|
+
)
|
|
966
|
+
return parsed
|