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,202 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Handlers for the ``skills`` command group.
3
+
4
+ These commands operate entirely on the local ``~/.logion/`` cache; they
5
+ do not call the marketplace API. The companion package (and any
6
+ agent) talks to the local cache through this CLI surface so install,
7
+ update, and inspection paths run through the same validators as the
8
+ rest of the CLI.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from cli._course_bundle import CourseBundleError, validate_course_bundle
19
+ from cli._local_state import (
20
+ LockHeldError,
21
+ UnsafeIdentifierError,
22
+ _utc_iso_now,
23
+ acquire_lock,
24
+ enrich_manifest,
25
+ installed_dir,
26
+ validate_manifest,
27
+ )
28
+ from cli._output import emit_json
29
+
30
+ from ._agent_symlink import (
31
+ apply_post_install_symlink,
32
+ resolve_symlink_intent,
33
+ )
34
+ from ._finalize import copy_and_finalize
35
+ from ._inspect_handler import handle_skills_inspect # noqa: F401 (re-export)
36
+ from ._install_helpers import (
37
+ check_existing_install,
38
+ copy_skill_files,
39
+ read_capabilities,
40
+ resolve_target,
41
+ )
42
+ from ._query_handlers import ( # noqa: F401 (re-export)
43
+ handle_skills_installed,
44
+ handle_skills_updates,
45
+ )
46
+ from ._update_handler import handle_skills_update # noqa: F401 (re-export)
47
+ from ._verify_handler import handle_skills_verify # noqa: F401 (re-export)
48
+
49
+
50
+ def _build_manifest_data(
51
+ args: argparse.Namespace,
52
+ course_id: str,
53
+ version_id: str,
54
+ source_dir: Path,
55
+ ) -> dict[str, Any]:
56
+ """Build the manifest dict for an install."""
57
+ install_source = getattr(args, "install_source", "manual")
58
+ is_marketplace = install_source == "logion-marketplace"
59
+ data: dict[str, Any] = {
60
+ "course_id": course_id,
61
+ "version_id": version_id,
62
+ "title": args.title or "",
63
+ "source": install_source,
64
+ "installed_at": "",
65
+ "entrypoint": "SKILL.md",
66
+ "capabilities": [],
67
+ "required_tools": ["terminal", "file"],
68
+ "permissions": [],
69
+ "env_vars": [],
70
+ "execution_policy": "approval-required",
71
+ "content_sha256": "",
72
+ "review_status": "approved",
73
+ "entitlement_status": "active" if is_marketplace else "unknown",
74
+ "license_scope": "unknown",
75
+ "official_update_channel": is_marketplace,
76
+ "last_verified_at": _utc_iso_now() if is_marketplace else None,
77
+ }
78
+ return read_capabilities(source_dir / "course" / "capabilities.yaml", data)
79
+
80
+
81
+ def _validate_pre_install(
82
+ manifest_data: dict[str, Any],
83
+ course_id: str,
84
+ version_id: str,
85
+ home: Path,
86
+ ) -> int:
87
+ """Validate manifest before filesystem writes. Returns 0 on success."""
88
+ pre_manifest = enrich_manifest(
89
+ {
90
+ **manifest_data,
91
+ "installed_at": (
92
+ manifest_data.get("installed_at") or _utc_iso_now()
93
+ ),
94
+ "content_sha256": "_",
95
+ },
96
+ course_id,
97
+ version_id,
98
+ home,
99
+ )
100
+ pre_errors = validate_manifest(pre_manifest)
101
+ if pre_errors:
102
+ for e in pre_errors:
103
+ print(f"MANIFEST ERROR: {e}", file=sys.stderr)
104
+ return 3
105
+ return 0
106
+
107
+
108
+ def handle_skills_install(args: argparse.Namespace) -> int: # noqa: C901
109
+ """Install a skill bundle from a local source directory."""
110
+ home = resolve_target(args)
111
+ source_dir = args.source.resolve()
112
+ course_id: str = args.course_id
113
+ version_id: str = args.version_id
114
+
115
+ if not (source_dir / "SKILL.md").is_file():
116
+ print(
117
+ f"ERROR: source directory must contain SKILL.md: {source_dir}",
118
+ file=sys.stderr,
119
+ )
120
+ return 1
121
+ try:
122
+ validate_course_bundle(source_dir)
123
+ except CourseBundleError as exc:
124
+ print(f"ERROR: invalid course bundle: {exc}", file=sys.stderr)
125
+ return 1
126
+
127
+ # Ask about the symlink up-front, before any filesystem writes.
128
+ # The user's choice is captured here and applied after install.
129
+ skill_name, symlink_parent = resolve_symlink_intent(source_dir, args)
130
+
131
+ if not args.force:
132
+ try:
133
+ rc = check_existing_install(
134
+ course_id, version_id, source_dir, home
135
+ )
136
+ except UnsafeIdentifierError as exc:
137
+ print(f"ERROR: {exc}", file=sys.stderr)
138
+ return 1
139
+ if rc != 0:
140
+ return rc
141
+
142
+ manifest_data = _build_manifest_data(
143
+ args, course_id, version_id, source_dir
144
+ )
145
+
146
+ # Validate the manifest *before* touching the filesystem so an
147
+ # invalid bundle (including dry-run) cannot leave a partial copy
148
+ # behind or report success without a real install.
149
+ rc = _validate_pre_install(manifest_data, course_id, version_id, home)
150
+ if rc != 0:
151
+ return rc
152
+
153
+ try:
154
+ dest = installed_dir(course_id, version_id, home)
155
+ except UnsafeIdentifierError as exc:
156
+ print(f"ERROR: {exc}", file=sys.stderr)
157
+ return 1
158
+
159
+ if args.dry_run:
160
+ copied = copy_skill_files(source_dir, dest, dry_run=True)
161
+ print(
162
+ f"Would install: {course_id}/{version_id} "
163
+ f"({len(copied)} files) -> {dest}"
164
+ )
165
+ return 0
166
+
167
+ try:
168
+ acquire_lock(course_id, version_id, home)
169
+ except LockHeldError as exc:
170
+ print(
171
+ f"ERROR: another install/update holds the lock for "
172
+ f"{course_id}/{version_id} at {exc.path}. Wait for it to "
173
+ "finish or remove the stale lock file.",
174
+ file=sys.stderr,
175
+ )
176
+ return 4
177
+
178
+ rc, copied = copy_and_finalize(
179
+ source_dir, dest, course_id, version_id, manifest_data, home
180
+ )
181
+ if rc != 0:
182
+ return rc
183
+
184
+ print(
185
+ f"Installed: {course_id}/{version_id} ({len(copied)} files) -> {dest}"
186
+ )
187
+
188
+ if symlink_parent and skill_name:
189
+ apply_post_install_symlink(symlink_parent, skill_name, dest)
190
+
191
+ if getattr(args, "json_output", False):
192
+ emit_json(
193
+ "logion.skills.install",
194
+ {
195
+ "course_id": course_id,
196
+ "version_id": version_id,
197
+ "destination": str(dest),
198
+ "files_installed": len(copied),
199
+ "symlinked": bool(symlink_parent and skill_name),
200
+ },
201
+ )
202
+ return 0
@@ -0,0 +1,154 @@
1
+ # SPDX-License-Identifier: MIT
2
+ """Parser registration for skills 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 ._search_handler import handle_skills_search
12
+ from .handlers import (
13
+ handle_skills_inspect,
14
+ handle_skills_install,
15
+ handle_skills_installed,
16
+ handle_skills_update,
17
+ handle_skills_updates,
18
+ handle_skills_verify,
19
+ )
20
+
21
+
22
+ def register(subparsers: argparse._SubParsersAction) -> None:
23
+ """Register the ``skills`` subcommand group."""
24
+ parser = subparsers.add_parser(
25
+ "skills",
26
+ help="Manage locally installed marketplace skills",
27
+ )
28
+ sub = parser.add_subparsers(
29
+ dest="skills_command",
30
+ required=True,
31
+ )
32
+
33
+ install = sub.add_parser(
34
+ "install",
35
+ help="Install a skill bundle from a local source directory",
36
+ parents=[COMMON_PARSER],
37
+ )
38
+ install.add_argument(
39
+ "--source",
40
+ type=Path,
41
+ required=True,
42
+ help="Path to the skill bundle source directory",
43
+ )
44
+ install.add_argument("--course-id", required=True)
45
+ install.add_argument("--version-id", required=True)
46
+ install.add_argument("--title", default=None)
47
+ install.add_argument(
48
+ "--target",
49
+ type=Path,
50
+ default=None,
51
+ help="Override LOGION_HOME for the install target",
52
+ )
53
+ install.add_argument(
54
+ "--dry-run",
55
+ action="store_true",
56
+ help="Show what would be installed without writing files",
57
+ )
58
+ install.add_argument(
59
+ "--force",
60
+ action="store_true",
61
+ help="Overwrite an existing install with different content",
62
+ )
63
+ install.add_argument(
64
+ "--install-source",
65
+ default="manual",
66
+ choices=["manual", "logion-marketplace"],
67
+ help="Provenance of the install (default: manual)",
68
+ )
69
+ install.add_argument(
70
+ "--symlink-dir",
71
+ default=None,
72
+ help=(
73
+ "Symlink the installed skill into this directory "
74
+ "(e.g. ~/.claude/skills). Skip the interactive prompt."
75
+ ),
76
+ )
77
+ install.add_argument(
78
+ "--no-symlink",
79
+ action="store_true",
80
+ help="Skip the agent-symlink prompt entirely (canonical install only)",
81
+ )
82
+ install.set_defaults(handler=handle_skills_install)
83
+
84
+ installed = sub.add_parser(
85
+ "installed",
86
+ help="List installed skills",
87
+ parents=[COMMON_PARSER],
88
+ )
89
+ installed.add_argument("--target", type=Path, default=None)
90
+ installed.set_defaults(handler=handle_skills_installed)
91
+
92
+ inspect = sub.add_parser(
93
+ "inspect",
94
+ help="Show the manifest for an installed skill",
95
+ parents=[COMMON_PARSER],
96
+ )
97
+ inspect.add_argument("course_id", metavar="COURSE_ID")
98
+ inspect.add_argument("--version-id", default=None)
99
+ inspect.add_argument("--verbose", action="store_true", default=False)
100
+ inspect.add_argument("--target", type=Path, default=None)
101
+ inspect.set_defaults(handler=handle_skills_inspect)
102
+
103
+ updates = sub.add_parser(
104
+ "updates",
105
+ help="Report integrity and update status for installed skills",
106
+ parents=[COMMON_PARSER],
107
+ )
108
+ updates.add_argument("--target", type=Path, default=None)
109
+ updates.set_defaults(handler=handle_skills_updates)
110
+
111
+ update = sub.add_parser(
112
+ "update",
113
+ help="Apply an update to an installed skill",
114
+ parents=[COMMON_PARSER],
115
+ )
116
+ update.add_argument("course_id", metavar="COURSE_ID")
117
+ update.add_argument("--version-id", required=True)
118
+ update.add_argument(
119
+ "--source",
120
+ type=Path,
121
+ required=True,
122
+ help="Path to the new skill bundle source directory",
123
+ )
124
+ update.add_argument("--title", default=None)
125
+ update.add_argument("--target", type=Path, default=None)
126
+ update.add_argument(
127
+ "--force",
128
+ action="store_true",
129
+ help="Overwrite a locally modified installation",
130
+ )
131
+ update.set_defaults(handler=handle_skills_update)
132
+
133
+ verify = sub.add_parser(
134
+ "verify",
135
+ help="Re-state locally stored entitlement status for installed skills",
136
+ parents=[COMMON_PARSER],
137
+ )
138
+ verify.add_argument("course_id", nargs="?", default=None)
139
+ verify.add_argument("--target", type=Path, default=None)
140
+ verify.set_defaults(handler=handle_skills_verify)
141
+
142
+ search = sub.add_parser(
143
+ "search",
144
+ help=(
145
+ "Search marketplace listings"
146
+ " with installed/entitlement annotations"
147
+ ),
148
+ parents=[COMMON_PARSER],
149
+ )
150
+ search.add_argument("query", metavar="QUERY")
151
+ search.add_argument("--limit", type=int, default=5)
152
+ search.add_argument("--verbose", action="store_true", default=False)
153
+ search.add_argument("--target", type=Path, default=None)
154
+ search.set_defaults(handler=handle_skills_search)