opencodekit 0.21.9 → 0.22.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.
Files changed (165) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/AGENTS.md +116 -499
  3. package/dist/template/.opencode/README.md +1 -1
  4. package/dist/template/.opencode/agent/build.md +56 -396
  5. package/dist/template/.opencode/agent/explore.md +15 -16
  6. package/dist/template/.opencode/agent/general.md +2 -2
  7. package/dist/template/.opencode/agent/plan.md +3 -3
  8. package/dist/template/.opencode/agent/review.md +2 -3
  9. package/dist/template/.opencode/agent/scout.md +34 -16
  10. package/dist/template/.opencode/agent/vision.md +0 -1
  11. package/dist/template/.opencode/command/clarify.md +48 -0
  12. package/dist/template/.opencode/command/commit.md +53 -0
  13. package/dist/template/.opencode/command/design.md +4 -4
  14. package/dist/template/.opencode/command/fix.md +56 -0
  15. package/dist/template/.opencode/command/improve-architecture.md +55 -0
  16. package/dist/template/.opencode/command/init.md +88 -68
  17. package/dist/template/.opencode/command/refactor.md +66 -0
  18. package/dist/template/.opencode/command/review-codebase.md +1 -1
  19. package/dist/template/.opencode/command/ship.md +1 -1
  20. package/dist/template/.opencode/command/test.md +66 -0
  21. package/dist/template/.opencode/dcp.jsonc +29 -46
  22. package/dist/template/.opencode/memory/README.md +3 -5
  23. package/dist/template/.opencode/memory/_templates/adr.md +45 -0
  24. package/dist/template/.opencode/memory/project/gotchas.md +1 -1
  25. package/dist/template/.opencode/memory/project/user.md +1 -2
  26. package/dist/template/.opencode/memory/session-context.md +1 -1
  27. package/dist/template/.opencode/opencode.json +10 -332
  28. package/dist/template/.opencode/plugin/README.md +1 -1
  29. package/dist/template/.opencode/plugin/guard.ts +62 -0
  30. package/dist/template/.opencode/plugin/{lib/memory-admin-tools.ts → memory/admin.ts} +4 -4
  31. package/dist/template/.opencode/plugin/{lib → memory}/capture.ts +1 -1
  32. package/dist/template/.opencode/plugin/{lib → memory}/compile.ts +2 -2
  33. package/dist/template/.opencode/plugin/{lib → memory}/context.ts +1 -1
  34. package/dist/template/.opencode/plugin/{lib → memory}/curator.ts +1 -1
  35. package/dist/template/.opencode/plugin/{lib → memory}/db/observations.ts +102 -3
  36. package/dist/template/.opencode/plugin/{lib → memory}/db/schema.ts +43 -1
  37. package/dist/template/.opencode/plugin/{lib → memory}/db/types.ts +22 -0
  38. package/dist/template/.opencode/plugin/{lib/memory-db.ts → memory/db.ts} +1 -1
  39. package/dist/template/.opencode/plugin/{lib → memory}/distill.ts +1 -1
  40. package/dist/template/.opencode/plugin/{lib/memory-helpers.ts → memory/helpers.ts} +5 -1
  41. package/dist/template/.opencode/plugin/{lib/memory-hooks.ts → memory/hooks.ts} +1 -1
  42. package/dist/template/.opencode/plugin/{lib → memory}/index-generator.ts +2 -2
  43. package/dist/template/.opencode/plugin/{lib → memory}/inject.ts +1 -1
  44. package/dist/template/.opencode/plugin/{lib → memory}/lint.ts +2 -2
  45. package/dist/template/.opencode/plugin/memory/tools.ts +322 -0
  46. package/dist/template/.opencode/plugin/{lib → memory}/validate.ts +2 -2
  47. package/dist/template/.opencode/plugin/memory.ts +7 -17
  48. package/dist/template/.opencode/plugin/srcwalk.ts +721 -0
  49. package/dist/template/.opencode/skill/agent-code-quality-gate/SKILL.md +98 -0
  50. package/dist/template/.opencode/skill/behavioral-kernel/SKILL.md +52 -0
  51. package/dist/template/.opencode/skill/browser-testing-with-devtools/SKILL.md +85 -0
  52. package/dist/template/.opencode/skill/code-cleanup/SKILL.md +114 -0
  53. package/dist/template/.opencode/skill/code-navigation/SKILL.md +142 -0
  54. package/dist/template/.opencode/skill/code-review-and-quality/SKILL.md +131 -0
  55. package/dist/template/.opencode/skill/debugging-and-error-recovery/SKILL.md +109 -0
  56. package/dist/template/.opencode/skill/deep-module-design/SKILL.md +207 -0
  57. package/dist/template/.opencode/skill/git-workflow-and-versioning/SKILL.md +77 -0
  58. package/dist/template/.opencode/skill/grill-me/SKILL.md +140 -0
  59. package/dist/template/.opencode/skill/memory-system/SKILL.md +9 -10
  60. package/dist/template/.opencode/skill/planning-and-task-breakdown/SKILL.md +116 -0
  61. package/dist/template/.opencode/skill/shipping-and-launch/SKILL.md +95 -0
  62. package/dist/template/.opencode/skill/source-driven-development/SKILL.md +103 -0
  63. package/dist/template/.opencode/skill/spec-driven-development/SKILL.md +121 -0
  64. package/dist/template/.opencode/skill/srcwalk/SKILL.md +161 -0
  65. package/dist/template/.opencode/skill/ubiquitous-language/SKILL.md +184 -0
  66. package/dist/template/.opencode/tool/context7.ts +1 -1
  67. package/dist/template/.opencode/tool/grepsearch.ts +1 -1
  68. package/package.json +1 -1
  69. package/dist/template/.opencode/AGENT_ALIGNMENT.md +0 -564
  70. package/dist/template/.opencode/agent/painter.md +0 -83
  71. package/dist/template/.opencode/command/compound.md +0 -240
  72. package/dist/template/.opencode/command/curate.md +0 -299
  73. package/dist/template/.opencode/command/handoff.md +0 -149
  74. package/dist/template/.opencode/command/health.md +0 -356
  75. package/dist/template/.opencode/command/init-context.md +0 -297
  76. package/dist/template/.opencode/command/init-user.md +0 -125
  77. package/dist/template/.opencode/command/iterate.md +0 -200
  78. package/dist/template/.opencode/command/lfg.md +0 -173
  79. package/dist/template/.opencode/command/resume.md +0 -78
  80. package/dist/template/.opencode/command/status.md +0 -126
  81. package/dist/template/.opencode/command/ui-slop-check.md +0 -169
  82. package/dist/template/.opencode/plugin/lib/memory-tools.ts +0 -535
  83. package/dist/template/.opencode/skill/agent-evals/SKILL.md +0 -208
  84. package/dist/template/.opencode/skill/anti-ai-slop/SKILL.md +0 -76
  85. package/dist/template/.opencode/skill/augment-context-engine/SKILL.md +0 -122
  86. package/dist/template/.opencode/skill/augment-context-engine/mcp.json +0 -6
  87. package/dist/template/.opencode/skill/brand-asset-protocol/SKILL.md +0 -222
  88. package/dist/template/.opencode/skill/code-search-patterns/SKILL.md +0 -253
  89. package/dist/template/.opencode/skill/code-simplification/SKILL.md +0 -211
  90. package/dist/template/.opencode/skill/context-condensation/SKILL.md +0 -149
  91. package/dist/template/.opencode/skill/context-initialization/SKILL.md +0 -69
  92. package/dist/template/.opencode/skill/context-management/SKILL.md +0 -390
  93. package/dist/template/.opencode/skill/deep-research/SKILL.md +0 -384
  94. package/dist/template/.opencode/skill/design-direction-advisor/SKILL.md +0 -139
  95. package/dist/template/.opencode/skill/dispatching-parallel-agents/SKILL.md +0 -191
  96. package/dist/template/.opencode/skill/executing-plans/SKILL.md +0 -247
  97. package/dist/template/.opencode/skill/figma-go/SKILL.md +0 -65
  98. package/dist/template/.opencode/skill/finishing-a-development-branch/SKILL.md +0 -357
  99. package/dist/template/.opencode/skill/full-output-enforcement/SKILL.md +0 -62
  100. package/dist/template/.opencode/skill/gh-address-comments/SKILL.md +0 -29
  101. package/dist/template/.opencode/skill/gh-address-comments/scripts/fetch_comments.py +0 -237
  102. package/dist/template/.opencode/skill/gh-fix-ci/SKILL.md +0 -38
  103. package/dist/template/.opencode/skill/gh-fix-ci/scripts/inspect_pr_checks.py +0 -509
  104. package/dist/template/.opencode/skill/hi-fi-prototype-html/SKILL.md +0 -253
  105. package/dist/template/.opencode/skill/html-deck-export/SKILL.md +0 -189
  106. package/dist/template/.opencode/skill/index-knowledge/SKILL.md +0 -413
  107. package/dist/template/.opencode/skill/memory-grounding/SKILL.md +0 -68
  108. package/dist/template/.opencode/skill/playwriter/SKILL.md +0 -158
  109. package/dist/template/.opencode/skill/portless/SKILL.md +0 -109
  110. package/dist/template/.opencode/skill/prd/SKILL.md +0 -146
  111. package/dist/template/.opencode/skill/prd-task/SKILL.md +0 -182
  112. package/dist/template/.opencode/skill/prd-task/references/prd-schema.json +0 -124
  113. package/dist/template/.opencode/skill/prompt-leverage/SKILL.md +0 -90
  114. package/dist/template/.opencode/skill/prompt-leverage/references/framework.md +0 -91
  115. package/dist/template/.opencode/skill/prompt-leverage/scripts/augment_prompt.py +0 -157
  116. package/dist/template/.opencode/skill/receiving-code-review/SKILL.md +0 -263
  117. package/dist/template/.opencode/skill/reconcile/SKILL.md +0 -183
  118. package/dist/template/.opencode/skill/reflection-checkpoints/SKILL.md +0 -183
  119. package/dist/template/.opencode/skill/requesting-code-review/SKILL.md +0 -443
  120. package/dist/template/.opencode/skill/requesting-code-review/references/specialist-profiles.md +0 -108
  121. package/dist/template/.opencode/skill/requesting-code-review/review.md +0 -160
  122. package/dist/template/.opencode/skill/rtk-command-compression/SKILL.md +0 -134
  123. package/dist/template/.opencode/skill/screenshot/SKILL.md +0 -48
  124. package/dist/template/.opencode/skill/screenshot/scripts/ensure_macos_permissions.sh +0 -54
  125. package/dist/template/.opencode/skill/screenshot/scripts/macos_display_info.swift +0 -22
  126. package/dist/template/.opencode/skill/screenshot/scripts/macos_permissions.swift +0 -40
  127. package/dist/template/.opencode/skill/screenshot/scripts/macos_window_info.swift +0 -126
  128. package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.ps1 +0 -163
  129. package/dist/template/.opencode/skill/screenshot/scripts/take_screenshot.py +0 -585
  130. package/dist/template/.opencode/skill/security-threat-model/SKILL.md +0 -36
  131. package/dist/template/.opencode/skill/security-threat-model/references/prompt-template.md +0 -255
  132. package/dist/template/.opencode/skill/security-threat-model/references/security-controls-and-assets.md +0 -32
  133. package/dist/template/.opencode/skill/sharing-skills/SKILL.md +0 -214
  134. package/dist/template/.opencode/skill/skill-creator/SKILL.md +0 -181
  135. package/dist/template/.opencode/skill/skill-installer/SKILL.md +0 -58
  136. package/dist/template/.opencode/skill/skill-installer/scripts/github_utils.py +0 -21
  137. package/dist/template/.opencode/skill/skill-installer/scripts/install-skill-from-github.py +0 -313
  138. package/dist/template/.opencode/skill/skill-installer/scripts/list-skills.py +0 -106
  139. package/dist/template/.opencode/skill/swarm-coordination/SKILL.md +0 -244
  140. package/dist/template/.opencode/skill/swarm-coordination/references/architecture.md +0 -39
  141. package/dist/template/.opencode/skill/swarm-coordination/references/delegation-worker-protocol.md +0 -145
  142. package/dist/template/.opencode/skill/swarm-coordination/references/dependency-graph.md +0 -50
  143. package/dist/template/.opencode/skill/swarm-coordination/references/drift-check.md +0 -90
  144. package/dist/template/.opencode/skill/swarm-coordination/references/integration-beads.md +0 -20
  145. package/dist/template/.opencode/skill/swarm-coordination/references/launch-flow.md +0 -186
  146. package/dist/template/.opencode/skill/swarm-coordination/references/reconciler.md +0 -172
  147. package/dist/template/.opencode/skill/swarm-coordination/references/tier-enforcement.md +0 -78
  148. package/dist/template/.opencode/skill/swarm-coordination/references/tmux-integration.md +0 -134
  149. package/dist/template/.opencode/skill/systematic-debugging/SKILL.md +0 -402
  150. package/dist/template/.opencode/skill/terse-output-mode/SKILL.md +0 -95
  151. package/dist/template/.opencode/skill/think-in-code/SKILL.md +0 -136
  152. package/dist/template/.opencode/skill/ux-quality-gates/SKILL.md +0 -137
  153. package/dist/template/.opencode/skill/v1-run/SKILL.md +0 -175
  154. package/dist/template/.opencode/skill/v1-run/mcp.json +0 -6
  155. package/dist/template/.opencode/skill/verification-gates/SKILL.md +0 -63
  156. package/dist/template/.opencode/skill/visual-analysis/SKILL.md +0 -154
  157. package/dist/template/.opencode/skill/web-design-guidelines/SKILL.md +0 -46
  158. package/dist/template/.opencode/skill/workspace-setup/SKILL.md +0 -76
  159. package/dist/template/.opencode/skill/writing-plans/SKILL.md +0 -320
  160. /package/dist/template/.opencode/plugin/{lib → memory}/compact.ts +0 -0
  161. /package/dist/template/.opencode/plugin/{lib → memory}/db/graph.ts +0 -0
  162. /package/dist/template/.opencode/plugin/{lib → memory}/db/maintenance.ts +0 -0
  163. /package/dist/template/.opencode/plugin/{lib → memory}/db/pipeline.ts +0 -0
  164. /package/dist/template/.opencode/plugin/{lib → memory}/notify.ts +0 -0
  165. /package/dist/template/.opencode/plugin/{lib → memory}/operation-log.ts +0 -0
@@ -1,58 +0,0 @@
1
- ---
2
- name: skill-installer
3
- description: Use when the user asks to list, import, or install skills from OpenAI or another GitHub repository into OpenCode skill directories.
4
- version: 1.0.0
5
- tags: [workflow, automation, integration]
6
- dependencies: []
7
- ---
8
-
9
- # skill-installer
10
-
11
- Import skills from GitHub into OpenCode-compatible skill directories.
12
-
13
- ## When to Use
14
-
15
- - User asks to “install skill X” from `openai/skills` or another repo
16
- - User wants to list available curated skills before choosing
17
- - User wants to copy one or more external skills into local OpenCode setup
18
-
19
- ## When NOT to Use
20
-
21
- - Skill already exists locally and only needs editing
22
- - User asked for manual adaptation (copy + rewrite), not automated install
23
-
24
- ## Default Destination
25
-
26
- - Global OpenCode skill directory: `~/.config/opencode/skill`
27
- - Override destination with `--dest <path>`
28
-
29
- ## Scripts
30
-
31
- - `scripts/list-skills.py`
32
- - Lists skills from a GitHub repo path and marks already-installed skills.
33
- - `scripts/install-skill-from-github.py`
34
- - Installs one or many skill folders from GitHub into OpenCode skill directory.
35
-
36
- ## Quick Start
37
-
38
- ```bash
39
- # List curated skills from openai/skills
40
- python3 .opencode/skill/skill-installer/scripts/list-skills.py
41
-
42
- # Install one curated skill into ~/.config/opencode/skill
43
- python3 .opencode/skill/skill-installer/scripts/install-skill-from-github.py \
44
- --repo openai/skills \
45
- --path skills/.curated/screenshot
46
-
47
- # Install into project-local skills directory
48
- python3 .opencode/skill/skill-installer/scripts/install-skill-from-github.py \
49
- --repo openai/skills \
50
- --path skills/.curated/gh-fix-ci \
51
- --dest .opencode/skill
52
- ```
53
-
54
- ## Notes
55
-
56
- - Supports direct GitHub URLs (`--url`) and `owner/repo` (`--repo`) mode.
57
- - Uses authenticated API requests if `GITHUB_TOKEN` or `GH_TOKEN` is set.
58
- - Fails fast if destination skill directory already exists.
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Shared GitHub helpers for skill install scripts."""
3
-
4
- from __future__ import annotations
5
-
6
- import os
7
- import urllib.request
8
-
9
-
10
- def github_request(url: str, user_agent: str) -> bytes:
11
- headers = {"User-Agent": user_agent}
12
- token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
13
- if token:
14
- headers["Authorization"] = f"token {token}"
15
- req = urllib.request.Request(url, headers=headers)
16
- with urllib.request.urlopen(req) as resp:
17
- return resp.read()
18
-
19
-
20
- def github_api_contents_url(repo: str, path: str, ref: str) -> str:
21
- return f"https://api.github.com/repos/{repo}/contents/{path}?ref={ref}"
@@ -1,313 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Install a skill from a GitHub repo path into OpenCode skill directory."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- from dataclasses import dataclass
8
- import os
9
- import shutil
10
- import subprocess
11
- import sys
12
- import tempfile
13
- import urllib.error
14
- import urllib.parse
15
- import zipfile
16
-
17
- from github_utils import github_request
18
-
19
- DEFAULT_REF = "main"
20
-
21
-
22
- @dataclass
23
- class Args:
24
- url: str | None = None
25
- repo: str | None = None
26
- path: list[str] | None = None
27
- ref: str = DEFAULT_REF
28
- dest: str | None = None
29
- name: str | None = None
30
- method: str = "auto"
31
-
32
-
33
- @dataclass
34
- class Source:
35
- owner: str
36
- repo: str
37
- ref: str
38
- paths: list[str]
39
- repo_url: str | None = None
40
-
41
-
42
- class InstallError(Exception):
43
- pass
44
-
45
-
46
- def _opencode_home() -> str:
47
- return os.environ.get("OPENCODE_HOME", os.path.expanduser("~/.config/opencode"))
48
-
49
-
50
- def _tmp_root() -> str:
51
- base = os.path.join(tempfile.gettempdir(), "opencode")
52
- os.makedirs(base, exist_ok=True)
53
- return base
54
-
55
-
56
- def _request(url: str) -> bytes:
57
- return github_request(url, "opencode-skill-install")
58
-
59
-
60
- def _parse_github_url(url: str, default_ref: str) -> tuple[str, str, str, str | None]:
61
- parsed = urllib.parse.urlparse(url)
62
- if parsed.netloc != "github.com":
63
- raise InstallError("Only GitHub URLs are supported for download mode.")
64
- parts = [p for p in parsed.path.split("/") if p]
65
- if len(parts) < 2:
66
- raise InstallError("Invalid GitHub URL.")
67
- owner, repo = parts[0], parts[1]
68
- ref = default_ref
69
- subpath = ""
70
- if len(parts) > 2:
71
- if parts[2] in ("tree", "blob"):
72
- if len(parts) < 4:
73
- raise InstallError("GitHub URL missing ref or path.")
74
- ref = parts[3]
75
- subpath = "/".join(parts[4:])
76
- else:
77
- subpath = "/".join(parts[2:])
78
- return owner, repo, ref, subpath or None
79
-
80
-
81
- def _download_repo_zip(owner: str, repo: str, ref: str, dest_dir: str) -> str:
82
- zip_url = f"https://codeload.github.com/{owner}/{repo}/zip/{ref}"
83
- zip_path = os.path.join(dest_dir, "repo.zip")
84
- try:
85
- payload = _request(zip_url)
86
- except urllib.error.HTTPError as exc:
87
- raise InstallError(f"Download failed: HTTP {exc.code}") from exc
88
- with open(zip_path, "wb") as file_handle:
89
- file_handle.write(payload)
90
- with zipfile.ZipFile(zip_path, "r") as zip_file:
91
- _safe_extract_zip(zip_file, dest_dir)
92
- top_levels = {name.split("/")[0] for name in zip_file.namelist() if name}
93
- if not top_levels:
94
- raise InstallError("Downloaded archive was empty.")
95
- if len(top_levels) != 1:
96
- raise InstallError("Unexpected archive layout.")
97
- return os.path.join(dest_dir, next(iter(top_levels)))
98
-
99
-
100
- def _run_git(args: list[str]) -> None:
101
- result = subprocess.run(
102
- args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
103
- )
104
- if result.returncode != 0:
105
- raise InstallError(result.stderr.strip() or "Git command failed.")
106
-
107
-
108
- def _safe_extract_zip(zip_file: zipfile.ZipFile, dest_dir: str) -> None:
109
- dest_root = os.path.realpath(dest_dir)
110
- for info in zip_file.infolist():
111
- extracted_path = os.path.realpath(os.path.join(dest_dir, info.filename))
112
- if extracted_path == dest_root or extracted_path.startswith(dest_root + os.sep):
113
- continue
114
- raise InstallError("Archive contains files outside the destination.")
115
- zip_file.extractall(dest_dir)
116
-
117
-
118
- def _validate_relative_path(path: str) -> None:
119
- if os.path.isabs(path) or os.path.normpath(path).startswith(".."):
120
- raise InstallError("Skill path must be a relative path inside the repo.")
121
-
122
-
123
- def _validate_skill_name(name: str) -> None:
124
- altsep = os.path.altsep
125
- if not name or os.path.sep in name or (altsep and altsep in name):
126
- raise InstallError("Skill name must be a single path segment.")
127
- if name in (".", ".."):
128
- raise InstallError("Invalid skill name.")
129
-
130
-
131
- def _git_sparse_checkout(
132
- repo_url: str, ref: str, paths: list[str], dest_dir: str
133
- ) -> str:
134
- repo_dir = os.path.join(dest_dir, "repo")
135
- clone_cmd = [
136
- "git",
137
- "clone",
138
- "--filter=blob:none",
139
- "--depth",
140
- "1",
141
- "--sparse",
142
- "--single-branch",
143
- "--branch",
144
- ref,
145
- repo_url,
146
- repo_dir,
147
- ]
148
- try:
149
- _run_git(clone_cmd)
150
- except InstallError:
151
- _run_git(
152
- [
153
- "git",
154
- "clone",
155
- "--filter=blob:none",
156
- "--depth",
157
- "1",
158
- "--sparse",
159
- "--single-branch",
160
- repo_url,
161
- repo_dir,
162
- ]
163
- )
164
- _run_git(["git", "-C", repo_dir, "sparse-checkout", "set", *paths])
165
- _run_git(["git", "-C", repo_dir, "checkout", ref])
166
- return repo_dir
167
-
168
-
169
- def _validate_skill(path: str) -> None:
170
- if not os.path.isdir(path):
171
- raise InstallError(f"Skill path not found: {path}")
172
- skill_md = os.path.join(path, "SKILL.md")
173
- if not os.path.isfile(skill_md):
174
- raise InstallError("SKILL.md not found in selected skill directory.")
175
-
176
-
177
- def _copy_skill(src: str, dest_dir: str) -> None:
178
- os.makedirs(os.path.dirname(dest_dir), exist_ok=True)
179
- if os.path.exists(dest_dir):
180
- raise InstallError(f"Destination already exists: {dest_dir}")
181
- shutil.copytree(src, dest_dir)
182
-
183
-
184
- def _build_repo_url(owner: str, repo: str) -> str:
185
- return f"https://github.com/{owner}/{repo}.git"
186
-
187
-
188
- def _build_repo_ssh(owner: str, repo: str) -> str:
189
- return f"git@github.com:{owner}/{repo}.git"
190
-
191
-
192
- def _prepare_repo(source: Source, method: str, tmp_dir: str) -> str:
193
- if method in ("download", "auto"):
194
- try:
195
- return _download_repo_zip(source.owner, source.repo, source.ref, tmp_dir)
196
- except InstallError as exc:
197
- if method == "download":
198
- raise
199
- err_msg = str(exc)
200
- if "HTTP 401" in err_msg or "HTTP 403" in err_msg or "HTTP 404" in err_msg:
201
- pass
202
- else:
203
- raise
204
- if method in ("git", "auto"):
205
- repo_url = source.repo_url or _build_repo_url(source.owner, source.repo)
206
- try:
207
- return _git_sparse_checkout(repo_url, source.ref, source.paths, tmp_dir)
208
- except InstallError:
209
- repo_url = _build_repo_ssh(source.owner, source.repo)
210
- return _git_sparse_checkout(repo_url, source.ref, source.paths, tmp_dir)
211
- raise InstallError("Unsupported method.")
212
-
213
-
214
- def _resolve_source(args: Args) -> Source:
215
- if args.url:
216
- owner, repo, ref, url_path = _parse_github_url(args.url, args.ref)
217
- if args.path is not None:
218
- paths = list(args.path)
219
- elif url_path:
220
- paths = [url_path]
221
- else:
222
- paths = []
223
- if not paths:
224
- raise InstallError("Missing --path for GitHub URL.")
225
- return Source(owner=owner, repo=repo, ref=ref, paths=paths)
226
-
227
- if not args.repo:
228
- raise InstallError("Provide --repo or --url.")
229
- if "://" in args.repo:
230
- return _resolve_source(
231
- Args(url=args.repo, repo=None, path=args.path, ref=args.ref)
232
- )
233
-
234
- repo_parts = [p for p in args.repo.split("/") if p]
235
- if len(repo_parts) != 2:
236
- raise InstallError("--repo must be in owner/repo format.")
237
- if not args.path:
238
- raise InstallError("Missing --path for --repo.")
239
- paths = list(args.path)
240
- return Source(
241
- owner=repo_parts[0],
242
- repo=repo_parts[1],
243
- ref=args.ref,
244
- paths=paths,
245
- )
246
-
247
-
248
- def _default_dest() -> str:
249
- return os.path.join(_opencode_home(), "skill")
250
-
251
-
252
- def _parse_args(argv: list[str]) -> Args:
253
- parser = argparse.ArgumentParser(description="Install a skill from GitHub.")
254
- parser.add_argument("--repo", help="owner/repo")
255
- parser.add_argument("--url", help="https://github.com/owner/repo[/tree/ref/path]")
256
- parser.add_argument(
257
- "--path",
258
- nargs="+",
259
- help="Path(s) to skill(s) inside repo",
260
- )
261
- parser.add_argument("--ref", default=DEFAULT_REF)
262
- parser.add_argument("--dest", help="Destination skills directory")
263
- parser.add_argument(
264
- "--name", help="Destination skill name (defaults to basename of path)"
265
- )
266
- parser.add_argument(
267
- "--method",
268
- choices=["auto", "download", "git"],
269
- default="auto",
270
- )
271
- return parser.parse_args(argv, namespace=Args())
272
-
273
-
274
- def main(argv: list[str]) -> int:
275
- args = _parse_args(argv)
276
- try:
277
- source = _resolve_source(args)
278
- source.ref = source.ref or args.ref
279
- if not source.paths:
280
- raise InstallError("No skill paths provided.")
281
- for path in source.paths:
282
- _validate_relative_path(path)
283
- dest_root = args.dest or _default_dest()
284
- tmp_dir = tempfile.mkdtemp(prefix="skill-install-", dir=_tmp_root())
285
- try:
286
- repo_root = _prepare_repo(source, args.method, tmp_dir)
287
- installed = []
288
- for path in source.paths:
289
- skill_name = args.name if len(source.paths) == 1 else None
290
- skill_name = skill_name or os.path.basename(path.rstrip("/"))
291
- _validate_skill_name(skill_name)
292
- if not skill_name:
293
- raise InstallError("Unable to derive skill name.")
294
- dest_dir = os.path.join(dest_root, skill_name)
295
- if os.path.exists(dest_dir):
296
- raise InstallError(f"Destination already exists: {dest_dir}")
297
- skill_src = os.path.join(repo_root, path)
298
- _validate_skill(skill_src)
299
- _copy_skill(skill_src, dest_dir)
300
- installed.append((skill_name, dest_dir))
301
- finally:
302
- if os.path.isdir(tmp_dir):
303
- shutil.rmtree(tmp_dir, ignore_errors=True)
304
- for skill_name, dest_dir in installed:
305
- print(f"Installed {skill_name} to {dest_dir}")
306
- return 0
307
- except InstallError as exc:
308
- print(f"Error: {exc}", file=sys.stderr)
309
- return 1
310
-
311
-
312
- if __name__ == "__main__":
313
- raise SystemExit(main(sys.argv[1:]))
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env python3
2
- """List skills from a GitHub repo path."""
3
-
4
- from __future__ import annotations
5
-
6
- import argparse
7
- import json
8
- import os
9
- import sys
10
- import urllib.error
11
-
12
- from github_utils import github_api_contents_url, github_request
13
-
14
- DEFAULT_REPO = "openai/skills"
15
- DEFAULT_PATH = "skills/.curated"
16
- DEFAULT_REF = "main"
17
-
18
-
19
- class ListError(Exception):
20
- pass
21
-
22
-
23
- class Args(argparse.Namespace):
24
- repo: str
25
- path: str
26
- ref: str
27
- format: str
28
-
29
-
30
- def _request(url: str) -> bytes:
31
- return github_request(url, "opencode-skill-list")
32
-
33
-
34
- def _opencode_home() -> str:
35
- return os.environ.get("OPENCODE_HOME", os.path.expanduser("~/.config/opencode"))
36
-
37
-
38
- def _installed_skills() -> set[str]:
39
- root = os.path.join(_opencode_home(), "skill")
40
- if not os.path.isdir(root):
41
- return set()
42
- entries = set()
43
- for name in os.listdir(root):
44
- path = os.path.join(root, name)
45
- if os.path.isdir(path):
46
- entries.add(name)
47
- return entries
48
-
49
-
50
- def _list_skills(repo: str, path: str, ref: str) -> list[str]:
51
- api_url = github_api_contents_url(repo, path, ref)
52
- try:
53
- payload = _request(api_url)
54
- except urllib.error.HTTPError as exc:
55
- if exc.code == 404:
56
- raise ListError(
57
- f"Skills path not found: https://github.com/{repo}/tree/{ref}/{path}"
58
- ) from exc
59
- raise ListError(f"Failed to fetch skills: HTTP {exc.code}") from exc
60
- data = json.loads(payload.decode("utf-8"))
61
- if not isinstance(data, list):
62
- raise ListError("Unexpected skills listing response.")
63
- skills = [item["name"] for item in data if item.get("type") == "dir"]
64
- return sorted(skills)
65
-
66
-
67
- def _parse_args(argv: list[str]) -> Args:
68
- parser = argparse.ArgumentParser(description="List skills.")
69
- parser.add_argument("--repo", default=DEFAULT_REPO)
70
- parser.add_argument(
71
- "--path",
72
- default=DEFAULT_PATH,
73
- help="Repo path to list (default: skills/.curated)",
74
- )
75
- parser.add_argument("--ref", default=DEFAULT_REF)
76
- parser.add_argument(
77
- "--format",
78
- choices=["text", "json"],
79
- default="text",
80
- help="Output format",
81
- )
82
- return parser.parse_args(argv, namespace=Args())
83
-
84
-
85
- def main(argv: list[str]) -> int:
86
- args = _parse_args(argv)
87
- try:
88
- skills = _list_skills(args.repo, args.path, args.ref)
89
- installed = _installed_skills()
90
- if args.format == "json":
91
- payload = [
92
- {"name": name, "installed": name in installed} for name in skills
93
- ]
94
- print(json.dumps(payload))
95
- else:
96
- for idx, name in enumerate(skills, start=1):
97
- suffix = " (already installed)" if name in installed else ""
98
- print(f"{idx}. {name}{suffix}")
99
- return 0
100
- except ListError as exc:
101
- print(f"Error: {exc}", file=sys.stderr)
102
- return 1
103
-
104
-
105
- if __name__ == "__main__":
106
- raise SystemExit(main(sys.argv[1:]))