Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
*
!Dockerfile
!entrypoint.sh
!git-export.py
!convert-gemini.auth.ts
!skills.yaml
!scripts/
!scripts/entrypoint.sh
!scripts/git-export.py
!scripts/convert-gemini.auth.ts
!scripts/install-skills.ts
4 changes: 3 additions & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ jobs:
with:
files: |
Dockerfile
skills.yaml
scripts/**

- name: Setup toolchain with mise
uses: jdx/mise-action@v2

- name: Run prek hooks
run: prek run --all-files --show-diff-on-failure --color=always

- name: Build Docker image if Dockerfile changed
- name: Build Docker image if Docker assets changed
if: steps.changed-files.outputs.any_changed == 'true'
run: |
docker build -t opencode-cli-pr:latest .
36 changes: 12 additions & 24 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,14 @@ FOE
ARG OPENCODE_VERSION=latest
ARG AZURE_FOUNDRY_PROVIDER_VERSION=0.2.0
ARG ENGRAM_VERSION=latest
ARG OPENCODE_BUILD_DIR=/usr/local/share/opencode-build

ENV OPENCODE_CONFIG_DIR=/etc/opencode
ENV OPENCODE_EXPERIMENTAL=1
ENV ENGRAM_DATA_DIR=/home/bun/.local/share/opencode/engram

ENV AGENT_BROWSER_ENGINE=lightpanda

# hadolint ignore=DL3045
COPY git-export.py git-export.py

# hadolint ignore=DL3003,SC2164
RUN <<'FOE'

Expand Down Expand Up @@ -178,30 +176,20 @@ chown -Rh bun:bun "$(echo ~bun)"

FOE

RUN <<'FOE'
source /etc/bash.bashrc

skills_dir="${OPENCODE_CONFIG_DIR}/skills"
mkdir -p "${skills_dir}"

skill_name="humanizer"
mkdir -p "${skills_dir}/${skill_name}"
curl -L 'https://raw.githubusercontent.com/blader/humanizer/refs/heads/main/SKILL.md' -o "${skills_dir}/${skill_name}/SKILL.md"
# hadolint ignore=DL3045
COPY scripts "${OPENCODE_BUILD_DIR}/scripts"
COPY skills.yaml "${OPENCODE_BUILD_DIR}/skills.yaml"

uv pip install --system "aleph-rlm[mcp]"
skill_name="aleph"
mkdir -p "${skills_dir}/${skill_name}"
curl -L 'https://raw.githubusercontent.com/Hmbown/aleph/refs/heads/main/docs/prompts/aleph.md' -o "${skills_dir}/${skill_name}/SKILL.md"
RUN <<'FOE'
source /etc/bash.bashrc

skill_name="changelog"
python git-export.py "https://github.com/sickn33/antigravity-awesome-skills/skills/changelog-automation" "${skills_dir}/${skill_name}" --force
BUN_INSTALL=/tmp/bun bun install --cwd "${OPENCODE_BUILD_DIR}/scripts" yaml || exit 1
bun "${OPENCODE_BUILD_DIR}/scripts/install-skills.ts" || exit 1

skill_name="agent-browser"
python git-export.py "https://github.com/vercel-labs/agent-browser/tree/main/skills/${skill_name}" "${skills_dir}/${skill_name}" --force
rm -rf "${OPENCODE_BUILD_DIR}"

rm -f git-export.py

cat >"${OPENCODE_CONFIG_DIR}/opencode.json" <<-'EOF'
cat >"${OPENCODE_CONFIG_DIR}/opencode.json" <<-'EOF'
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
Expand Down Expand Up @@ -260,8 +248,8 @@ EOF

FOE

COPY --chmod=0555 entrypoint.sh /entrypoint.sh
COPY --chmod=0555 convert-gemini.auth.ts /usr/local/bin/convert-gemini.auth.ts
COPY --chmod=0555 scripts/entrypoint.sh /entrypoint.sh
COPY --chmod=0555 scripts/convert-gemini.auth.ts /usr/local/bin/convert-gemini.auth.ts

USER bun:bun

Expand Down
File renamed without changes.
File renamed without changes.
164 changes: 71 additions & 93 deletions git-export.py → scripts/git-export.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ def info(message: str) -> None:
print(f"[git-export] {message}", flush=True)


def run_git(git_bin: str, args: list[str], cwd: Path | None = None, verbose: bool = False) -> None:
def run_git(
git_bin: str, args: list[str], cwd: Path | None = None, verbose: bool = False
) -> None:
cmd = [git_bin, *args]
if verbose:
location = str(cwd) if cwd else os.getcwd()
print(f"+ (cwd={location}) {' '.join(cmd)}")
try:
subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True, text=True, capture_output=True)
subprocess.run(
cmd,
cwd=str(cwd) if cwd else None,
check=True,
text=True,
capture_output=True,
)
except subprocess.CalledProcessError as e:
stderr = (e.stderr or "").strip()
stdout = (e.stdout or "").strip()
Expand Down Expand Up @@ -65,14 +73,16 @@ def parse_github_directory_url(url: str) -> tuple[str, str, str | None]:
- https://github.com/org/repo/blob/main/lang/ruby
"""
parsed = urllib.parse.urlparse(url)
if parsed.scheme not in ("http", "https") or parsed.netloc not in ("github.com", "www.github.com"):
if parsed.scheme not in ("http", "https") or parsed.netloc not in (
"github.com",
"www.github.com",
):
raise GitExportError(f"Not a supported GitHub URL: {url}")

parts = [p for p in parsed.path.split("/") if p]
if len(parts) < 3:
raise GitExportError(
"GitHub URL must include a directory path after owner/repo "
f"(got: {url})"
f"GitHub URL must include a directory path after owner/repo (got: {url})"
)

owner = parts[0]
Expand All @@ -87,8 +97,7 @@ def parse_github_directory_url(url: str) -> tuple[str, str, str | None]:
if rest[0] in ("tree", "blob"):
if len(rest) < 3:
raise GitExportError(
"tree/blob URLs must include ref and directory path, "
f"got: {url}"
f"tree/blob URLs must include ref and directory path, got: {url}"
)
ref = rest[1]
source = "/".join(rest[2:])
Expand Down Expand Up @@ -171,8 +180,18 @@ def export_directory(

info("Step 2/6: configuring sparse checkout")
step_start = time.perf_counter()
run_git(git_bin, ["sparse-checkout", "init", "--cone"], cwd=clone_dir, verbose=verbose)
run_git(git_bin, ["sparse-checkout", "set", "--", source_path], cwd=clone_dir, verbose=verbose)
run_git(
git_bin,
["sparse-checkout", "init", "--cone"],
cwd=clone_dir,
verbose=verbose,
)
run_git(
git_bin,
["sparse-checkout", "set", "--", source_path],
cwd=clone_dir,
verbose=verbose,
)
info(f"Step 2/6 complete in {time.perf_counter() - step_start:.1f}s")

info("Step 3/6: checking out requested ref/path")
Expand All @@ -184,7 +203,12 @@ def export_directory(
cwd=clone_dir,
verbose=verbose,
)
run_git(git_bin, ["checkout", "--detach", "FETCH_HEAD"], cwd=clone_dir, verbose=verbose)
run_git(
git_bin,
["checkout", "--detach", "FETCH_HEAD"],
cwd=clone_dir,
verbose=verbose,
)
else:
run_git(git_bin, ["checkout"], cwd=clone_dir, verbose=verbose)
info(f"Step 3/6 complete in {time.perf_counter() - step_start:.1f}s")
Expand Down Expand Up @@ -215,114 +239,68 @@ def export_directory(
copy_entry(child, output_dir / child.name)
info(f"Step 6/6 complete in {time.perf_counter() - step_start:.1f}s")

# Explicitly ensure .git is never left in output.
info("Finalizing export (removing .git if present)")
shutil.rmtree(output_dir / ".git", ignore_errors=True)
info(f"All done in {time.perf_counter() - start_total:.1f}s")
info(f"Export complete in {time.perf_counter() - start_total:.1f}s")


def main() -> int:
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description=(
"Export one directory from a huge Git repository using treeless + sparse clone."
)
)
parser.add_argument(
"input",
help=(
"Either a repository URL (legacy mode) or a full GitHub directory URL, "
"e.g. https://github.com/apache/avro/lang/ruby"
),
description="Export a directory from a GitHub repository"
)
parser.add_argument("source", help="GitHub directory URL or repo URL")
parser.add_argument("output", help="Output directory path")
parser.add_argument("--ref", default=None, help="Git ref to checkout")
parser.add_argument(
"arg2",
help=(
"In URL mode: destination output directory. "
"In legacy mode: source directory path."
),
"--path",
default=None,
help="Directory path inside the repo (required for raw repo URLs)",
)
parser.add_argument("--depth", type=int, default=1, help="Clone depth (default: 1)")
parser.add_argument("--git", default="git", help="Git binary to use (default: git)")
parser.add_argument(
"arg3",
nargs="?",
help="Legacy mode only: destination output directory.",
)
parser.add_argument(
"--source",
help=(
"Source directory path when using 2-arg mode with a repository URL input."
),
)
parser.add_argument(
"--ref",
"-r",
help="Branch/tag/ref to export (default: repository default branch)",
)
parser.add_argument(
"--depth",
type=int,
default=1,
help="Fetch depth for clone/fetch (default: 1)",
)
parser.add_argument(
"--force",
"-f",
action="store_true",
help="Overwrite output directory if it already exists",
)
parser.add_argument(
"--git-bin",
default="git",
help="Git binary path/name (default: git)",
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Print git commands while running",
"--force", action="store_true", help="Overwrite output if it exists"
)
parser.add_argument("--verbose", action="store_true", help="Print git commands")
return parser

args = parser.parse_args()

if args.depth < 1:
print("Error: --depth must be >= 1", file=sys.stderr)
return 2
def main(argv: list[str]) -> int:
parser = build_parser()
args = parser.parse_args(argv)

output_dir = Path(args.output)

try:
parsed_ref: str | None = None
if args.arg3 is not None:
# Legacy mode: repo source output
repo_url = args.input
source_path = args.arg2
output_path = args.arg3
if args.source.startswith("https://github.com/"):
repo_url, source_path, inferred_ref = parse_github_directory_url(
args.source
)
ref = args.ref if args.ref is not None else inferred_ref
else:
# URL mode: input output
output_path = args.arg2
if args.source:
repo_url = args.input
source_path = args.source
else:
repo_url, source_path, parsed_ref = parse_github_directory_url(args.input)
if not args.path:
raise GitExportError(
"--path is required when source is not a GitHub directory URL"
)
repo_url = args.source
source_path = normalize_source_path(args.path)
ref = args.ref

export_directory(
repo_url=repo_url,
source_path=source_path,
output_dir=Path(output_path),
ref=args.ref or parsed_ref,
output_dir=output_dir,
ref=ref,
depth=args.depth,
force=args.force,
git_bin=args.git_bin,
git_bin=args.git,
verbose=args.verbose,
)
return 0
except GitExportError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except FileNotFoundError as e:
print(f"Error: unable to execute git binary '{args.git_bin}': {e}", file=sys.stderr)
return 1

print(f"Export complete: {Path(output_path).resolve()}")
return 0
return 2


if __name__ == "__main__":
raise SystemExit(main())
raise SystemExit(main(sys.argv[1:]))
Loading
Loading