logion-cli 0.1.0__py3-none-any.whl
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.
- cli/__init__.py +2 -0
- cli/_config.py +51 -0
- cli/_confirm.py +16 -0
- cli/_context.py +17 -0
- cli/_course_bundle.py +46 -0
- cli/_course_capabilities.py +580 -0
- cli/_credentials.py +104 -0
- cli/_errors.py +82 -0
- cli/_first_run.py +90 -0
- cli/_harness/__init__.py +68 -0
- cli/_harness/base.py +106 -0
- cli/_harness/claude_code.py +168 -0
- cli/_harness/codex.py +79 -0
- cli/_harness/custom.py +55 -0
- cli/_harness/hermes.py +93 -0
- cli/_harness/opencode.py +255 -0
- cli/_local_state.py +1053 -0
- cli/_options.py +36 -0
- cli/_output.py +47 -0
- cli/_parser.py +73 -0
- cli/_recall_calibration.py +90 -0
- cli/_recall_ranker.py +74 -0
- cli/_taxonomy.py +120 -0
- cli/_update_policy.py +152 -0
- cli/_utils.py +16 -0
- cli/_version.py +26 -0
- cli/commands/__init__.py +2 -0
- cli/commands/admin.py +535 -0
- cli/commands/bounties.py +490 -0
- cli/commands/course_reviews/__init__.py +6 -0
- cli/commands/course_reviews/_download_handler.py +104 -0
- cli/commands/course_reviews/_render.py +129 -0
- cli/commands/course_reviews/handlers.py +197 -0
- cli/commands/course_reviews/parser.py +93 -0
- cli/commands/courses/__init__.py +6 -0
- cli/commands/courses/_capability_render.py +183 -0
- cli/commands/courses/_cmd_help.py +18 -0
- cli/commands/courses/_purchase.py +76 -0
- cli/commands/courses/_review_helpers.py +93 -0
- cli/commands/courses/_taxonomy_data.py +173 -0
- cli/commands/courses/_upload_bundle_validation.py +28 -0
- cli/commands/courses/_uploads_push.py +243 -0
- cli/commands/courses/capabilities.py +250 -0
- cli/commands/courses/capability_frontmatter.py +150 -0
- cli/commands/courses/handlers.py +50 -0
- cli/commands/courses/mutations.py +217 -0
- cli/commands/courses/parser.py +66 -0
- cli/commands/courses/parser_capabilities.py +95 -0
- cli/commands/courses/parser_sections.py +239 -0
- cli/commands/courses/parser_uploads.py +84 -0
- cli/commands/courses/parser_utils.py +65 -0
- cli/commands/courses/publication.py +60 -0
- cli/commands/courses/report_usage.py +131 -0
- cli/commands/courses/reviews.py +237 -0
- cli/commands/courses/taxonomy_handler.py +61 -0
- cli/commands/courses/taxonomy_suggest.py +197 -0
- cli/commands/courses/uploads.py +142 -0
- cli/commands/courses/versions.py +65 -0
- cli/commands/credits/__init__.py +6 -0
- cli/commands/credits/_helpers.py +153 -0
- cli/commands/credits/handlers.py +218 -0
- cli/commands/credits/parser.py +115 -0
- cli/commands/docs/__init__.py +6 -0
- cli/commands/docs/handlers.py +137 -0
- cli/commands/docs/parser.py +27 -0
- cli/commands/health/__init__.py +6 -0
- cli/commands/health/handlers.py +26 -0
- cli/commands/health/parser.py +20 -0
- cli/commands/identity/__init__.py +6 -0
- cli/commands/identity/_autopost.py +97 -0
- cli/commands/identity/_closing_copy.py +89 -0
- cli/commands/identity/_companion.py +232 -0
- cli/commands/identity/_companion_source.py +135 -0
- cli/commands/identity/_harness_select.py +85 -0
- cli/commands/identity/_onboarding_helpers.py +168 -0
- cli/commands/identity/handlers.py +173 -0
- cli/commands/identity/onboarding.py +246 -0
- cli/commands/identity/parser.py +72 -0
- cli/commands/listings/__init__.py +6 -0
- cli/commands/listings/handlers.py +135 -0
- cli/commands/listings/parser.py +57 -0
- cli/commands/notifications/__init__.py +6 -0
- cli/commands/notifications/handlers.py +120 -0
- cli/commands/notifications/parser.py +49 -0
- cli/commands/payments/__init__.py +6 -0
- cli/commands/payments/_orders_helpers.py +114 -0
- cli/commands/payments/handlers.py +138 -0
- cli/commands/payments/parser.py +97 -0
- cli/commands/recall/__init__.py +7 -0
- cli/commands/recall/handlers.py +87 -0
- cli/commands/recall/parser.py +70 -0
- cli/commands/referrals/__init__.py +6 -0
- cli/commands/referrals/_helpers.py +63 -0
- cli/commands/referrals/handlers.py +100 -0
- cli/commands/referrals/parser.py +65 -0
- cli/commands/reports/__init__.py +6 -0
- cli/commands/reports/handlers.py +57 -0
- cli/commands/reports/parser.py +52 -0
- cli/commands/skills/__init__.py +7 -0
- cli/commands/skills/_agent_symlink.py +161 -0
- cli/commands/skills/_finalize.py +112 -0
- cli/commands/skills/_inspect_handler.py +218 -0
- cli/commands/skills/_install_helpers.py +186 -0
- cli/commands/skills/_query_handlers.py +83 -0
- cli/commands/skills/_search_handler.py +136 -0
- cli/commands/skills/_update_handler.py +110 -0
- cli/commands/skills/_verify_handler.py +109 -0
- cli/commands/skills/handlers.py +202 -0
- cli/commands/skills/parser.py +154 -0
- cli/commands/workspace.py +406 -0
- cli/docs/README.md +5 -0
- cli/docs/__init__.py +1 -0
- cli/docs/bounties-and-referrals.md +18 -0
- cli/docs/concepts.md +47 -0
- cli/docs/creating-courses.md +25 -0
- cli/docs/credits-and-purchases.md +30 -0
- cli/docs/credits-terms.md +23 -0
- cli/docs/getting-started.md +95 -0
- cli/docs/marketplace-loop.md +108 -0
- cli/docs/privacy.md +30 -0
- cli/docs/referral-terms.md +24 -0
- cli/docs/reviews.md +47 -0
- cli/docs/safety.md +28 -0
- cli/docs/terms.md +54 -0
- cli/main.py +84 -0
- cli/templates/__init__.py +2 -0
- cli/templates/course_capabilities.template.yaml +189 -0
- cli/templates/course_license_apache-2.0.template.txt +30 -0
- cli/templates/course_license_logion-standard-course-v1.template.txt +49 -0
- cli/templates/course_license_mit.template.txt +21 -0
- logion_cli-0.1.0.dist-info/METADATA +49 -0
- logion_cli-0.1.0.dist-info/RECORD +135 -0
- logion_cli-0.1.0.dist-info/WHEEL +4 -0
- logion_cli-0.1.0.dist-info/entry_points.txt +4 -0
- logion_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Parser registration for courses commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
from cli._options import COMMON_PARSER
|
|
9
|
+
|
|
10
|
+
from .handlers import handle_feedback, handle_versions_get
|
|
11
|
+
from .parser_capabilities import register_capabilities
|
|
12
|
+
from .parser_sections import (
|
|
13
|
+
CMD_HELP,
|
|
14
|
+
register_create,
|
|
15
|
+
register_get,
|
|
16
|
+
register_publication,
|
|
17
|
+
register_purchase,
|
|
18
|
+
register_reviews,
|
|
19
|
+
register_update,
|
|
20
|
+
register_uploads,
|
|
21
|
+
)
|
|
22
|
+
from .report_usage import (
|
|
23
|
+
register_report_usage,
|
|
24
|
+
)
|
|
25
|
+
from .taxonomy_handler import register_taxonomy
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
29
|
+
"""Register the ``courses`` subcommand group."""
|
|
30
|
+
parser = subparsers.add_parser("courses", help="Manage courses")
|
|
31
|
+
sub = parser.add_subparsers(
|
|
32
|
+
dest="courses_command",
|
|
33
|
+
required=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
register_create(sub)
|
|
37
|
+
register_get(sub)
|
|
38
|
+
register_update(sub)
|
|
39
|
+
register_uploads(sub)
|
|
40
|
+
register_publication(sub)
|
|
41
|
+
register_purchase(sub)
|
|
42
|
+
register_reviews(sub)
|
|
43
|
+
register_report_usage(sub)
|
|
44
|
+
register_capabilities(sub)
|
|
45
|
+
register_taxonomy(sub)
|
|
46
|
+
feedback = sub.add_parser(
|
|
47
|
+
"feedback",
|
|
48
|
+
help=CMD_HELP["feedback"],
|
|
49
|
+
parents=[COMMON_PARSER],
|
|
50
|
+
)
|
|
51
|
+
feedback.add_argument("course_id", metavar="COURSE_ID")
|
|
52
|
+
feedback.set_defaults(handler=handle_feedback)
|
|
53
|
+
|
|
54
|
+
versions = sub.add_parser("versions", help=CMD_HELP["versions"])
|
|
55
|
+
versions_sub = versions.add_subparsers(
|
|
56
|
+
dest="courses_versions_command",
|
|
57
|
+
required=True,
|
|
58
|
+
)
|
|
59
|
+
get = versions_sub.add_parser(
|
|
60
|
+
"get",
|
|
61
|
+
help="Get a course version",
|
|
62
|
+
parents=[COMMON_PARSER],
|
|
63
|
+
)
|
|
64
|
+
get.add_argument("course_id", metavar="COURSE_ID")
|
|
65
|
+
get.add_argument("version_id", metavar="VERSION_ID")
|
|
66
|
+
get.set_defaults(handler=handle_versions_get)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Parser registration for course capability commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from cli._options import COMMON_PARSER
|
|
10
|
+
|
|
11
|
+
from .capabilities import (
|
|
12
|
+
handle_courses_capabilities_print,
|
|
13
|
+
handle_courses_capabilities_scaffold,
|
|
14
|
+
handle_courses_capabilities_validate,
|
|
15
|
+
)
|
|
16
|
+
from .parser_sections import CMD_HELP
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def register_capabilities(subparsers: argparse._SubParsersAction) -> None:
|
|
20
|
+
"""Register course capability manifest subcommands."""
|
|
21
|
+
capabilities = subparsers.add_parser(
|
|
22
|
+
"capabilities",
|
|
23
|
+
help=CMD_HELP["capabilities"],
|
|
24
|
+
)
|
|
25
|
+
capabilities_sub = capabilities.add_subparsers(
|
|
26
|
+
dest="courses_capabilities_command",
|
|
27
|
+
required=True,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
validate = capabilities_sub.add_parser(
|
|
31
|
+
"validate",
|
|
32
|
+
help="Validate a local capability manifest",
|
|
33
|
+
parents=[COMMON_PARSER],
|
|
34
|
+
)
|
|
35
|
+
validate.add_argument(
|
|
36
|
+
"--bundle-dir",
|
|
37
|
+
type=Path,
|
|
38
|
+
required=True,
|
|
39
|
+
help="Path to the course bundle directory",
|
|
40
|
+
)
|
|
41
|
+
validate.set_defaults(handler=handle_courses_capabilities_validate)
|
|
42
|
+
|
|
43
|
+
print_cmd = capabilities_sub.add_parser(
|
|
44
|
+
"print",
|
|
45
|
+
help="Print the normalised capability manifest as JSON",
|
|
46
|
+
parents=[COMMON_PARSER],
|
|
47
|
+
)
|
|
48
|
+
print_cmd.add_argument(
|
|
49
|
+
"--bundle-dir",
|
|
50
|
+
type=Path,
|
|
51
|
+
required=True,
|
|
52
|
+
help="Path to the course bundle directory",
|
|
53
|
+
)
|
|
54
|
+
print_cmd.set_defaults(handler=handle_courses_capabilities_print)
|
|
55
|
+
|
|
56
|
+
scaffold = capabilities_sub.add_parser(
|
|
57
|
+
"scaffold",
|
|
58
|
+
help="Write a commented course/capabilities.yaml scaffold",
|
|
59
|
+
parents=[COMMON_PARSER],
|
|
60
|
+
)
|
|
61
|
+
scaffold.add_argument(
|
|
62
|
+
"--bundle-dir",
|
|
63
|
+
type=Path,
|
|
64
|
+
default=None,
|
|
65
|
+
help=(
|
|
66
|
+
"Course bundle directory. If omitted the scaffold is "
|
|
67
|
+
"printed to stdout."
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
scaffold.add_argument(
|
|
71
|
+
"--force",
|
|
72
|
+
action="store_true",
|
|
73
|
+
help="Overwrite an existing course/capabilities.yaml",
|
|
74
|
+
)
|
|
75
|
+
scaffold.add_argument(
|
|
76
|
+
"--license-template",
|
|
77
|
+
choices=["mit", "apache-2.0", "logion-standard-course-v1"],
|
|
78
|
+
default=None,
|
|
79
|
+
help=(
|
|
80
|
+
"Write bundle-dir/LICENSE from the selected template. "
|
|
81
|
+
"When omitted, scaffold writes MIT by default."
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
scaffold.add_argument(
|
|
85
|
+
"--from-skill",
|
|
86
|
+
type=Path,
|
|
87
|
+
default=None,
|
|
88
|
+
metavar="SKILL_MD_PATH",
|
|
89
|
+
help=(
|
|
90
|
+
"Seed the scaffold from the metadata.logion capability "
|
|
91
|
+
"manifest in the given SKILL.md instead of emitting the "
|
|
92
|
+
"generic template."
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
scaffold.set_defaults(handler=handle_courses_capabilities_scaffold)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Subparser builders for courses commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
from cli._options import COMMON_PARSER
|
|
9
|
+
|
|
10
|
+
from ._cmd_help import CMD_HELP
|
|
11
|
+
from .handlers import (
|
|
12
|
+
handle_create,
|
|
13
|
+
handle_get,
|
|
14
|
+
handle_publication_latest,
|
|
15
|
+
handle_publication_request,
|
|
16
|
+
handle_purchase,
|
|
17
|
+
handle_reviews_list,
|
|
18
|
+
handle_reviews_mine,
|
|
19
|
+
handle_reviews_summary,
|
|
20
|
+
handle_reviews_upsert,
|
|
21
|
+
handle_update,
|
|
22
|
+
)
|
|
23
|
+
from .parser_uploads import register_uploads as _register_uploads
|
|
24
|
+
from .parser_utils import (
|
|
25
|
+
add_category_argument,
|
|
26
|
+
add_tag_arguments,
|
|
27
|
+
add_tristate_flag,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Re-export for existing imports.
|
|
31
|
+
register_uploads = _register_uploads
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def register_create(subparsers: argparse._SubParsersAction) -> None:
|
|
35
|
+
create = subparsers.add_parser(
|
|
36
|
+
"create",
|
|
37
|
+
help=CMD_HELP["create"],
|
|
38
|
+
parents=[COMMON_PARSER],
|
|
39
|
+
)
|
|
40
|
+
create.add_argument("--title", required=True)
|
|
41
|
+
create.add_argument("--slug", required=True)
|
|
42
|
+
create.add_argument("--description")
|
|
43
|
+
create.add_argument("--price-cents", type=int)
|
|
44
|
+
create.add_argument("--currency")
|
|
45
|
+
add_tag_arguments(create, default=[])
|
|
46
|
+
add_category_argument(create)
|
|
47
|
+
create.add_argument("--language")
|
|
48
|
+
create.add_argument("--short-summary")
|
|
49
|
+
create.add_argument(
|
|
50
|
+
"--visibility", choices=["public", "unlisted", "private"]
|
|
51
|
+
)
|
|
52
|
+
create.set_defaults(handler=handle_create)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register_get(subparsers: argparse._SubParsersAction) -> None:
|
|
56
|
+
get = subparsers.add_parser(
|
|
57
|
+
"get",
|
|
58
|
+
help=CMD_HELP["get"],
|
|
59
|
+
parents=[COMMON_PARSER],
|
|
60
|
+
)
|
|
61
|
+
get.add_argument("course_id", metavar="COURSE_ID")
|
|
62
|
+
get.set_defaults(handler=handle_get)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def register_update(subparsers: argparse._SubParsersAction) -> None:
|
|
66
|
+
update = subparsers.add_parser(
|
|
67
|
+
"update",
|
|
68
|
+
help=CMD_HELP["update"],
|
|
69
|
+
parents=[COMMON_PARSER],
|
|
70
|
+
)
|
|
71
|
+
update.add_argument("course_id", metavar="COURSE_ID")
|
|
72
|
+
update.add_argument("--title")
|
|
73
|
+
|
|
74
|
+
description = update.add_mutually_exclusive_group()
|
|
75
|
+
description.add_argument("--description")
|
|
76
|
+
description.add_argument(
|
|
77
|
+
"--clear-description",
|
|
78
|
+
action="store_true",
|
|
79
|
+
help="Clear the course description",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
price = update.add_mutually_exclusive_group()
|
|
83
|
+
price.add_argument("--price-cents", type=int)
|
|
84
|
+
price.add_argument(
|
|
85
|
+
"--clear-price",
|
|
86
|
+
action="store_true",
|
|
87
|
+
help=(
|
|
88
|
+
"Clear the course price (price_cents and currency). "
|
|
89
|
+
"Note: this also clears the currency."
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
currency = update.add_mutually_exclusive_group()
|
|
94
|
+
currency.add_argument("--currency")
|
|
95
|
+
currency.add_argument(
|
|
96
|
+
"--clear-currency",
|
|
97
|
+
action="store_true",
|
|
98
|
+
help="Clear the course currency",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
language = update.add_mutually_exclusive_group()
|
|
102
|
+
language.add_argument("--language")
|
|
103
|
+
language.add_argument(
|
|
104
|
+
"--clear-language",
|
|
105
|
+
action="store_true",
|
|
106
|
+
help="Clear the course language",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
short_summary = update.add_mutually_exclusive_group()
|
|
110
|
+
short_summary.add_argument("--short-summary")
|
|
111
|
+
short_summary.add_argument(
|
|
112
|
+
"--clear-short-summary",
|
|
113
|
+
action="store_true",
|
|
114
|
+
help="Clear the short summary",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
add_tag_arguments(update, default=None)
|
|
118
|
+
add_category_argument(update)
|
|
119
|
+
update.add_argument(
|
|
120
|
+
"--visibility", choices=["public", "unlisted", "private"]
|
|
121
|
+
)
|
|
122
|
+
update.set_defaults(handler=handle_update)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def register_publication(subparsers: argparse._SubParsersAction) -> None:
|
|
126
|
+
publication = subparsers.add_parser(
|
|
127
|
+
"publication",
|
|
128
|
+
help=CMD_HELP["publication"],
|
|
129
|
+
)
|
|
130
|
+
pub_sub = publication.add_subparsers(
|
|
131
|
+
dest="courses_publication_command",
|
|
132
|
+
required=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
request = pub_sub.add_parser(
|
|
136
|
+
"request",
|
|
137
|
+
help="Request publication review",
|
|
138
|
+
parents=[COMMON_PARSER],
|
|
139
|
+
)
|
|
140
|
+
request.add_argument("course_id", metavar="COURSE_ID")
|
|
141
|
+
request.set_defaults(handler=handle_publication_request)
|
|
142
|
+
|
|
143
|
+
latest = pub_sub.add_parser(
|
|
144
|
+
"latest",
|
|
145
|
+
help="Get latest publication review status",
|
|
146
|
+
parents=[COMMON_PARSER],
|
|
147
|
+
)
|
|
148
|
+
latest.add_argument("course_id", metavar="COURSE_ID")
|
|
149
|
+
add_tristate_flag(latest, "--include-pass", dest="include_pass")
|
|
150
|
+
latest.set_defaults(handler=handle_publication_latest)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def register_reviews(subparsers: argparse._SubParsersAction) -> None:
|
|
154
|
+
reviews = subparsers.add_parser("reviews", help=CMD_HELP["reviews"])
|
|
155
|
+
reviews_sub = reviews.add_subparsers(
|
|
156
|
+
dest="courses_reviews_command",
|
|
157
|
+
required=True,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
list_parser = reviews_sub.add_parser(
|
|
161
|
+
"list",
|
|
162
|
+
help="List reviews for a course",
|
|
163
|
+
parents=[COMMON_PARSER],
|
|
164
|
+
)
|
|
165
|
+
list_parser.add_argument("course_id", metavar="COURSE_ID")
|
|
166
|
+
list_parser.add_argument("--version")
|
|
167
|
+
list_parser.add_argument("--limit", type=int, default=5)
|
|
168
|
+
list_parser.add_argument("--cursor")
|
|
169
|
+
list_parser.set_defaults(handler=handle_reviews_list)
|
|
170
|
+
|
|
171
|
+
mine = reviews_sub.add_parser(
|
|
172
|
+
"mine",
|
|
173
|
+
help="Get your review for a course version",
|
|
174
|
+
parents=[COMMON_PARSER],
|
|
175
|
+
)
|
|
176
|
+
mine.add_argument("course_id", metavar="COURSE_ID")
|
|
177
|
+
mine.add_argument("--version-id")
|
|
178
|
+
mine.set_defaults(handler=handle_reviews_mine)
|
|
179
|
+
|
|
180
|
+
upsert = reviews_sub.add_parser(
|
|
181
|
+
"upsert",
|
|
182
|
+
help="Create or update a review for a course version",
|
|
183
|
+
parents=[COMMON_PARSER],
|
|
184
|
+
)
|
|
185
|
+
upsert.add_argument("course_id", metavar="COURSE_ID")
|
|
186
|
+
upsert.add_argument("version_id", metavar="VERSION_ID")
|
|
187
|
+
upsert.add_argument("--rating", type=int, required=True)
|
|
188
|
+
upsert.add_argument("--body")
|
|
189
|
+
add_tristate_flag(upsert, "--completed-task", dest="completed_task")
|
|
190
|
+
upsert.add_argument("--reliability", type=float)
|
|
191
|
+
upsert.add_argument("--usefulness", type=float)
|
|
192
|
+
upsert.add_argument("--tool-safety", type=float)
|
|
193
|
+
upsert.add_argument("--token-efficiency", type=float)
|
|
194
|
+
upsert.set_defaults(handler=handle_reviews_upsert)
|
|
195
|
+
|
|
196
|
+
summary = reviews_sub.add_parser(
|
|
197
|
+
"summary",
|
|
198
|
+
help="Show aggregate review statistics for a course",
|
|
199
|
+
parents=[COMMON_PARSER],
|
|
200
|
+
)
|
|
201
|
+
summary.add_argument("course_id", metavar="COURSE_ID")
|
|
202
|
+
summary.add_argument("--version")
|
|
203
|
+
summary.add_argument("--limit", type=int, default=5)
|
|
204
|
+
summary.set_defaults(handler=handle_reviews_summary)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def register_purchase(subparsers: argparse._SubParsersAction) -> None:
|
|
208
|
+
"""Register the ``courses purchase`` subcommand."""
|
|
209
|
+
purchase = subparsers.add_parser(
|
|
210
|
+
"purchase",
|
|
211
|
+
help=CMD_HELP["purchase"],
|
|
212
|
+
parents=[COMMON_PARSER],
|
|
213
|
+
)
|
|
214
|
+
purchase.add_argument("course_id", metavar="COURSE_ID")
|
|
215
|
+
purchase.add_argument(
|
|
216
|
+
"--expected-price-cents",
|
|
217
|
+
dest="expected_price_cents",
|
|
218
|
+
type=int,
|
|
219
|
+
default=None,
|
|
220
|
+
help=(
|
|
221
|
+
"Price guard: fail if the course price has"
|
|
222
|
+
" changed. Omit to skip the guard (the --yes"
|
|
223
|
+
" confirmation still protects against"
|
|
224
|
+
" accidental purchases)."
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
purchase.add_argument(
|
|
228
|
+
"--idempotency-key",
|
|
229
|
+
dest="idempotency_key",
|
|
230
|
+
default=None,
|
|
231
|
+
help="Optional idempotency key to safely retry a purchase.",
|
|
232
|
+
)
|
|
233
|
+
purchase.add_argument(
|
|
234
|
+
"--yes",
|
|
235
|
+
action="store_true",
|
|
236
|
+
default=False,
|
|
237
|
+
help="Confirm the purchase without prompting.",
|
|
238
|
+
)
|
|
239
|
+
purchase.set_defaults(handler=handle_purchase)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Parser registration for ``courses uploads`` subcommands.
|
|
3
|
+
|
|
4
|
+
Lives in its own module so :mod:`parser_sections` stays under the
|
|
5
|
+
per-file line budget.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
from cli._options import COMMON_PARSER
|
|
13
|
+
|
|
14
|
+
from ._uploads_push import handle_uploads_push
|
|
15
|
+
from .handlers import handle_uploads_complete, handle_uploads_create
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register_uploads(subparsers: argparse._SubParsersAction) -> None:
|
|
19
|
+
"""Register the ``uploads`` subcommand group."""
|
|
20
|
+
uploads = subparsers.add_parser("uploads", help="Manage course uploads")
|
|
21
|
+
uploads_sub = uploads.add_subparsers(
|
|
22
|
+
dest="courses_uploads_command",
|
|
23
|
+
required=True,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
create = uploads_sub.add_parser(
|
|
27
|
+
"create",
|
|
28
|
+
help="Create an upload session for a course version",
|
|
29
|
+
parents=[COMMON_PARSER],
|
|
30
|
+
)
|
|
31
|
+
create.add_argument("course_id", metavar="COURSE_ID")
|
|
32
|
+
create.add_argument(
|
|
33
|
+
"--file",
|
|
34
|
+
action="append",
|
|
35
|
+
dest="files",
|
|
36
|
+
default=[],
|
|
37
|
+
help=(
|
|
38
|
+
"File path to include in the upload session. "
|
|
39
|
+
"Use FILE_PATH or UPLOAD_PATH=FILE_PATH to override the upload "
|
|
40
|
+
"path. When omitted, only the basename is used and directory "
|
|
41
|
+
"structure is flattened."
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
create.set_defaults(handler=handle_uploads_create)
|
|
45
|
+
|
|
46
|
+
complete = uploads_sub.add_parser(
|
|
47
|
+
"complete",
|
|
48
|
+
help="Complete an upload session",
|
|
49
|
+
parents=[COMMON_PARSER],
|
|
50
|
+
)
|
|
51
|
+
complete.add_argument("course_id", metavar="COURSE_ID")
|
|
52
|
+
complete.add_argument("version_id", metavar="VERSION_ID")
|
|
53
|
+
complete.set_defaults(handler=handle_uploads_complete)
|
|
54
|
+
|
|
55
|
+
push = uploads_sub.add_parser(
|
|
56
|
+
"push",
|
|
57
|
+
help="PUT each file in an upload session to its presigned URL",
|
|
58
|
+
parents=[COMMON_PARSER],
|
|
59
|
+
)
|
|
60
|
+
push.add_argument("course_id", metavar="COURSE_ID")
|
|
61
|
+
push.add_argument("version_id", metavar="VERSION_ID")
|
|
62
|
+
push.add_argument(
|
|
63
|
+
"--session-file",
|
|
64
|
+
required=True,
|
|
65
|
+
help=(
|
|
66
|
+
"Path to the JSON returned by `uploads create`; use '-' to read "
|
|
67
|
+
"from stdin."
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
push.add_argument(
|
|
71
|
+
"--file",
|
|
72
|
+
action="append",
|
|
73
|
+
dest="files",
|
|
74
|
+
default=[],
|
|
75
|
+
help=(
|
|
76
|
+
"Local file to push. Use UPLOAD_PATH=LOCAL_PATH (matched against "
|
|
77
|
+
"the session's `filename`) or LOCAL_PATH for basename matching. "
|
|
78
|
+
"Repeatable."
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
# --max-retries and --timeout come from COMMON_PARSER; defaults are
|
|
82
|
+
# applied in the handler so push has sensible behaviour even when
|
|
83
|
+
# the caller omits them.
|
|
84
|
+
push.set_defaults(handler=handle_uploads_push)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Shared parser helpers for courses commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_tag_arguments(
|
|
10
|
+
parser: argparse.ArgumentParser,
|
|
11
|
+
*,
|
|
12
|
+
default: list[str] | None,
|
|
13
|
+
) -> None:
|
|
14
|
+
"""Add ``--tag`` and ``--clear-tags`` arguments."""
|
|
15
|
+
group = parser.add_mutually_exclusive_group()
|
|
16
|
+
group.add_argument(
|
|
17
|
+
"--tag",
|
|
18
|
+
action="append",
|
|
19
|
+
dest="tags",
|
|
20
|
+
default=default,
|
|
21
|
+
help="Tag to add (can be specified multiple times)",
|
|
22
|
+
)
|
|
23
|
+
if default is None:
|
|
24
|
+
group.add_argument(
|
|
25
|
+
"--clear-tags",
|
|
26
|
+
action="store_true",
|
|
27
|
+
help="Remove all tags from the course",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_category_argument(
|
|
32
|
+
parser: argparse.ArgumentParser,
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Add ``--category`` argument to a create or update parser."""
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--category",
|
|
37
|
+
default=None,
|
|
38
|
+
help=(
|
|
39
|
+
"Course category slug (e.g. devops, security, writing). "
|
|
40
|
+
"Unknown slugs are rejected by the API."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def add_tristate_flag(
|
|
46
|
+
parser: argparse.ArgumentParser,
|
|
47
|
+
flag: str,
|
|
48
|
+
*,
|
|
49
|
+
dest: str,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""Add a three-state boolean flag."""
|
|
52
|
+
base = flag.removeprefix("--")
|
|
53
|
+
group = parser.add_mutually_exclusive_group()
|
|
54
|
+
group.add_argument(
|
|
55
|
+
flag,
|
|
56
|
+
dest=dest,
|
|
57
|
+
action="store_true",
|
|
58
|
+
default=None,
|
|
59
|
+
)
|
|
60
|
+
group.add_argument(
|
|
61
|
+
f"--no-{base}",
|
|
62
|
+
dest=dest,
|
|
63
|
+
action="store_false",
|
|
64
|
+
default=None,
|
|
65
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
"""Publication handlers for courses commands."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
from cli._config import resolve_config_from_args
|
|
9
|
+
from cli._context import make_client
|
|
10
|
+
from cli._errors import handle_error, validate_uuid_id
|
|
11
|
+
from cli._output import emit, emit_json, to_data
|
|
12
|
+
from cli._utils import only_not_none
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_publication_request(args: argparse.Namespace) -> int:
|
|
16
|
+
"""Execute publication request."""
|
|
17
|
+
bad_id = validate_uuid_id(args.course_id, "COURSE_ID")
|
|
18
|
+
if bad_id is not None:
|
|
19
|
+
return bad_id
|
|
20
|
+
config = resolve_config_from_args(args)
|
|
21
|
+
client = make_client(config)
|
|
22
|
+
try:
|
|
23
|
+
result = client.v1.courses.request_publication_review(
|
|
24
|
+
course_id=args.course_id,
|
|
25
|
+
)
|
|
26
|
+
if config.json_output:
|
|
27
|
+
emit_json("logion.courses.publication.request", to_data(result))
|
|
28
|
+
else:
|
|
29
|
+
emit(result, json_output=False)
|
|
30
|
+
except Exception as exc:
|
|
31
|
+
return handle_error(exc)
|
|
32
|
+
else:
|
|
33
|
+
return 0
|
|
34
|
+
finally:
|
|
35
|
+
client.close()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def handle_publication_latest(args: argparse.Namespace) -> int:
|
|
39
|
+
"""Execute publication latest."""
|
|
40
|
+
bad_id = validate_uuid_id(args.course_id, "COURSE_ID")
|
|
41
|
+
if bad_id is not None:
|
|
42
|
+
return bad_id
|
|
43
|
+
config = resolve_config_from_args(args)
|
|
44
|
+
client = make_client(config)
|
|
45
|
+
try:
|
|
46
|
+
kwargs = only_not_none(
|
|
47
|
+
{"course_id": args.course_id},
|
|
48
|
+
include_pass=args.include_pass,
|
|
49
|
+
)
|
|
50
|
+
result = client.v1.courses.get_latest_publication_review(**kwargs)
|
|
51
|
+
if config.json_output:
|
|
52
|
+
emit_json("logion.courses.publication.latest", to_data(result))
|
|
53
|
+
else:
|
|
54
|
+
emit(result, json_output=False)
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
return handle_error(exc)
|
|
57
|
+
else:
|
|
58
|
+
return 0
|
|
59
|
+
finally:
|
|
60
|
+
client.close()
|