replace shell update scripts with Go tooling
This commit is contained in:
parent
df368945bc
commit
2f252345d9
2
.github/workflows/sync-skills.yml
vendored
2
.github/workflows/sync-skills.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Sync skills from clawdbot
|
- name: Sync skills from clawdbot
|
||||||
run: scripts/sync-skills.sh
|
run: go run ./cmd/sync-skills
|
||||||
|
|
||||||
- name: Commit & push if changed
|
- name: Commit & push if changed
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/update-tools.yml
vendored
2
.github/workflows/update-tools.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Update tool versions and hashes
|
- name: Update tool versions and hashes
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
run: scripts/update-tools.sh
|
run: go run ./cmd/update-tools
|
||||||
|
|
||||||
- name: Commit & push if changed
|
- name: Commit & push if changed
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -65,7 +65,7 @@ inputs.nix-steipete-tools.packages.aarch64-darwin.peekaboo
|
|||||||
Skills are vendored from [clawdbot/clawdbot](https://github.com/clawdbot/clawdbot) main branch. No pinning - we track latest.
|
Skills are vendored from [clawdbot/clawdbot](https://github.com/clawdbot/clawdbot) main branch. No pinning - we track latest.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/sync-skills.sh
|
go run ./cmd/sync-skills
|
||||||
```
|
```
|
||||||
|
|
||||||
Pulls latest main via sparse checkout, only updates files when contents actually change.
|
Pulls latest main via sparse checkout, only updates files when contents actually change.
|
||||||
@ -75,7 +75,7 @@ Pulls latest main via sparse checkout, only updates files when contents actually
|
|||||||
Tools track upstream GitHub releases directly (not Homebrew).
|
Tools track upstream GitHub releases directly (not Homebrew).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scripts/update-tools.sh
|
go run ./cmd/update-tools
|
||||||
```
|
```
|
||||||
|
|
||||||
Fetches latest release versions/URLs/hashes and updates the Nix expressions. Oracle uses pnpm and auto-derives its hash via build mismatch.
|
Fetches latest release versions/URLs/hashes and updates the Nix expressions. Oracle uses pnpm and auto-derives its hash via build mismatch.
|
||||||
|
|||||||
112
cmd/sync-skills/main.go
Normal file
112
cmd/sync-skills/main.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mapping struct {
|
||||||
|
Tool string
|
||||||
|
Up string
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(dir string, name string, args ...string) error {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
cmd.Dir = dir
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &out
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("%s %v: %v: %s", name, args, err, out.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
if _, err := io.Copy(out, in); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
repoRoot, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
workdir, err := os.MkdirTemp("", "clawdbot-skills-")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(workdir)
|
||||||
|
|
||||||
|
mappings := []Mapping{
|
||||||
|
{"summarize", "skills/summarize"},
|
||||||
|
{"gogcli", "skills/gog"},
|
||||||
|
{"camsnap", "skills/camsnap"},
|
||||||
|
{"sonoscli", "skills/sonoscli"},
|
||||||
|
{"bird", "skills/bird"},
|
||||||
|
{"peekaboo", "skills/peekaboo"},
|
||||||
|
{"sag", "skills/sag"},
|
||||||
|
{"imsg", "skills/imsg"},
|
||||||
|
{"oracle", "skills/oracle"},
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[sync-skills] cloning clawdbot main")
|
||||||
|
if err := run("", "git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", "https://github.com/clawdbot/clawdbot.git", workdir); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
paths := []string{}
|
||||||
|
for _, m := range mappings {
|
||||||
|
paths = append(paths, m.Up)
|
||||||
|
}
|
||||||
|
args := append([]string{"sparse-checkout", "set"}, paths...)
|
||||||
|
if err := run(workdir, "git", args...); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated := false
|
||||||
|
for _, m := range mappings {
|
||||||
|
src := filepath.Join(workdir, m.Up, "SKILL.md")
|
||||||
|
dest := filepath.Join(repoRoot, "tools", m.Tool, "skills", filepath.Base(m.Up), "SKILL.md")
|
||||||
|
if _, err := os.Stat(src); err != nil {
|
||||||
|
log.Printf("[sync-skills] missing %s", src)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
same := false
|
||||||
|
if b1, err1 := os.ReadFile(src); err1 == nil {
|
||||||
|
if b2, err2 := os.ReadFile(dest); err2 == nil && bytes.Equal(b1, b2) {
|
||||||
|
same = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !same {
|
||||||
|
if err := copyFile(src, dest); err != nil {
|
||||||
|
log.Fatalf("copy %s -> %s: %v", src, dest, err)
|
||||||
|
}
|
||||||
|
updated = true
|
||||||
|
log.Printf("[sync-skills] updated %s", m.Tool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updated {
|
||||||
|
log.Printf("[sync-skills] no changes")
|
||||||
|
}
|
||||||
|
}
|
||||||
147
cmd/update-tools/main.go
Normal file
147
cmd/update-tools/main.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/clawdbot/nix-stepiete-tools/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Name string
|
||||||
|
Repo string
|
||||||
|
AssetRegex *regexp.Regexp
|
||||||
|
NixFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTool(tool Tool) error {
|
||||||
|
log.Printf("[update-tools] %s", tool.Name)
|
||||||
|
rel, err := internal.LatestRelease(tool.Repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := strings.TrimPrefix(rel.TagName, "v")
|
||||||
|
var assetURL string
|
||||||
|
for _, a := range rel.Assets {
|
||||||
|
if tool.AssetRegex.MatchString(a.Name) {
|
||||||
|
assetURL = a.BrowserDownloadURL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if assetURL == "" {
|
||||||
|
return fmt.Errorf("no asset matched for %s", tool.Name)
|
||||||
|
}
|
||||||
|
hash, err := internal.PrefetchHash(assetURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`url = "[^"]+";`), fmt.Sprintf(`url = "%s";`, assetURL)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := internal.ReplaceOnce(tool.NixFile, regexp.MustCompile(`hash = "sha256-[^"]+";`), fmt.Sprintf(`hash = "%s";`, hash)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateOracle(repoRoot string) error {
|
||||||
|
log.Printf("[update-tools] oracle")
|
||||||
|
rel, err := internal.LatestRelease("steipete/oracle")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
version := strings.TrimPrefix(rel.TagName, "v")
|
||||||
|
var assetURL string
|
||||||
|
for _, a := range rel.Assets {
|
||||||
|
if matched, _ := regexp.MatchString(`oracle-[0-9.]+\.tgz`, a.Name); matched {
|
||||||
|
assetURL = a.BrowserDownloadURL
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if assetURL == "" {
|
||||||
|
return fmt.Errorf("no asset matched for oracle")
|
||||||
|
}
|
||||||
|
assetHash, err := internal.PrefetchHash(assetURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lockURL := fmt.Sprintf("https://github.com/steipete/oracle/archive/refs/tags/%s.tar.gz", rel.TagName)
|
||||||
|
lockHash, err := internal.PrefetchHash(lockURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oracleFile := filepath.Join(repoRoot, "nix", "pkgs", "oracle.nix")
|
||||||
|
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`version = "[^"]+";`), fmt.Sprintf(`version = "%s";`, version)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`url = "[^"]+";`), fmt.Sprintf(`url = "%s";`, assetURL)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := internal.ReplaceOnce(oracleFile, regexp.MustCompile(`hash = "sha256-[^"]+";`), fmt.Sprintf(`hash = "%s";`, assetHash)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
lockRe := regexp.MustCompile(`lockSrc = fetchFromGitHub \{[^}]*?hash = "sha256-[^"]+";`)
|
||||||
|
if err := internal.ReplaceOnceFunc(oracleFile, lockRe, func(s string) string {
|
||||||
|
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, lockHash))
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pnpmRe := regexp.MustCompile(`pnpmDeps.*?hash = "sha256-[^"]+";`)
|
||||||
|
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||||
|
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, `hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";`)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[update-tools] oracle: deriving pnpm hash")
|
||||||
|
logText, _ := internal.NixBuildOracle()
|
||||||
|
pnpmHash := internal.ExtractGotHash(logText)
|
||||||
|
if pnpmHash == "" {
|
||||||
|
return fmt.Errorf("oracle pnpm hash not found in build output")
|
||||||
|
}
|
||||||
|
if err := internal.ReplaceOnceFunc(oracleFile, pnpmRe, func(s string) string {
|
||||||
|
return regexp.MustCompile(`hash = "sha256-[^"]+";`).ReplaceAllString(s, fmt.Sprintf(`hash = "%s";`, pnpmHash))
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
repoRoot, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tools := []Tool{
|
||||||
|
{"summarize", "steipete/summarize", regexp.MustCompile(`summarize-macos-arm64-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "summarize.nix")},
|
||||||
|
{"gogcli", "steipete/gogcli", regexp.MustCompile(`gogcli_[0-9.]+_darwin_arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "gogcli.nix")},
|
||||||
|
{"camsnap", "steipete/camsnap", regexp.MustCompile(`camsnap-macos-arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "camsnap.nix")},
|
||||||
|
{"sonoscli", "steipete/sonoscli", regexp.MustCompile(`sonoscli-macos-arm64\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "sonoscli.nix")},
|
||||||
|
{"bird", "steipete/bird", regexp.MustCompile(`bird-macos-universal-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "bird.nix")},
|
||||||
|
{"peekaboo", "steipete/peekaboo", regexp.MustCompile(`peekaboo-macos-universal\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "peekaboo.nix")},
|
||||||
|
{"poltergeist", "steipete/poltergeist", regexp.MustCompile(`poltergeist-macos-universal-v[0-9.]+\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "poltergeist.nix")},
|
||||||
|
{"sag", "steipete/sag", regexp.MustCompile(`sag_[0-9.]+_darwin_universal\.tar\.gz`), filepath.Join(repoRoot, "nix", "pkgs", "sag.nix")},
|
||||||
|
{"imsg", "steipete/imsg", regexp.MustCompile(`imsg-macos\.zip`), filepath.Join(repoRoot, "nix", "pkgs", "imsg.nix")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range tools {
|
||||||
|
if err := updateTool(tool); err != nil {
|
||||||
|
log.Fatalf("update %s failed: %v", tool.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateOracle(repoRoot); err != nil {
|
||||||
|
log.Fatalf("update oracle failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
internal/fs.go
Normal file
33
internal/fs.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ReplaceOnce(path string, re *regexp.Regexp, replace string) error {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orig := string(data)
|
||||||
|
out := re.ReplaceAllString(orig, replace)
|
||||||
|
if out == orig {
|
||||||
|
return fmt.Errorf("pattern not found in %s", path)
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, []byte(out), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplaceOnceFunc(path string, re *regexp.Regexp, fn func(string) string) error {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
orig := string(data)
|
||||||
|
out := re.ReplaceAllStringFunc(orig, fn)
|
||||||
|
if out == orig {
|
||||||
|
return fmt.Errorf("pattern not found in %s", path)
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, []byte(out), 0644)
|
||||||
|
}
|
||||||
49
internal/github.go
Normal file
49
internal/github.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Release struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Assets []Asset `json:"assets"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Asset struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
BrowserDownloadURL string `json:"browser_download_url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LatestRelease(repo string) (*Release, error) {
|
||||||
|
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo)
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", "application/vnd.github+json")
|
||||||
|
if token := os.Getenv("GH_TOKEN"); token != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return nil, fmt.Errorf("github api %s: %s", resp.Status, string(body))
|
||||||
|
}
|
||||||
|
var rel Release
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rel.TagName == "" {
|
||||||
|
return nil, errors.New("missing tag_name in release")
|
||||||
|
}
|
||||||
|
return &rel, nil
|
||||||
|
}
|
||||||
49
internal/nix.go
Normal file
49
internal/nix.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrefetchResult struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrefetchHash(url string) (string, error) {
|
||||||
|
cmd := exec.Command("nix", "store", "prefetch-file", "--json", url)
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &out
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return "", fmt.Errorf("prefetch failed: %v: %s", err, out.String())
|
||||||
|
}
|
||||||
|
var res PrefetchResult
|
||||||
|
if err := json.Unmarshal(out.Bytes(), &res); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if res.Hash == "" {
|
||||||
|
return "", fmt.Errorf("empty hash for %s", url)
|
||||||
|
}
|
||||||
|
return res.Hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NixBuildOracle() (string, error) {
|
||||||
|
cmd := exec.Command("nix", "build", ".#oracle")
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd.Stdout = &out
|
||||||
|
cmd.Stderr = &out
|
||||||
|
err := cmd.Run()
|
||||||
|
return out.String(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExtractGotHash(log string) string {
|
||||||
|
for _, line := range strings.Split(log, "\n") {
|
||||||
|
if idx := strings.Index(line, "got: sha256-"); idx != -1 {
|
||||||
|
return strings.TrimSpace(line[idx+5:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
workdir="$(mktemp -d)"
|
|
||||||
trap 'rm -rf "${workdir}"' EXIT
|
|
||||||
|
|
||||||
# Mapping: tool -> upstream skill dir (relative to clawdbot repo)
|
|
||||||
declare -A SKILLS
|
|
||||||
SKILLS[summarize]="skills/summarize"
|
|
||||||
SKILLS[gogcli]="skills/gog"
|
|
||||||
SKILLS[camsnap]="skills/camsnap"
|
|
||||||
SKILLS[sonoscli]="skills/sonoscli"
|
|
||||||
SKILLS[bird]="skills/bird"
|
|
||||||
SKILLS[peekaboo]="skills/peekaboo"
|
|
||||||
SKILLS[sag]="skills/sag"
|
|
||||||
SKILLS[imsg]="skills/imsg"
|
|
||||||
SKILLS[oracle]="skills/oracle"
|
|
||||||
|
|
||||||
# Clone only the needed paths from latest main.
|
|
||||||
# Note: no pinning; this is explicitly "latest main".
|
|
||||||
git -c advice.detachedHead=false clone --depth 1 --filter=blob:none --sparse \
|
|
||||||
https://github.com/clawdbot/clawdbot.git "${workdir}/clawdbot" >/dev/null
|
|
||||||
|
|
||||||
pushd "${workdir}/clawdbot" >/dev/null
|
|
||||||
git sparse-checkout set "${SKILLS[@]}" >/dev/null
|
|
||||||
popd >/dev/null
|
|
||||||
|
|
||||||
updated=0
|
|
||||||
for tool in "${!SKILLS[@]}"; do
|
|
||||||
src_dir="${workdir}/clawdbot/${SKILLS[$tool]}"
|
|
||||||
dest_dir="${repo_root}/tools/${tool}/skills/$(basename "${SKILLS[$tool]}")"
|
|
||||||
|
|
||||||
if [[ ! -d "${src_dir}" ]]; then
|
|
||||||
echo "[sync-skills] missing upstream skill: ${SKILLS[$tool]} (skip)" >&2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p "${dest_dir}"
|
|
||||||
|
|
||||||
# Copy only if content changed (avoid noisy commits).
|
|
||||||
if ! cmp -s "${src_dir}/SKILL.md" "${dest_dir}/SKILL.md" 2>/dev/null; then
|
|
||||||
cp "${src_dir}/SKILL.md" "${dest_dir}/SKILL.md"
|
|
||||||
updated=1
|
|
||||||
echo "[sync-skills] updated ${tool}" >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If upstream has extra files in the skill dir, sync them too.
|
|
||||||
rsync -a --delete --exclude SKILL.md "${src_dir}/" "${dest_dir}/" >/dev/null
|
|
||||||
if [[ -n "$(git -C "${repo_root}" status --porcelain "${dest_dir}" 2>/dev/null)" ]]; then
|
|
||||||
updated=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $updated -eq 0 ]]; then
|
|
||||||
echo "[sync-skills] no changes" >&2
|
|
||||||
fi
|
|
||||||
@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
echo "[update-tools] jq is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v nix >/dev/null 2>&1; then
|
|
||||||
echo "[update-tools] nix is required" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
prefetch_hash() {
|
|
||||||
local url="$1"
|
|
||||||
nix store prefetch-file --json "$url" | jq -r .hash
|
|
||||||
}
|
|
||||||
|
|
||||||
latest_release() {
|
|
||||||
local repo="$1"
|
|
||||||
gh api "repos/${repo}/releases/latest"
|
|
||||||
}
|
|
||||||
|
|
||||||
update_nix_file() {
|
|
||||||
local file="$1"
|
|
||||||
local version="$2"
|
|
||||||
local url="$3"
|
|
||||||
local hash="$4"
|
|
||||||
|
|
||||||
VERSION="$version" URL="$url" HASH="$hash" FILE="$file" python3 - <<'PY'
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
path = Path(os.environ["FILE"])
|
|
||||||
version = os.environ["VERSION"]
|
|
||||||
url = os.environ["URL"]
|
|
||||||
hash_ = os.environ["HASH"]
|
|
||||||
|
|
||||||
text = path.read_text()
|
|
||||||
text, n1 = re.subn(r'version = "[^"]+";', f'version = "{version}";', text, count=1)
|
|
||||||
text, n2 = re.subn(r'url = "[^"]+";', f'url = "{url}";', text, count=1)
|
|
||||||
text, n3 = re.subn(r'hash = "sha256-[^"]+";', f'hash = "{hash_}";', text, count=1)
|
|
||||||
|
|
||||||
if n1 == 0 or n2 == 0 or n3 == 0:
|
|
||||||
raise SystemExit(f"update failed for {path}: version/url/hash not found")
|
|
||||||
|
|
||||||
path.write_text(text)
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
update_tool() {
|
|
||||||
local tool="$1"
|
|
||||||
local repo="$2"
|
|
||||||
local asset_regex="$3"
|
|
||||||
local nix_file="$4"
|
|
||||||
|
|
||||||
echo "[update-tools] ${tool}" >&2
|
|
||||||
local json
|
|
||||||
json=$(latest_release "$repo")
|
|
||||||
|
|
||||||
local tag
|
|
||||||
tag=$(echo "$json" | jq -r .tag_name)
|
|
||||||
local version
|
|
||||||
version="${tag#v}"
|
|
||||||
|
|
||||||
local asset
|
|
||||||
asset=$(echo "$json" | jq -r --arg re "$asset_regex" '.assets[] | select(.name|test($re)) | .browser_download_url' | head -1)
|
|
||||||
|
|
||||||
if [[ -z "$asset" ]]; then
|
|
||||||
echo "[update-tools] no asset matched for ${tool} (${asset_regex})" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local hash
|
|
||||||
hash=$(prefetch_hash "$asset")
|
|
||||||
|
|
||||||
update_nix_file "$nix_file" "$version" "$asset" "$hash"
|
|
||||||
}
|
|
||||||
|
|
||||||
update_oracle() {
|
|
||||||
local tool="oracle"
|
|
||||||
local repo="steipete/oracle"
|
|
||||||
local nix_file="$repo_root/nix/pkgs/oracle.nix"
|
|
||||||
|
|
||||||
echo "[update-tools] ${tool}" >&2
|
|
||||||
local json
|
|
||||||
json=$(latest_release "$repo")
|
|
||||||
|
|
||||||
local tag
|
|
||||||
tag=$(echo "$json" | jq -r .tag_name)
|
|
||||||
local version
|
|
||||||
version="${tag#v}"
|
|
||||||
|
|
||||||
local asset
|
|
||||||
asset=$(echo "$json" | jq -r '.assets[] | select(.name|test("oracle-[0-9.]+\\.tgz")) | .browser_download_url' | head -1)
|
|
||||||
|
|
||||||
if [[ -z "$asset" ]]; then
|
|
||||||
echo "[update-tools] no asset matched for oracle" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
local asset_hash
|
|
||||||
asset_hash=$(prefetch_hash "$asset")
|
|
||||||
|
|
||||||
local lock_url
|
|
||||||
lock_url="https://github.com/steipete/oracle/archive/refs/tags/${tag}.tar.gz"
|
|
||||||
local lock_hash
|
|
||||||
lock_hash=$(prefetch_hash "$lock_url")
|
|
||||||
|
|
||||||
VERSION="$version" URL="$asset" HASH="$asset_hash" LOCK_HASH="$lock_hash" FILE="$nix_file" python3 - <<'PY'
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
path = Path(os.environ["FILE"])
|
|
||||||
version = os.environ["VERSION"]
|
|
||||||
url = os.environ["URL"]
|
|
||||||
hash_ = os.environ["HASH"]
|
|
||||||
lock_hash = os.environ["LOCK_HASH"]
|
|
||||||
|
|
||||||
text = path.read_text()
|
|
||||||
text, n1 = re.subn(r'version = "[^"]+";', f'version = "{version}";', text, count=1)
|
|
||||||
text, n2 = re.subn(r'url = "[^"]+";', f'url = "{url}";', text, count=1)
|
|
||||||
text, n3 = re.subn(r'hash = "sha256-[^"]+";', f'hash = "{hash_}";', text, count=1)
|
|
||||||
text, n4 = re.subn(r'lockSrc = fetchFromGitHub \{[^}]*?hash = "sha256-[^"]+";',
|
|
||||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', f'hash = "{lock_hash}";', m.group(0)),
|
|
||||||
text, count=1, flags=re.S)
|
|
||||||
text, n5 = re.subn(r'pnpmDeps.*?hash = "sha256-[^"]+";',
|
|
||||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', 'hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";', m.group(0)),
|
|
||||||
text, count=1, flags=re.S)
|
|
||||||
|
|
||||||
if n1 == 0 or n2 == 0 or n3 == 0 or n4 == 0 or n5 == 0:
|
|
||||||
raise SystemExit(f"update failed for {path}: fields not found")
|
|
||||||
|
|
||||||
path.write_text(text)
|
|
||||||
PY
|
|
||||||
|
|
||||||
# Derive pnpmDeps hash from a build mismatch
|
|
||||||
set +e
|
|
||||||
build_log=$(nix build .#oracle 2>&1)
|
|
||||||
set -e
|
|
||||||
if echo "$build_log" | grep -q "got: sha256-"; then
|
|
||||||
pnpm_hash=$(echo "$build_log" | sed -n 's/.*got: \(sha256-[A-Za-z0-9+/=]*\).*/\1/p' | head -1)
|
|
||||||
if [[ -n "$pnpm_hash" ]]; then
|
|
||||||
PNPM_HASH="$pnpm_hash" FILE="$nix_file" python3 - <<'PY'
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
path = Path(os.environ["FILE"])
|
|
||||||
pnpm_hash = os.environ["PNPM_HASH"]
|
|
||||||
text = path.read_text()
|
|
||||||
text, n = re.subn(r'pnpmDeps.*?hash = "sha256-[^"]+";',
|
|
||||||
lambda m: re.sub(r'hash = "sha256-[^"]+";', f'hash = "{pnpm_hash}";', m.group(0)),
|
|
||||||
text, count=1, flags=re.S)
|
|
||||||
if n == 0:
|
|
||||||
raise SystemExit(f"update failed for {path}: pnpmDeps hash not found")
|
|
||||||
path.write_text(text)
|
|
||||||
PY
|
|
||||||
else
|
|
||||||
echo "[update-tools] failed to extract pnpmDeps hash" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[update-tools] no pnpmDeps hash mismatch found" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
update_tool summarize "steipete/summarize" "summarize-macos-arm64-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/summarize.nix"
|
|
||||||
update_tool gogcli "steipete/gogcli" "gogcli_[0-9.]+_darwin_arm64\\.tar\\.gz" "$repo_root/nix/pkgs/gogcli.nix"
|
|
||||||
update_tool camsnap "steipete/camsnap" "camsnap-macos-arm64\\.tar\\.gz" "$repo_root/nix/pkgs/camsnap.nix"
|
|
||||||
update_tool sonoscli "steipete/sonoscli" "sonoscli-macos-arm64\\.tar\\.gz" "$repo_root/nix/pkgs/sonoscli.nix"
|
|
||||||
update_tool bird "steipete/bird" "bird-macos-universal-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/bird.nix"
|
|
||||||
update_tool peekaboo "steipete/peekaboo" "peekaboo-macos-universal\\.tar\\.gz" "$repo_root/nix/pkgs/peekaboo.nix"
|
|
||||||
update_tool poltergeist "steipete/poltergeist" "poltergeist-macos-universal-v[0-9.]+\\.tar\\.gz" "$repo_root/nix/pkgs/poltergeist.nix"
|
|
||||||
update_tool sag "steipete/sag" "sag_[0-9.]+_darwin_universal\\.tar\\.gz" "$repo_root/nix/pkgs/sag.nix"
|
|
||||||
update_tool imsg "steipete/imsg" "imsg-macos\\.zip" "$repo_root/nix/pkgs/imsg.nix"
|
|
||||||
update_oracle
|
|
||||||
Loading…
Reference in New Issue
Block a user