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.
Files changed (135) hide show
  1. cli/__init__.py +2 -0
  2. cli/_config.py +51 -0
  3. cli/_confirm.py +16 -0
  4. cli/_context.py +17 -0
  5. cli/_course_bundle.py +46 -0
  6. cli/_course_capabilities.py +580 -0
  7. cli/_credentials.py +104 -0
  8. cli/_errors.py +82 -0
  9. cli/_first_run.py +90 -0
  10. cli/_harness/__init__.py +68 -0
  11. cli/_harness/base.py +106 -0
  12. cli/_harness/claude_code.py +168 -0
  13. cli/_harness/codex.py +79 -0
  14. cli/_harness/custom.py +55 -0
  15. cli/_harness/hermes.py +93 -0
  16. cli/_harness/opencode.py +255 -0
  17. cli/_local_state.py +1053 -0
  18. cli/_options.py +36 -0
  19. cli/_output.py +47 -0
  20. cli/_parser.py +73 -0
  21. cli/_recall_calibration.py +90 -0
  22. cli/_recall_ranker.py +74 -0
  23. cli/_taxonomy.py +120 -0
  24. cli/_update_policy.py +152 -0
  25. cli/_utils.py +16 -0
  26. cli/_version.py +26 -0
  27. cli/commands/__init__.py +2 -0
  28. cli/commands/admin.py +535 -0
  29. cli/commands/bounties.py +490 -0
  30. cli/commands/course_reviews/__init__.py +6 -0
  31. cli/commands/course_reviews/_download_handler.py +104 -0
  32. cli/commands/course_reviews/_render.py +129 -0
  33. cli/commands/course_reviews/handlers.py +197 -0
  34. cli/commands/course_reviews/parser.py +93 -0
  35. cli/commands/courses/__init__.py +6 -0
  36. cli/commands/courses/_capability_render.py +183 -0
  37. cli/commands/courses/_cmd_help.py +18 -0
  38. cli/commands/courses/_purchase.py +76 -0
  39. cli/commands/courses/_review_helpers.py +93 -0
  40. cli/commands/courses/_taxonomy_data.py +173 -0
  41. cli/commands/courses/_upload_bundle_validation.py +28 -0
  42. cli/commands/courses/_uploads_push.py +243 -0
  43. cli/commands/courses/capabilities.py +250 -0
  44. cli/commands/courses/capability_frontmatter.py +150 -0
  45. cli/commands/courses/handlers.py +50 -0
  46. cli/commands/courses/mutations.py +217 -0
  47. cli/commands/courses/parser.py +66 -0
  48. cli/commands/courses/parser_capabilities.py +95 -0
  49. cli/commands/courses/parser_sections.py +239 -0
  50. cli/commands/courses/parser_uploads.py +84 -0
  51. cli/commands/courses/parser_utils.py +65 -0
  52. cli/commands/courses/publication.py +60 -0
  53. cli/commands/courses/report_usage.py +131 -0
  54. cli/commands/courses/reviews.py +237 -0
  55. cli/commands/courses/taxonomy_handler.py +61 -0
  56. cli/commands/courses/taxonomy_suggest.py +197 -0
  57. cli/commands/courses/uploads.py +142 -0
  58. cli/commands/courses/versions.py +65 -0
  59. cli/commands/credits/__init__.py +6 -0
  60. cli/commands/credits/_helpers.py +153 -0
  61. cli/commands/credits/handlers.py +218 -0
  62. cli/commands/credits/parser.py +115 -0
  63. cli/commands/docs/__init__.py +6 -0
  64. cli/commands/docs/handlers.py +137 -0
  65. cli/commands/docs/parser.py +27 -0
  66. cli/commands/health/__init__.py +6 -0
  67. cli/commands/health/handlers.py +26 -0
  68. cli/commands/health/parser.py +20 -0
  69. cli/commands/identity/__init__.py +6 -0
  70. cli/commands/identity/_autopost.py +97 -0
  71. cli/commands/identity/_closing_copy.py +89 -0
  72. cli/commands/identity/_companion.py +232 -0
  73. cli/commands/identity/_companion_source.py +135 -0
  74. cli/commands/identity/_harness_select.py +85 -0
  75. cli/commands/identity/_onboarding_helpers.py +168 -0
  76. cli/commands/identity/handlers.py +173 -0
  77. cli/commands/identity/onboarding.py +246 -0
  78. cli/commands/identity/parser.py +72 -0
  79. cli/commands/listings/__init__.py +6 -0
  80. cli/commands/listings/handlers.py +135 -0
  81. cli/commands/listings/parser.py +57 -0
  82. cli/commands/notifications/__init__.py +6 -0
  83. cli/commands/notifications/handlers.py +120 -0
  84. cli/commands/notifications/parser.py +49 -0
  85. cli/commands/payments/__init__.py +6 -0
  86. cli/commands/payments/_orders_helpers.py +114 -0
  87. cli/commands/payments/handlers.py +138 -0
  88. cli/commands/payments/parser.py +97 -0
  89. cli/commands/recall/__init__.py +7 -0
  90. cli/commands/recall/handlers.py +87 -0
  91. cli/commands/recall/parser.py +70 -0
  92. cli/commands/referrals/__init__.py +6 -0
  93. cli/commands/referrals/_helpers.py +63 -0
  94. cli/commands/referrals/handlers.py +100 -0
  95. cli/commands/referrals/parser.py +65 -0
  96. cli/commands/reports/__init__.py +6 -0
  97. cli/commands/reports/handlers.py +57 -0
  98. cli/commands/reports/parser.py +52 -0
  99. cli/commands/skills/__init__.py +7 -0
  100. cli/commands/skills/_agent_symlink.py +161 -0
  101. cli/commands/skills/_finalize.py +112 -0
  102. cli/commands/skills/_inspect_handler.py +218 -0
  103. cli/commands/skills/_install_helpers.py +186 -0
  104. cli/commands/skills/_query_handlers.py +83 -0
  105. cli/commands/skills/_search_handler.py +136 -0
  106. cli/commands/skills/_update_handler.py +110 -0
  107. cli/commands/skills/_verify_handler.py +109 -0
  108. cli/commands/skills/handlers.py +202 -0
  109. cli/commands/skills/parser.py +154 -0
  110. cli/commands/workspace.py +406 -0
  111. cli/docs/README.md +5 -0
  112. cli/docs/__init__.py +1 -0
  113. cli/docs/bounties-and-referrals.md +18 -0
  114. cli/docs/concepts.md +47 -0
  115. cli/docs/creating-courses.md +25 -0
  116. cli/docs/credits-and-purchases.md +30 -0
  117. cli/docs/credits-terms.md +23 -0
  118. cli/docs/getting-started.md +95 -0
  119. cli/docs/marketplace-loop.md +108 -0
  120. cli/docs/privacy.md +30 -0
  121. cli/docs/referral-terms.md +24 -0
  122. cli/docs/reviews.md +47 -0
  123. cli/docs/safety.md +28 -0
  124. cli/docs/terms.md +54 -0
  125. cli/main.py +84 -0
  126. cli/templates/__init__.py +2 -0
  127. cli/templates/course_capabilities.template.yaml +189 -0
  128. cli/templates/course_license_apache-2.0.template.txt +30 -0
  129. cli/templates/course_license_logion-standard-course-v1.template.txt +49 -0
  130. cli/templates/course_license_mit.template.txt +21 -0
  131. logion_cli-0.1.0.dist-info/METADATA +49 -0
  132. logion_cli-0.1.0.dist-info/RECORD +135 -0
  133. logion_cli-0.1.0.dist-info/WHEEL +4 -0
  134. logion_cli-0.1.0.dist-info/entry_points.txt +4 -0
  135. 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()