mirror of
https://github.com/Heretek-AI/awesome-opencode.git
synced 2026-11-07 02:35:58 -10:00
chore: rebrand to Heretek-AI, archive discussions, and prepare PR import
This commit is contained in:
1 parent
bcc5c03cec
commit
1cb5ad8c4b
9 files changed
+2558
-14
No files matched your search
File renamed without changes.
@@ -24,6 +24,12 @@
|
||||
|
||||
<br>
|
||||
|
||||
> [!NOTE]
|
||||
> **Active Hard Fork**: `Heretek-AI/awesome-opencode` is the actively maintained community continuation of `awesome-opencode`.
|
||||
> All original upstream issues, pull requests, and discussions are preserved and imported. Submissions and PRs are actively reviewed!
|
||||
|
||||
<br>
|
||||
|
||||
[**OFFICIAL**](#official) • [**PLUGINS**](#plugins) • [**THEMES**](#themes) • [**AGENTS**](#agents) • [**PROJECTS**](#projects) • [**RESOURCES**](#resources)
|
||||
|
||||
<br>
|
||||
@@ -1316,7 +1322,7 @@ suggestions. All processing stays local by default — no data leaves the machin
|
||||
</details>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Plugin via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Plugin via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -1396,7 +1402,7 @@ Also available for bat and Ghostty in the same repository.
|
||||
</details>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Theme via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Theme via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -1489,7 +1495,7 @@ Also available for bat and Ghostty in the same repository.
|
||||
</details>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add an Agent via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add an Agent via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -2084,7 +2090,7 @@ template (cowork-with-github). Install: drag-and-drop .plugin into Cowork.
|
||||
</details>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Project via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Project via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -2159,7 +2165,7 @@ template (cowork-with-github). Install: drag-and-drop .plugin into Cowork.
|
||||
</details>
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Resource via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Resource via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br><br>
|
||||
@@ -2167,7 +2173,7 @@ template (cowork-with-github). Install: drag-and-drop .plugin into Cowork.
|
||||
<div align="center">
|
||||
<h3>🤝 Contributing</h3>
|
||||
Found an Awesome Opencode project? <br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>Submit a Pull Request</b></a> to add it to the list!
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>Submit a Pull Request</b></a> to add it to the list!
|
||||
<br><br>
|
||||
<sub>Released under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0 Universal</a>.</sub>
|
||||
</div>
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://github.com/awesome-opencode/awesome-opencode/data/schema.json",
|
||||
"$id": "https://github.com/Heretek-AI/awesome-opencode/data/schema.json",
|
||||
"title": "Awesome Opencode Entry",
|
||||
"description": "Schema for validating YAML entries in the awesome-opencode list",
|
||||
"description": "Schema for validating YAML entries in the Heretek-AI/awesome-opencode list",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Exports all 75 discussions from upstream awesome-opencode/awesome-opencode
|
||||
to docs/archive/upstream-discussions.md for historical documentation and link preservation.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
UPSTREAM_OWNER = "awesome-opencode"
|
||||
UPSTREAM_NAME = "awesome-opencode"
|
||||
OUTPUT_FILE = os.path.join(os.path.dirname(__file__), "../../docs/archive/upstream-discussions.md")
|
||||
|
||||
def get_token():
|
||||
token = os.environ.get("GITHUB_TOKEN")
|
||||
if not token:
|
||||
try:
|
||||
token = subprocess.check_output(["gh", "auth", "token"], text=True).strip()
|
||||
except Exception as e:
|
||||
sys.exit(f"Failed to get GitHub token: {e}")
|
||||
return token
|
||||
|
||||
TOKEN = get_token()
|
||||
|
||||
QUERY = """
|
||||
query($cursor: String) {
|
||||
repository(owner: "awesome-opencode", name: "awesome-opencode") {
|
||||
discussions(first: 50, after: $cursor) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
endCursor
|
||||
}
|
||||
nodes {
|
||||
number
|
||||
title
|
||||
body
|
||||
url
|
||||
createdAt
|
||||
author {
|
||||
login
|
||||
}
|
||||
category {
|
||||
name
|
||||
}
|
||||
comments(first: 50) {
|
||||
nodes {
|
||||
author {
|
||||
login
|
||||
}
|
||||
createdAt
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def fetch_all_discussions():
|
||||
discussions = []
|
||||
cursor = None
|
||||
headers = {
|
||||
"Authorization": f"bearer {TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Heretek-Discussions-Archive"
|
||||
}
|
||||
|
||||
while True:
|
||||
payload = {"query": QUERY, "variables": {"cursor": cursor}}
|
||||
req = urllib.request.Request(
|
||||
"https://api.github.com/graphql",
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers=headers
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
repo_data = data.get("data", {}).get("repository", {})
|
||||
disc_conn = repo_data.get("discussions", {})
|
||||
nodes = disc_conn.get("nodes", [])
|
||||
discussions.extend(nodes)
|
||||
|
||||
page_info = disc_conn.get("pageInfo", {})
|
||||
if page_info.get("hasNextPage"):
|
||||
cursor = page_info.get("endCursor")
|
||||
else:
|
||||
break
|
||||
|
||||
discussions.sort(key=lambda d: d["number"])
|
||||
return discussions
|
||||
|
||||
def main():
|
||||
print("Fetching upstream discussions via GraphQL...")
|
||||
discussions = fetch_all_discussions()
|
||||
print(f"Fetched {len(discussions)} discussions. Writing archive to {OUTPUT_FILE}...")
|
||||
|
||||
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
|
||||
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
||||
f.write("# Upstream GitHub Discussions Archive\n\n")
|
||||
f.write("> **Notice**: This document preserves historical GitHub Discussions from [`awesome-opencode/awesome-opencode`](https://github.com/awesome-opencode/awesome-opencode/discussions) as part of the hard fork by Heretek-AI.\n\n")
|
||||
f.write(f"Total Discussions Archived: **{len(discussions)}**\n\n---\n\n")
|
||||
|
||||
for d in discussions:
|
||||
num = d["number"]
|
||||
title = d["title"]
|
||||
author = d["author"]["login"] if d.get("author") else "ghost"
|
||||
created_at = d["createdAt"]
|
||||
category = d.get("category", {}).get("name", "General")
|
||||
url = d["url"]
|
||||
body = d.get("body") or "*No content provided.*"
|
||||
comments = d.get("comments", {}).get("nodes", [])
|
||||
|
||||
f.write(f"## Discussion #{num}: {title}\n\n")
|
||||
f.write(f"- **Original URL**: [{url}]({url})\n")
|
||||
f.write(f"- **Author**: [@{author}](https://github.com/{author})\n")
|
||||
f.write(f"- **Created At**: `{created_at}`\n")
|
||||
f.write(f"- **Category**: `{category}`\n\n")
|
||||
f.write("### Post Content\n\n")
|
||||
f.write(f"{body}\n\n")
|
||||
|
||||
if comments:
|
||||
f.write(f"### Comments ({len(comments)})\n\n")
|
||||
for c in comments:
|
||||
c_author = c["author"]["login"] if c.get("author") else "ghost"
|
||||
c_created = c["createdAt"]
|
||||
c_body = c.get("body") or ""
|
||||
f.write(f"#### [@{c_author}](https://github.com/{c_author}) commented on `{c_created}`:\n\n")
|
||||
f.write(f"{c_body}\n\n")
|
||||
|
||||
f.write("---\n\n")
|
||||
|
||||
print(f"Successfully archived {len(discussions)} discussions to {OUTPUT_FILE}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Imports all issues from awesome-opencode/awesome-opencode to Heretek-AI/awesome-opencode
|
||||
using the GitHub Issue Import API (golden-comet-preview) to preserve timestamps,
|
||||
closed status, and comments without generating spam notifications.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
UPSTREAM_REPO = "awesome-opencode/awesome-opencode"
|
||||
TARGET_REPO = "Heretek-AI/awesome-opencode"
|
||||
MAPPING_FILE = os.path.join(os.path.dirname(__file__), "upstream_issues_map.json")
|
||||
|
||||
def get_token():
|
||||
token = os.environ.get("GITHUB_TOKEN")
|
||||
if not token:
|
||||
try:
|
||||
token = subprocess.check_output(["gh", "auth", "token"], text=True).strip()
|
||||
except Exception as e:
|
||||
sys.exit(f"Failed to get GitHub token via gh CLI: {e}")
|
||||
return token
|
||||
|
||||
TOKEN = get_token()
|
||||
|
||||
def gh_api_request(url, method="GET", data=None, accept="application/vnd.github.v3+json"):
|
||||
headers = {
|
||||
"Authorization": f"token {TOKEN}",
|
||||
"Accept": accept,
|
||||
"User-Agent": "Heretek-HardFork-Migration"
|
||||
}
|
||||
body = None
|
||||
if data is not None:
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
content = resp.read().decode("utf-8")
|
||||
return json.loads(content) if content else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
error_body = e.read().decode("utf-8")
|
||||
print(f"HTTPError {e.code} for {method} {url}: {error_body}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
def sanitize_mentions(text):
|
||||
if not text:
|
||||
return ""
|
||||
# Avoid triggering accidental @mentions by converting @username to markdown links
|
||||
# but ignore email addresses (e.g. [email protected]) and code blocks
|
||||
def replace_mention(match):
|
||||
prefix = match.group(1)
|
||||
username = match.group(2)
|
||||
return f"{prefix}[@{username}](https://github.com/{username})"
|
||||
|
||||
# Match @username when preceded by start of line or whitespace
|
||||
return re.sub(r'(^|[\s(])@([a-zA-Z0-9_\-]+)', replace_mention, text)
|
||||
|
||||
def fetch_upstream_issues():
|
||||
print(f"Fetching issues from {UPSTREAM_REPO}...")
|
||||
issues = []
|
||||
page = 1
|
||||
while True:
|
||||
url = f"https://api.github.com/repos/{UPSTREAM_REPO}/issues?state=all&per_page=100&page={page}&direction=asc"
|
||||
items = gh_api_request(url)
|
||||
if not items:
|
||||
break
|
||||
for item in items:
|
||||
if "pull_request" not in item:
|
||||
issues.append(item)
|
||||
page += 1
|
||||
|
||||
issues.sort(key=lambda x: x["number"])
|
||||
print(f"Found {len(issues)} upstream issues (excluding PRs).")
|
||||
return issues
|
||||
|
||||
def fetch_comments(comments_url):
|
||||
all_comments = []
|
||||
page = 1
|
||||
while True:
|
||||
url = f"{comments_url}?per_page=100&page={page}"
|
||||
items = gh_api_request(url)
|
||||
if not items:
|
||||
break
|
||||
all_comments.extend(items)
|
||||
page += 1
|
||||
return all_comments
|
||||
|
||||
def import_issue(issue, comments):
|
||||
num = issue["number"]
|
||||
title = issue["title"]
|
||||
state = issue["state"].upper()
|
||||
author = issue["user"]["login"] if issue.get("user") else "unknown"
|
||||
created_at = issue["created_at"]
|
||||
closed_at = issue.get("closed_at")
|
||||
raw_body = issue.get("body") or "*No description provided.*"
|
||||
sanitized_body = sanitize_mentions(raw_body)
|
||||
|
||||
provenance_banner = (
|
||||
f"> [!NOTE]\n"
|
||||
f"> **Imported from upstream issue**: [{UPSTREAM_REPO}#{num}](https://github.com/{UPSTREAM_REPO}/issues/{num})\n"
|
||||
f"> **Original Author**: [@{author}](https://github.com/{author})\n"
|
||||
f"> **Created At**: `{created_at}`\n"
|
||||
f"> **Original State**: `{state}`"
|
||||
)
|
||||
if closed_at:
|
||||
provenance_banner += f"\n> **Closed At**: `{closed_at}`"
|
||||
|
||||
full_body = f"{provenance_banner}\n\n---\n\n{sanitized_body}"
|
||||
|
||||
imported_comments = []
|
||||
for c in comments:
|
||||
c_author = c["user"]["login"] if c.get("user") else "unknown"
|
||||
c_created_at = c["created_at"]
|
||||
c_body = sanitize_mentions(c.get("body") or "")
|
||||
c_banner = f"> **Comment by [@{c_author}](https://github.com/{c_author}) on `{c_created_at}`**:"
|
||||
imported_comments.append({
|
||||
"created_at": c_created_at,
|
||||
"body": f"{c_banner}\n\n{c_body}"
|
||||
})
|
||||
|
||||
payload = {
|
||||
"issue": {
|
||||
"title": f"[Upstream #{num}] {title}",
|
||||
"body": full_body,
|
||||
"created_at": created_at,
|
||||
"closed": (state == "CLOSED"),
|
||||
"labels": [l["name"] for l in issue.get("labels", [])]
|
||||
}
|
||||
}
|
||||
if closed_at and state == "CLOSED":
|
||||
payload["issue"]["closed_at"] = closed_at
|
||||
if imported_comments:
|
||||
payload["comments"] = imported_comments
|
||||
|
||||
import_url = f"https://api.github.com/repos/{TARGET_REPO}/import/issues"
|
||||
res = gh_api_request(
|
||||
import_url,
|
||||
method="POST",
|
||||
data=payload,
|
||||
accept="application/vnd.github.golden-comet-preview+json"
|
||||
)
|
||||
|
||||
poll_url = res.get("url")
|
||||
if not poll_url:
|
||||
return res
|
||||
|
||||
# Poll status until imported
|
||||
for _ in range(30):
|
||||
time.sleep(1.0)
|
||||
status_res = gh_api_request(
|
||||
poll_url,
|
||||
method="GET",
|
||||
accept="application/vnd.github.golden-comet-preview+json"
|
||||
)
|
||||
status = status_res.get("status")
|
||||
if status == "imported":
|
||||
return status_res
|
||||
elif status == "failed":
|
||||
print(f"Import failed for upstream #{num}: {status_res}", file=sys.stderr)
|
||||
return status_res
|
||||
|
||||
return status_res
|
||||
|
||||
def load_mapping():
|
||||
if os.path.exists(MAPPING_FILE):
|
||||
with open(MAPPING_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def save_mapping(mapping):
|
||||
with open(MAPPING_FILE, "w") as f:
|
||||
json.dump(mapping, f, indent=2)
|
||||
|
||||
def main():
|
||||
mapping = load_mapping()
|
||||
issues = fetch_upstream_issues()
|
||||
print(f"Total upstream issues to process: {len(issues)}")
|
||||
|
||||
for idx, issue in enumerate(issues, 1):
|
||||
num = issue["number"]
|
||||
if str(num) in mapping:
|
||||
print(f"[{idx}/{len(issues)}] Upstream #{num} already imported as {mapping[str(num)]['issue_url']}. Skipping.")
|
||||
continue
|
||||
|
||||
print(f"[{idx}/{len(issues)}] Importing Upstream #{num}: {issue['title'][:50]}...")
|
||||
comments = []
|
||||
if issue.get("comments", 0) > 0 and issue.get("comments_url"):
|
||||
comments = fetch_comments(issue["comments_url"])
|
||||
|
||||
retries = 3
|
||||
while retries > 0:
|
||||
try:
|
||||
res = import_issue(issue, comments)
|
||||
issue_url = res.get("issue_url")
|
||||
print(f" ✓ Imported upstream #{num} -> {issue_url}")
|
||||
mapping[str(num)] = {
|
||||
"upstream_number": num,
|
||||
"title": issue["title"],
|
||||
"state": issue["state"],
|
||||
"import_id": res.get("id"),
|
||||
"issue_url": issue_url
|
||||
}
|
||||
save_mapping(mapping)
|
||||
time.sleep(1.5) # Avoid secondary rate limits
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (403, 429):
|
||||
print(" Rate limited, sleeping 30s...", file=sys.stderr)
|
||||
time.sleep(30)
|
||||
retries -= 1
|
||||
else:
|
||||
print(f" Error importing #{num}: {e}", file=sys.stderr)
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Unexpected error importing #{num}: {e}", file=sys.stderr)
|
||||
time.sleep(2)
|
||||
retries -= 1
|
||||
|
||||
print("\nIssue migration complete!")
|
||||
print(f"Total imported issues: {len(mapping)} / {len(issues)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Imports all open pull requests from awesome-opencode/awesome-opencode to Heretek-AI/awesome-opencode:
|
||||
1. Pushes PR commit refs to origin branches (upstream-pr/<number>)
|
||||
2. Recreates the PRs on Heretek-AI/awesome-opencode against main with complete provenance and author attribution.
|
||||
3. Automatically tracks progress in upstream_prs_map.json so runs are idempotent and resumable.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
UPSTREAM_REPO = "awesome-opencode/awesome-opencode"
|
||||
TARGET_REPO = "Heretek-AI/awesome-opencode"
|
||||
MAPPING_FILE = os.path.join(os.path.dirname(__file__), "upstream_prs_map.json")
|
||||
|
||||
def get_token():
|
||||
token = os.environ.get("GITHUB_TOKEN")
|
||||
if not token:
|
||||
try:
|
||||
token = subprocess.check_output(["gh", "auth", "token"], text=True).strip()
|
||||
except Exception as e:
|
||||
sys.exit(f"Failed to get GitHub token via gh CLI: {e}")
|
||||
return token
|
||||
|
||||
TOKEN = get_token()
|
||||
|
||||
def gh_api_request(url, method="GET", data=None):
|
||||
headers = {
|
||||
"Authorization": f"token {TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"User-Agent": "Heretek-HardFork-PR-Migration"
|
||||
}
|
||||
body = None
|
||||
if data is not None:
|
||||
body = json.dumps(data).encode("utf-8")
|
||||
headers["Content-Type"] = "application/json"
|
||||
|
||||
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
content = resp.read().decode("utf-8")
|
||||
return json.loads(content) if content else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
error_body = e.read().decode("utf-8")
|
||||
print(f"HTTPError {e.code} for {method} {url}: {error_body}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
def sanitize_mentions(text):
|
||||
if not text:
|
||||
return ""
|
||||
def replace_mention(match):
|
||||
prefix = match.group(1)
|
||||
username = match.group(2)
|
||||
return f"{prefix}[@{username}](https://github.com/{username})"
|
||||
return re.sub(r'(^|[\s(])@([a-zA-Z0-9_\-]+)', replace_mention, text)
|
||||
|
||||
def fetch_upstream_open_prs():
|
||||
print(f"Fetching open PRs from {UPSTREAM_REPO}...")
|
||||
cmd = [
|
||||
"gh", "pr", "list",
|
||||
"-R", UPSTREAM_REPO,
|
||||
"--state", "open",
|
||||
"--limit", "600",
|
||||
"--json", "number,title,body,author,createdAt,headRefName,url"
|
||||
]
|
||||
out = subprocess.check_output(cmd, text=True)
|
||||
prs = json.loads(out)
|
||||
prs.sort(key=lambda x: x["number"])
|
||||
print(f"Found {len(prs)} open PRs on upstream.")
|
||||
return prs
|
||||
|
||||
def push_pr_branches_in_batches(prs, batch_size=50):
|
||||
print(f"Checking and pushing {len(prs)} PR branches to origin...")
|
||||
refspecs = []
|
||||
for pr in prs:
|
||||
num = pr["number"]
|
||||
local_ref = f"refs/remotes/upstream-pr/{num}"
|
||||
# verify local ref exists
|
||||
res = subprocess.run(["git", "rev-parse", "--verify", local_ref], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if res.returncode == 0:
|
||||
refspecs.append(f"{local_ref}:refs/heads/upstream-pr/{num}")
|
||||
else:
|
||||
print(f"Warning: local ref {local_ref} not found for PR #{num}!", file=sys.stderr)
|
||||
|
||||
for i in range(0, len(refspecs), batch_size):
|
||||
chunk = refspecs[i:i + batch_size]
|
||||
print(f"Pushing branches batch {i+1} to {min(i + batch_size, len(refspecs))} of {len(refspecs)}...")
|
||||
cmd = ["git", "push", "origin"] + chunk
|
||||
subprocess.check_call(cmd)
|
||||
|
||||
print("All PR branches pushed successfully!")
|
||||
|
||||
def load_mapping():
|
||||
if os.path.exists(MAPPING_FILE):
|
||||
with open(MAPPING_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
return {}
|
||||
|
||||
def save_mapping(mapping):
|
||||
with open(MAPPING_FILE, "w") as f:
|
||||
json.dump(mapping, f, indent=2)
|
||||
|
||||
def create_target_pr(pr):
|
||||
num = pr["number"]
|
||||
title = pr["title"]
|
||||
author = pr["author"]["login"] if pr.get("author") else "unknown"
|
||||
created_at = pr["createdAt"]
|
||||
head_ref = pr.get("headRefName", "branch")
|
||||
sanitized_body = sanitize_mentions(pr.get("body") or "*No description provided.*")
|
||||
|
||||
body = (
|
||||
f"> [!NOTE]\n"
|
||||
f"> **Original Pull Request**: [{UPSTREAM_REPO}#{num}](https://github.com/{UPSTREAM_REPO}/pull/{num})\n"
|
||||
f"> **Original Author**: [@{author}](https://github.com/{author})\n"
|
||||
f"> **Submitted At**: `{created_at}`\n"
|
||||
f"> **Original Branch**: `{head_ref}`\n\n"
|
||||
f"---\n\n"
|
||||
f"{sanitized_body}"
|
||||
)
|
||||
|
||||
url = f"https://api.github.com/repos/{TARGET_REPO}/pulls"
|
||||
payload = {
|
||||
"title": f"[Upstream #{num}] {title}",
|
||||
"head": f"upstream-pr/{num}",
|
||||
"base": "main",
|
||||
"body": body,
|
||||
"maintainer_can_modify": True
|
||||
}
|
||||
return gh_api_request(url, method="POST", data=payload)
|
||||
|
||||
def main():
|
||||
prs = fetch_upstream_open_prs()
|
||||
mapping = load_mapping()
|
||||
|
||||
# Step 1: Ensure all branches are pushed to origin
|
||||
push_pr_branches_in_batches(prs)
|
||||
|
||||
# Step 2: Create PRs on GitHub
|
||||
print(f"\nCreating {len(prs)} Pull Requests on {TARGET_REPO}...")
|
||||
for idx, pr in enumerate(prs, 1):
|
||||
num = pr["number"]
|
||||
if str(num) in mapping:
|
||||
print(f"[{idx}/{len(prs)}] PR #{num} already migrated as {mapping[str(num)]['pr_url']}. Skipping.")
|
||||
continue
|
||||
|
||||
print(f"[{idx}/{len(prs)}] Creating PR for Upstream #{num}: {pr['title'][:50]}...")
|
||||
retries = 5
|
||||
backoff = 30
|
||||
while retries > 0:
|
||||
try:
|
||||
new_pr = create_target_pr(pr)
|
||||
pr_url = new_pr.get("html_url")
|
||||
new_num = new_pr.get("number")
|
||||
print(f" ✓ Created PR #{new_num} -> {pr_url}")
|
||||
mapping[str(num)] = {
|
||||
"upstream_number": num,
|
||||
"new_pr_number": new_num,
|
||||
"pr_url": pr_url,
|
||||
"title": pr["title"]
|
||||
}
|
||||
save_mapping(mapping)
|
||||
time.sleep(1.8) # Rate limiting sleep
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code in (403, 429):
|
||||
print(f" Secondary rate limit hit! Sleeping for {backoff}s before retry...", file=sys.stderr)
|
||||
time.sleep(backoff)
|
||||
backoff *= 2
|
||||
retries -= 1
|
||||
elif e.code == 422:
|
||||
# Likely branch has no diff against main or was already merged/empty
|
||||
error_text = str(e)
|
||||
print(f" Unprocessable Entity (422) for PR #{num} (possibly already merged or identical to main). Recording as skipped.", file=sys.stderr)
|
||||
mapping[str(num)] = {
|
||||
"upstream_number": num,
|
||||
"status": "skipped_422",
|
||||
"title": pr["title"]
|
||||
}
|
||||
save_mapping(mapping)
|
||||
break
|
||||
else:
|
||||
print(f" HTTP error {e.code} for PR #{num}: {e}", file=sys.stderr)
|
||||
time.sleep(5)
|
||||
retries -= 1
|
||||
except Exception as e:
|
||||
print(f" Unexpected error for PR #{num}: {e}", file=sys.stderr)
|
||||
time.sleep(5)
|
||||
retries -= 1
|
||||
|
||||
print("\nPR migration complete!")
|
||||
print(f"Total processed PRs: {len(mapping)} / {len(prs)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,303 @@
|
||||
{
|
||||
"24": {
|
||||
"upstream_number": 24,
|
||||
"title": "Add plugin sections",
|
||||
"state": "closed",
|
||||
"import_id": 16769974,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/3"
|
||||
},
|
||||
"67": {
|
||||
"upstream_number": 67,
|
||||
"title": "Samual",
|
||||
"state": "closed",
|
||||
"import_id": 16769975,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/4"
|
||||
},
|
||||
"99": {
|
||||
"upstream_number": 99,
|
||||
"title": "change all https://github.com/sst links to https://github.com/anomalyco",
|
||||
"state": "closed",
|
||||
"import_id": 16769976,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/5"
|
||||
},
|
||||
"100": {
|
||||
"upstream_number": 100,
|
||||
"title": "Incorrect link for the Smart Voice Notify https://github.com/MasuRii/opencode-smart-voice-notify",
|
||||
"state": "closed",
|
||||
"import_id": 16769977,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/6"
|
||||
},
|
||||
"156": {
|
||||
"upstream_number": 156,
|
||||
"title": "\ud83d\udc1b OpenCode v1.1.50 MCP Bug - All MCP Tools Failing",
|
||||
"state": "closed",
|
||||
"import_id": 16769978,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/7"
|
||||
},
|
||||
"190": {
|
||||
"upstream_number": 190,
|
||||
"title": "[Bug]: Not reading github copilot premium request usage correctly",
|
||||
"state": "closed",
|
||||
"import_id": 16769979,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/8"
|
||||
},
|
||||
"195": {
|
||||
"upstream_number": 195,
|
||||
"title": "Offering to help maintain the list",
|
||||
"state": "closed",
|
||||
"import_id": 16769980,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/9"
|
||||
},
|
||||
"216": {
|
||||
"upstream_number": 216,
|
||||
"title": "Add SuperLocalMemory \u2014 Memory MCP server compatible with OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16769981,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/10"
|
||||
},
|
||||
"228": {
|
||||
"upstream_number": 228,
|
||||
"title": "Add opencode-remote-control: Multi-platform remote control via Telegram/Feishu/WeChat",
|
||||
"state": "open",
|
||||
"import_id": 16769982,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/11"
|
||||
},
|
||||
"230": {
|
||||
"upstream_number": 230,
|
||||
"title": "Add zosma-qa \u2014 QA agents for OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16769983,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/12"
|
||||
},
|
||||
"236": {
|
||||
"upstream_number": 236,
|
||||
"title": "Add AnveVoice \u2014 Voice AI Plugin for OpenCode Agents",
|
||||
"state": "open",
|
||||
"import_id": 16769984,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/13"
|
||||
},
|
||||
"240": {
|
||||
"upstream_number": 240,
|
||||
"title": "Add Clarvia \u2014 AEO quality scanner for MCP tools and AI agents",
|
||||
"state": "closed",
|
||||
"import_id": 16769985,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/14"
|
||||
},
|
||||
"241": {
|
||||
"upstream_number": 241,
|
||||
"title": "Add dreampowers: A complete AI skill set for Chinese novel writing",
|
||||
"state": "open",
|
||||
"import_id": 16769986,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/15"
|
||||
},
|
||||
"242": {
|
||||
"upstream_number": 242,
|
||||
"title": "Consider listing in awesome-codex-plugins",
|
||||
"state": "open",
|
||||
"import_id": 16769987,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/16"
|
||||
},
|
||||
"265": {
|
||||
"upstream_number": 265,
|
||||
"title": "Add opencode-auto-resume",
|
||||
"state": "closed",
|
||||
"import_id": 16769988,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/17"
|
||||
},
|
||||
"280": {
|
||||
"upstream_number": 280,
|
||||
"title": "Add opencode-easy-workflow",
|
||||
"state": "closed",
|
||||
"import_id": 16769989,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/18"
|
||||
},
|
||||
"295": {
|
||||
"upstream_number": 295,
|
||||
"title": "The comportement of home end is unexpected when we are writing a new prompt",
|
||||
"state": "open",
|
||||
"import_id": 16769990,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/19"
|
||||
},
|
||||
"307": {
|
||||
"upstream_number": 307,
|
||||
"title": "Add Skills\u5b9d (skilery.com) - Chinese AI Skills Marketplace",
|
||||
"state": "open",
|
||||
"import_id": 16769991,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/20"
|
||||
},
|
||||
"332": {
|
||||
"upstream_number": 332,
|
||||
"title": "Add \u4e2d\u533b\u4e4b\u5fc3 Oh My TCM Powers",
|
||||
"state": "open",
|
||||
"import_id": 16769992,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/21"
|
||||
},
|
||||
"333": {
|
||||
"upstream_number": 333,
|
||||
"title": "Add Qi Men Dun Jia: Eight Gates Huaqi Array (\u5947\u95e8\u9041\u7532 \u516b\u95e8\u5316\u6c14\u9635)",
|
||||
"state": "open",
|
||||
"import_id": 16769993,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/22"
|
||||
},
|
||||
"343": {
|
||||
"upstream_number": 343,
|
||||
"title": "Add extra maintainers?",
|
||||
"state": "open",
|
||||
"import_id": 16769994,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/23"
|
||||
},
|
||||
"398": {
|
||||
"upstream_number": 398,
|
||||
"title": "Add opencode-timings plugin",
|
||||
"state": "open",
|
||||
"import_id": 16769995,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/24"
|
||||
},
|
||||
"399": {
|
||||
"upstream_number": 399,
|
||||
"title": "Add n3rv \u2014 Engineering infrastructure for opencode agents",
|
||||
"state": "closed",
|
||||
"import_id": 16769996,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/25"
|
||||
},
|
||||
"409": {
|
||||
"upstream_number": 409,
|
||||
"title": "Community project: OneTicket Core \u2013 turning GitHub into an agentic delivery pipeline powered by OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16769997,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/26"
|
||||
},
|
||||
"430": {
|
||||
"upstream_number": 430,
|
||||
"title": "Add AIDevOps to plugins",
|
||||
"state": "open",
|
||||
"import_id": 16769998,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/27"
|
||||
},
|
||||
"453": {
|
||||
"upstream_number": 453,
|
||||
"title": "Agents",
|
||||
"state": "open",
|
||||
"import_id": 16769999,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/28"
|
||||
},
|
||||
"475": {
|
||||
"upstream_number": 475,
|
||||
"title": "Add Zara Agent OPC - Empathetic AI Engineering Partner",
|
||||
"state": "open",
|
||||
"import_id": 16770000,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/29"
|
||||
},
|
||||
"484": {
|
||||
"upstream_number": 484,
|
||||
"title": "Add Skill Hub \u2014 Multi-Platform Skills Discovery",
|
||||
"state": "open",
|
||||
"import_id": 16770001,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/30"
|
||||
},
|
||||
"502": {
|
||||
"upstream_number": 502,
|
||||
"title": "Add Data Olympus as governed project knowledge MCP server",
|
||||
"state": "open",
|
||||
"import_id": 16770002,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/31"
|
||||
},
|
||||
"507": {
|
||||
"upstream_number": 507,
|
||||
"title": "Add OpenCode Agent Variants",
|
||||
"state": "open",
|
||||
"import_id": 16770003,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/32"
|
||||
},
|
||||
"509": {
|
||||
"upstream_number": 509,
|
||||
"title": "Menu bar tool for visualizing token usage and estimated costs - Tokenscope",
|
||||
"state": "open",
|
||||
"import_id": 16770004,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/33"
|
||||
},
|
||||
"517": {
|
||||
"upstream_number": 517,
|
||||
"title": "how to change plan or build?",
|
||||
"state": "open",
|
||||
"import_id": 16770005,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/34"
|
||||
},
|
||||
"520": {
|
||||
"upstream_number": 520,
|
||||
"title": "UI regressions flow introduced in version 1.18.0",
|
||||
"state": "open",
|
||||
"import_id": 16770006,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/35"
|
||||
},
|
||||
"607": {
|
||||
"upstream_number": 607,
|
||||
"title": "Submit IdeaHunter for the tools/resources list",
|
||||
"state": "open",
|
||||
"import_id": 16770007,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/36"
|
||||
},
|
||||
"639": {
|
||||
"upstream_number": 639,
|
||||
"title": "Resource suggestion: TANCO SkillHub for reusable OpenCode/Agent Skills",
|
||||
"state": "open",
|
||||
"import_id": 16770008,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/37"
|
||||
},
|
||||
"641": {
|
||||
"upstream_number": 641,
|
||||
"title": "Add auto-resume",
|
||||
"state": "open",
|
||||
"import_id": 16770009,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/38"
|
||||
},
|
||||
"643": {
|
||||
"upstream_number": 643,
|
||||
"title": "Is this project still actively maintained?",
|
||||
"state": "open",
|
||||
"import_id": 16770010,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/39"
|
||||
},
|
||||
"651": {
|
||||
"upstream_number": 651,
|
||||
"title": "Listing request: SandBase CLI project for OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16770011,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/40"
|
||||
},
|
||||
"702": {
|
||||
"upstream_number": 702,
|
||||
"title": "Add AegisOS Enterprise AI Workforce OS",
|
||||
"state": "open",
|
||||
"import_id": 16770012,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/41"
|
||||
},
|
||||
"714": {
|
||||
"upstream_number": 714,
|
||||
"title": "Show Opencode V1 and V2 support",
|
||||
"state": "open",
|
||||
"import_id": 16770013,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/42"
|
||||
},
|
||||
"763": {
|
||||
"upstream_number": 763,
|
||||
"title": "Add DataCloak: secret/PII cloak plugin for OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16770014,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/43"
|
||||
},
|
||||
"775": {
|
||||
"upstream_number": 775,
|
||||
"title": "Add opencode-seatbelt: macOS kernel sandbox plugin for OpenCode",
|
||||
"state": "open",
|
||||
"import_id": 16770015,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/44"
|
||||
},
|
||||
"868": {
|
||||
"upstream_number": 868,
|
||||
"title": "Listing request: Telai, vertical tabs terminal for Opencode",
|
||||
"state": "open",
|
||||
"import_id": 16770016,
|
||||
"issue_url": "https://api.github.com/repos/Heretek-AI/awesome-opencode/issues/45"
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,12 @@
|
||||
|
||||
<br>
|
||||
|
||||
> [!NOTE]
|
||||
> **Active Hard Fork**: `Heretek-AI/awesome-opencode` is the actively maintained community continuation of `awesome-opencode`.
|
||||
> All original upstream issues, pull requests, and discussions are preserved and imported. Submissions and PRs are actively reviewed!
|
||||
|
||||
<br>
|
||||
|
||||
[**OFFICIAL**](#official) • [**PLUGINS**](#plugins) • [**THEMES**](#themes) • [**AGENTS**](#agents) • [**PROJECTS**](#projects) • [**RESOURCES**](#resources)
|
||||
|
||||
<br>
|
||||
@@ -55,7 +61,7 @@
|
||||
{{PLUGINS}}
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Plugin via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Plugin via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -69,7 +75,7 @@
|
||||
{{THEMES}}
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Theme via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Theme via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -83,7 +89,7 @@
|
||||
{{AGENTS}}
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add an Agent via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add an Agent via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -97,7 +103,7 @@
|
||||
{{PROJECTS}}
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Project via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Project via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -111,7 +117,7 @@
|
||||
{{RESOURCES}}
|
||||
|
||||
<br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Resource via PR</b></a>
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>➕ Add a Resource via PR</b></a>
|
||||
</details>
|
||||
|
||||
<br><br>
|
||||
@@ -119,7 +125,7 @@
|
||||
<div align="center">
|
||||
<h3>🤝 Contributing</h3>
|
||||
Found an Awesome Opencode project? <br>
|
||||
<a href="https://github.com/awesome-opencode/awesome-opencode/blob/main/contributing.md"><b>Submit a Pull Request</b></a> to add it to the list!
|
||||
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/contributing.md"><b>Submit a Pull Request</b></a> to add it to the list!
|
||||
<br><br>
|
||||
<sub>Released under <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0 Universal</a>.</sub>
|
||||
</div>
|
||||
Reference in new issue
Block a user