Merge branch 'master' into feat/car

This commit is contained in:
blacktop
2025-09-22 11:26:37 -06:00
106 changed files with 5127 additions and 22305 deletions
+2 -2
View File
@@ -8,11 +8,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "stable"
- name: Cache go.mod
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@beta
+5 -5
View File
@@ -27,15 +27,15 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "stable"
check-latest: true
cache: true
- uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
- uses: github/codeql-action/autobuild@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
- uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3
- uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3
- uses: github/codeql-action/autobuild@192325c86100d080feab897ff886c34abd4c83a3 # v3
- uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: actions/dependency-review-action@v4
with:
allow-licenses: BSD-2-Clause, BSD-3-Clause, MIT, Apache-2.0, MPL-2.0
+2 -2
View File
@@ -17,14 +17,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Check Cache
run: |
cat hack/.watch_cache || echo "No cache found"
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.24"
- name: Run ipsw watch WebKit/WebKit (LOCKDOWN MODE)
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
+3 -3
View File
@@ -18,13 +18,13 @@ jobs:
run:
working-directory: www
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 8
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version: 20
cache: "pnpm"
@@ -36,7 +36,7 @@ jobs:
run: pnpm build --locale en
- name: Upload Build Artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: www/build
+13 -13
View File
@@ -3,17 +3,17 @@ on:
push:
branches:
- master
paths-ignore:
- "*.md"
- "www/**"
- ".github/workflows/apple-meta.yml"
- ".github/workflows/codeql.yml"
- ".github/workflows/discord.yml"
- ".github/workflows/docs.yml"
- ".github/workflows/webkit-meta.yml"
- ".github/workflows/winget.yml"
- ".goreleaser.yml"
- "config.example.yml"
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- "cmd/**"
- "pkg/**"
- "internal/**"
- "api/**"
- "Makefile"
- ".github/workflows/go.yml"
- ".github/workflows/release.yml"
pull_request:
branches:
- master
@@ -49,11 +49,11 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "stable"
+3 -3
View File
@@ -9,10 +9,10 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.22"
@@ -22,7 +22,7 @@ jobs:
GOPROXY=${{ secrets.GOPROXY }} go mod tidy
- name: Actions Status Discord
uses: sarisia/actions-status-discord@5ddd3b114a98457dd80a39b2f00b6a998cd69008 # v1.15.3
uses: sarisia/actions-status-discord@11a0bfe3b50977e38aa2bd4a4ebd296415e83c19 # v1.15.4
if: always()
with:
webhook: ${{ secrets.GOPROXY_DISCORD_WEBHOOK }}
+2 -2
View File
@@ -17,13 +17,13 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Fetch all tags
run: git fetch --force --tags
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "stable"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.'
+179 -38
View File
@@ -4,74 +4,215 @@ on:
schedule:
- cron: '0 19 * * *' # daily at 11:00 PST (19:00 UTC)
workflow_dispatch:
inputs:
force_update:
description: 'Force update even if no new IPSWs detected'
required: false
default: false
type: boolean
platforms:
description: 'Platforms to check (comma-separated: ios,macos)'
required: false
default: 'ios,macos'
type: string
jobs:
update-entitlements-db:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.24'
go-version: '1.25'
- name: Build ipsw CLI
run: |
go build -o ipsw ./cmd/ipsw
- name: Determine latest IPSW URL
id: get-ipsw
- name: Determine latest IPSW URLs
id: get-ipsws
run: |
# Get the latest IPSW URL
echo "CURRENT_IPSW_URL=$(./ipsw dl ipsw --device iPhone17,1 --latest --urls)" >> $GITHUB_ENV
# Determine which platforms to check
PLATFORMS="${{ github.event.inputs.platforms || 'ios,macos' }}"
echo "Checking platforms: $PLATFORMS"
# Function to get latest IPSW URL for a platform
get_ipsw_url() {
local device="$1"
local platform_name="$2"
# Try to get latest IPSW URL, handle errors gracefully
if [ "$platform_name" = "macOS" ]; then
# macOS doesn't use device IDs, just get latest macOS IPSW
url=$(./ipsw dl ipsw --macos --latest --urls 2>/dev/null | head -1 || echo "")
else
url=$(./ipsw dl ipsw --device "$device" --latest --urls 2>/dev/null | head -1 || echo "")
fi
if [ -n "$url" ] && [ "$url" != "null" ]; then
echo "$url"
else
echo ""
fi
}
# Initialize URLs
IOS_URL=""
MACOS_URL=""
# Check each requested platform
if echo "$PLATFORMS" | grep -q "ios"; then
IOS_URL=$(get_ipsw_url "iPhone17,1" "iOS")
fi
if echo "$PLATFORMS" | grep -q "macos"; then
MACOS_URL=$(get_ipsw_url "" "macOS")
fi
# Export URLs to environment
echo "IOS_URL=$IOS_URL" >> $GITHUB_ENV
echo "MACOS_URL=$MACOS_URL" >> $GITHUB_ENV
- name: Check for new IPSW
id: check-ipsw
- name: Check for new IPSWs
id: check-ipsws
run: |
LAST=$(jq -r '.latest_ipsw.url // ""' hack/.watch_cache)
echo "Last cached IPSW URL: $LAST"
echo "Current IPSW URL: $CURRENT_IPSW_URL"
if [ "$LAST" = "$CURRENT_IPSW_URL" ]; then
echo "No new IPSW found; skipping"
echo "should_update=false" >> $GITHUB_OUTPUT
# Get cached URLs
LAST_IOS=$(jq -r '.latest_ipsw.ios_url // ""' hack/.watch_cache)
LAST_MACOS=$(jq -r '.latest_ipsw.macos_url // ""' hack/.watch_cache)
echo "=== Cached URLs ==="
echo "iOS: $LAST_IOS"
echo "macOS: $LAST_MACOS"
echo "=== Current URLs ==="
echo "iOS: $IOS_URL"
echo "macOS: $MACOS_URL"
# Check for changes
SHOULD_UPDATE_IOS="false"
SHOULD_UPDATE_MACOS="false"
SHOULD_UPDATE_ANY="false"
if [ -n "$IOS_URL" ] && [ "$LAST_IOS" != "$IOS_URL" ]; then
echo "iOS IPSW changed: $LAST_IOS -> $IOS_URL"
SHOULD_UPDATE_IOS="true"
SHOULD_UPDATE_ANY="true"
fi
if [ -n "$MACOS_URL" ] && [ "$LAST_MACOS" != "$MACOS_URL" ]; then
echo "macOS IPSW changed: $LAST_MACOS -> $MACOS_URL"
SHOULD_UPDATE_MACOS="true"
SHOULD_UPDATE_ANY="true"
fi
# Force update if requested
if [ "${{ github.event.inputs.force_update }}" = "true" ]; then
echo "Force update requested"
SHOULD_UPDATE_ANY="true"
if [ -n "$IOS_URL" ]; then SHOULD_UPDATE_IOS="true"; fi
if [ -n "$MACOS_URL" ]; then SHOULD_UPDATE_MACOS="true"; fi
fi
# Export update flags
echo "should_update_ios=$SHOULD_UPDATE_IOS" >> $GITHUB_OUTPUT
echo "should_update_macos=$SHOULD_UPDATE_MACOS" >> $GITHUB_OUTPUT
echo "should_update_any=$SHOULD_UPDATE_ANY" >> $GITHUB_OUTPUT
if [ "$SHOULD_UPDATE_ANY" = "true" ]; then
echo "Will proceed with database updates"
else
echo "New IPSW found; proceeding with update"
echo "should_update=true" >> $GITHUB_OUTPUT
echo "No new IPSWs found; skipping updates"
fi
- name: Download IPSW
if: steps.check-ipsw.outputs.should_update == 'true'
- name: Download IPSWs
if: steps.check-ipsws.outputs.should_update_any == 'true'
run: |
echo "Downloading IPSW: $CURRENT_IPSW_URL"
curl -L "$CURRENT_IPSW_URL" -o latest.ipsw
echo "IPSW downloaded: $(ls -lh latest.ipsw)"
echo "Downloading new IPSWs..."
# Download iOS IPSW
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ] && [ -n "$IOS_URL" ]; then
echo "Downloading iOS IPSW: $IOS_URL"
curl -L "$IOS_URL" -o ios_latest.ipsw
echo "iOS IPSW downloaded: $(ls -lh ios_latest.ipsw)"
fi
# Download macOS IPSW
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ] && [ -n "$MACOS_URL" ]; then
echo "Downloading macOS IPSW: $MACOS_URL"
curl -L "$MACOS_URL" -o macos_latest.ipsw
echo "macOS IPSW downloaded: $(ls -lh macos_latest.ipsw)"
fi
- name: Update entitlements database
if: steps.check-ipsw.outputs.should_update == 'true'
if: steps.check-ipsws.outputs.should_update_any == 'true'
run: |
echo "Updating Supabase entitlements database..."
./ipsw ent \
--pg-host ${{ secrets.SUPABASE_HOST }} \
--pg-port 6543 \
--pg-user postgres \
--pg-password "${{ secrets.SUPABASE_PASSWORD }}" \
--pg-database postgres \
--pg-sslmode require \
--ipsw latest.ipsw
echo "Database update completed successfully"
echo "Updating Supabase entitlements database with replacement support..."
# Function to update database for a platform
update_platform() {
local platform="$1"
local ipsw_file="$2"
if [ -f "$ipsw_file" ]; then
echo "Processing $platform IPSW: $ipsw_file"
./ipsw ent --ipsw "$ipsw_file" --replace \
--pg-host "${{ secrets.SUPABASE_HOST }}" \
--pg-port 5432 \
--pg-user "${{ secrets.SUPABASE_USER }}" \
--pg-password "${{ secrets.SUPABASE_PASSWORD }}" \
--pg-database postgres \
--pg-sslmode require \
--pg-poolmode session
echo "$platform database update completed successfully"
else
echo "Skipping $platform (no IPSW file: $ipsw_file)"
fi
}
# Process each platform that needs updating
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ]; then
update_platform "iOS" "ios_latest.ipsw"
fi
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ]; then
update_platform "macOS" "macos_latest.ipsw"
fi
echo "All database updates completed successfully"
- name: Update cache and commit
if: steps.check-ipsw.outputs.should_update == 'true'
if: steps.check-ipsws.outputs.should_update_any == 'true'
run: |
# Update the cache file with the new IPSW URL
jq --arg url "$CURRENT_IPSW_URL" '.latest_ipsw = {"url": $url}' hack/.watch_cache > hack/.watch_cache.tmp
# Update the cache file with new IPSW URLs
echo "Updating cache with new IPSW URLs..."
# Create temporary cache with current URLs
jq --arg ios_url "$IOS_URL" \
--arg macos_url "$MACOS_URL" \
'.latest_ipsw = {
"ios_url": (if $ios_url != "" then $ios_url else .latest_ipsw.ios_url // "" end),
"macos_url": (if $macos_url != "" then $macos_url else .latest_ipsw.macos_url // "" end),
"url": (if $ios_url != "" then $ios_url else .latest_ipsw.url // "" end)
}' hack/.watch_cache > hack/.watch_cache.tmp
mv hack/.watch_cache.tmp hack/.watch_cache
# Configure git
git config --local user.name "github-actions[bot]"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
# Commit only the cache file (no more SQLite DB to commit)
# Create commit message with updated platforms
UPDATED_PLATFORMS=""
if [ "${{ steps.check-ipsws.outputs.should_update_ios }}" = "true" ]; then
UPDATED_PLATFORMS="${UPDATED_PLATFORMS}iOS "
fi
if [ "${{ steps.check-ipsws.outputs.should_update_macos }}" = "true" ]; then
UPDATED_PLATFORMS="${UPDATED_PLATFORMS}macOS "
fi
COMMIT_MSG="chore(ents): update entitlements DB for ${UPDATED_PLATFORMS}[skip ci]"
# Commit cache file changes
git add hack/.watch_cache
git commit -m "chore(ents): update entitlements DB to $CURRENT_IPSW_URL [skip ci]" || echo "No changes to commit"
git commit -m "$COMMIT_MSG" || echo "No changes to commit"
git push
+2 -2
View File
@@ -9,10 +9,10 @@ jobs:
update-all-fcs-keys:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: '1.24'
+2 -2
View File
@@ -8,11 +8,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "stable"
- name: Cache go.mod
+5
View File
@@ -13,6 +13,9 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Local cache
.gocache
# IDE
.idea
.vscode
@@ -25,6 +28,7 @@ __debug_*
CLAUDE*.md
.gemini
GEMINI*.md
AGENTS.md
# Go Work
*.work
@@ -75,6 +79,7 @@ internal/commands/ida/dscu/data/dscu.py
internal/download/data/proto
venv/
keybag.plist
OPC/
# Unicorn EMU
unicorn2/
+1 -1
View File
@@ -78,7 +78,7 @@ builds:
- arm64
tags:
- libusb
- unicorn
# - unicorn
- objc
- sandbox
- wallpaper
+1 -1
View File
@@ -1,7 +1,7 @@
####################################################
# GOLANG BUILDER
####################################################
FROM golang:1.24 AS builder
FROM golang:1.25 AS builder
ARG VERSION
ARG COMMIT
+1 -1
View File
@@ -1,7 +1,7 @@
####################################################
# GOLANG BUILDER
####################################################
FROM golang:1.24 AS builder
FROM golang:1.25 AS builder
ARG VERSION
+6 -1
View File
@@ -57,6 +57,11 @@ func getFsFiles(pemDB string) gin.HandlerFunc {
pemDbPath = filepath.Clean(pemDB)
}
}
mountPointParam, _ := c.GetQuery("mount_point")
if mountPointParam != "" {
mountPointParam = filepath.Clean(mountPointParam)
}
i, err := info.Parse(ipswPath)
if err != nil {
@@ -99,7 +104,7 @@ func getFsFiles(pemDB string) gin.HandlerFunc {
// mount filesystem DMG
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", dmgPath))
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, mountPointParam)
if err != nil {
if !errors.Is(err, utils.ErrMountResourceBusy) {
c.AbortWithStatusJSON(http.StatusInternalServerError, types.GenericError{Error: fmt.Sprintf("failed to mount DMG: %v", err)})
+12 -1
View File
@@ -50,6 +50,11 @@ func AddRoutes(rg *gin.RouterGroup, pemDB string) {
// description: path to AEA pem DB JSON file
// required: false
// type: string
// + name: mount_point
// in: query
// description: custom mount point path
// required: false
// type: string
// Responses:
// 500: genericError
// 200: mountReponse
@@ -69,12 +74,18 @@ func AddRoutes(rg *gin.RouterGroup, pemDB string) {
pemDbPath = filepath.Clean(pemDB)
}
}
mountPointParam, _ := c.GetQuery("mount_point")
if mountPointParam != "" {
mountPointParam = filepath.Clean(mountPointParam)
}
dmgType := c.Param("type")
if !slices.Contains([]string{"app", "sys", "fs"}, dmgType) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid dmg type: must be app, sys, or fs"})
return
}
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath, nil)
ctx, err := mount.DmgInIPSW(ipswPath, dmgType, pemDbPath, nil, mountPointParam)
if err != nil {
if errors.Unwrap(err) == info.ErrorCryptexNotFound {
c.AbortWithError(http.StatusNotFound, err)
+43 -4
View File
@@ -51,6 +51,7 @@ func init() {
entCmd.Flags().String("pg-password", "", "PostgreSQL password")
entCmd.Flags().String("pg-database", "", "PostgreSQL database name")
entCmd.Flags().String("pg-sslmode", "require", "PostgreSQL SSL mode (disable, require, verify-ca, verify-full)")
entCmd.Flags().String("pg-poolmode", "", "PostgreSQL pool mode (session, transaction, statement, or empty for no pooling)")
// Search flags
entCmd.Flags().StringP("key", "k", "", "Search for entitlement key pattern")
@@ -63,6 +64,11 @@ func init() {
entCmd.Flags().Bool("stats", false, "Show database statistics")
entCmd.Flags().Int("limit", 100, "Limit number of results")
// Replacement flags
entCmd.Flags().Bool("replace", false, "Replace older builds of the same iOS version with newer builds")
entCmd.Flags().String("replace-strategy", "auto", "Replacement strategy: auto, prompt, force")
entCmd.Flags().Bool("dry-run", false, "Show what would be replaced without making changes")
// Viper bindings
viper.BindPFlag("ent.ipsw", entCmd.Flags().Lookup("ipsw"))
viper.BindPFlag("ent.input", entCmd.Flags().Lookup("input"))
@@ -73,6 +79,7 @@ func init() {
viper.BindPFlag("ent.pg-password", entCmd.Flags().Lookup("pg-password"))
viper.BindPFlag("ent.pg-database", entCmd.Flags().Lookup("pg-database"))
viper.BindPFlag("ent.pg-sslmode", entCmd.Flags().Lookup("pg-sslmode"))
viper.BindPFlag("ent.pg-poolmode", entCmd.Flags().Lookup("pg-poolmode"))
viper.BindPFlag("ent.key", entCmd.Flags().Lookup("key"))
viper.BindPFlag("ent.value", entCmd.Flags().Lookup("value"))
viper.BindPFlag("ent.file", entCmd.Flags().Lookup("file"))
@@ -80,6 +87,9 @@ func init() {
viper.BindPFlag("ent.file-only", entCmd.Flags().Lookup("file-only"))
viper.BindPFlag("ent.stats", entCmd.Flags().Lookup("stats"))
viper.BindPFlag("ent.limit", entCmd.Flags().Lookup("limit"))
viper.BindPFlag("ent.replace", entCmd.Flags().Lookup("replace"))
viper.BindPFlag("ent.replace-strategy", entCmd.Flags().Lookup("replace-strategy"))
viper.BindPFlag("ent.dry-run", entCmd.Flags().Lookup("dry-run"))
}
@@ -113,7 +123,13 @@ var entCmd = &cobra.Command{
ipsw ent --sqlite entitlements.db --stats
# Search PostgreSQL database (Supabase)
ipsw ent --pg-host db.xyz.supabase.co --pg-user postgres --pg-password your-password --pg-database postgres --key sandbox`),
ipsw ent --pg-host db.xyz.supabase.co --pg-user postgres --pg-password your-password --pg-database postgres --key sandbox
# Replace older iOS builds with newer ones
ipsw ent --sqlite entitlements.db --ipsw iPhone16,1_26.0_22G87_Restore.ipsw --replace
# Preview what would be replaced
ipsw ent --sqlite entitlements.db --ipsw iPhone16,1_26.0_22G87_Restore.ipsw --replace --dry-run`),
Args: cobra.NoArgs,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -131,6 +147,7 @@ var entCmd = &cobra.Command{
pgPassword := viper.GetString("ent.pg-password")
pgDatabase := viper.GetString("ent.pg-database")
pgSSLMode := viper.GetString("ent.pg-sslmode")
pgPoolMode := viper.GetString("ent.pg-poolmode")
keyPattern := viper.GetString("ent.key")
valuePattern := viper.GetString("ent.value")
filePattern := viper.GetString("ent.file")
@@ -138,6 +155,9 @@ var entCmd = &cobra.Command{
fileOnly := viper.GetBool("ent.file-only")
showStats := viper.GetBool("ent.stats")
limit := viper.GetInt("ent.limit")
replace := viper.GetBool("ent.replace")
replaceStrategy := viper.GetString("ent.replace-strategy")
dryRun := viper.GetBool("ent.dry-run")
// Validate required flags
if sqliteDB == "" && pgHost == "" {
@@ -171,6 +191,19 @@ var entCmd = &cobra.Command{
return fmt.Errorf("--key, --value, --file, and --stats are mutually exclusive")
}
// Validate replacement flags
if replace && (keyPattern != "" || valuePattern != "" || filePattern != "" || showStats) {
return fmt.Errorf("--replace cannot be used with search operations")
}
if replaceStrategy != "auto" && replaceStrategy != "prompt" && replaceStrategy != "force" {
return fmt.Errorf("--replace-strategy must be one of: auto, prompt, force")
}
if dryRun && !replace {
return fmt.Errorf("--dry-run can only be used with --replace")
}
// Validate PostgreSQL flags if using PostgreSQL
if pgHost != "" {
if pgUser == "" || pgDatabase == "" {
@@ -190,7 +223,13 @@ var entCmd = &cobra.Command{
// Handle database creation
if len(ipsws) > 0 || len(inputs) > 0 {
if pgHost != "" {
return ent.CreatePostgreSQLDatabase(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, ipsws, inputs)
if replace {
return ent.CreatePostgreSQLDatabaseWithReplacement(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, ipsws, inputs, replaceStrategy, dryRun)
}
return ent.CreatePostgreSQLDatabase(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, ipsws, inputs)
}
if replace {
return ent.CreateSQLiteDatabaseWithReplacement(sqliteDB, ipsws, inputs, replaceStrategy, dryRun)
}
return ent.CreateSQLiteDatabase(sqliteDB, ipsws, inputs)
}
@@ -199,14 +238,14 @@ var entCmd = &cobra.Command{
if showStats {
if pgHost != "" {
return ent.ShowPostgreSQLStatistics(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode)
return ent.ShowPostgreSQLStatistics(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode)
}
return ent.ShowSQLiteStatistics(sqliteDB)
}
// Perform search
if pgHost != "" {
return ent.SearchPostgreSQLEntitlements(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
return ent.SearchPostgreSQLEntitlements(pgHost, pgPort, pgUser, pgPassword, pgDatabase, pgSSLMode, pgPoolMode, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
}
return ent.SearchSQLiteEntitlements(sqliteDB, keyPattern, valuePattern, filePattern, versionFilter, fileOnly, limit)
},
+1 -1
View File
@@ -70,7 +70,7 @@ var iaCmd = &cobra.Command{
dmgPath := filepath.Join(outDir, "SharedSupport.dmg")
log.Debugf("Mounting %s", dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return fmt.Errorf("failed to mount DMG: %v", err)
}
+1 -1
View File
@@ -118,7 +118,7 @@ var mdevsCmd = &cobra.Command{
}
// mount filesystem DMG
log.Debugf("Mounting %s", dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return fmt.Errorf("failed to mount DMG: %v", err)
}
+7 -1
View File
@@ -45,9 +45,11 @@ func init() {
mountCmd.Flags().StringP("key", "k", "", "DMG key")
mountCmd.Flags().Bool("lookup", false, "Lookup DMG keys on theapplewiki.com")
mountCmd.Flags().String("pem-db", "", "AEA pem DB JSON file")
mountCmd.Flags().StringP("mount-point", "m", "", "Custom mount point (default: /tmp/<dmg>.mount)")
viper.BindPFlag("mount.key", mountCmd.Flags().Lookup("key"))
viper.BindPFlag("mount.lookup", mountCmd.Flags().Lookup("lookup"))
viper.BindPFlag("mount.pem-db", mountCmd.Flags().Lookup("pem-db"))
viper.BindPFlag("mount.mount-point", mountCmd.Flags().Lookup("mount-point"))
}
// mountCmd represents the mount command
@@ -69,6 +71,9 @@ var mountCmd = &cobra.Command{
# Mount dyld shared cache (exc) DMG with AEA pem DB
$ ipsw mount exc iPhone.ipsw --pem-db /path/to/pem.json
# Mount to a custom mount point
$ ipsw mount fs iPhone.ipsw --mount-point /mnt/ios-filesystem
`),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
@@ -86,6 +91,7 @@ var mountCmd = &cobra.Command{
key := viper.GetString("mount.key")
lookupKeys := viper.GetBool("mount.lookup")
pemDB := viper.GetString("mount.pem-db")
mountPoint := viper.GetString("mount.mount-point")
// validate flags
if len(key) > 0 && lookupKeys {
return fmt.Errorf("cannot use --key AND --lookup flags together")
@@ -129,7 +135,7 @@ var mountCmd = &cobra.Command{
keys = key
}
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB, keys)
mctx, err := mount.DmgInIPSW(args[1], args[0], pemDB, keys, mountPoint)
if err != nil {
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
}
+31 -19
View File
@@ -55,6 +55,7 @@ func init() {
otaExtractCmd.Flags().StringP("range", "r", "", "Regex pattern control the payloadv2 file range to search")
otaExtractCmd.Flags().BoolP("confirm", "y", false, "Confirm searching for pattern in payloadv2 files")
otaExtractCmd.Flags().BoolP("decomp", "x", false, "Decompress pbzx files")
otaExtractCmd.Flags().BoolP("flat", "f", false, "Do NOT preserve directory structure when extracting")
otaExtractCmd.Flags().StringP("output", "o", "", "Output folder")
otaExtractCmd.MarkFlagDirname("output")
viper.BindPFlag("ota.extract.cryptex", otaExtractCmd.Flags().Lookup("cryptex"))
@@ -64,6 +65,7 @@ func init() {
viper.BindPFlag("ota.extract.range", otaExtractCmd.Flags().Lookup("range"))
viper.BindPFlag("ota.extract.confirm", otaExtractCmd.Flags().Lookup("confirm"))
viper.BindPFlag("ota.extract.decomp", otaExtractCmd.Flags().Lookup("decomp"))
viper.BindPFlag("ota.extract.flat", otaExtractCmd.Flags().Lookup("flat"))
viper.BindPFlag("ota.extract.output", otaExtractCmd.Flags().Lookup("output"))
}
@@ -79,11 +81,12 @@ var otaExtractCmd = &cobra.Command{
// flags
decomp := viper.GetBool("ota.extract.decomp")
cryptex := viper.GetString("ota.extract.cryptex")
flat := viper.GetBool("ota.extract.flat")
// validate flags
if len(args) > 1 && viper.IsSet("ota.extract.pattern") {
if len(args) > 1 && viper.GetString("ota.extract.pattern") != "" {
return fmt.Errorf("cannot use both FILENAME and flag for --pattern")
}
if viper.IsSet("ota.extract.cryptex") && !slices.Contains(validCryptexes, cryptex) {
if viper.GetString("ota.extract.cryptex") != "" && !slices.Contains(validCryptexes, cryptex) {
return fmt.Errorf("invalid --cryptex: '%s' (must be one of: %s)", cryptex, strings.Join(validCryptexes, ", "))
}
@@ -101,17 +104,17 @@ var otaExtractCmd = &cobra.Command{
return fmt.Errorf("failed to get OTA folder: %v", err)
}
if viper.IsSet("ota.extract.output") {
if viper.GetString("ota.extract.output") != "" {
output = filepath.Join(viper.GetString("ota.extract.output"), output)
if err := os.MkdirAll(output, 0o755); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
}
}
if viper.IsSet("ota.extract.cryptex") || viper.GetBool("ota.extract.dyld") || viper.GetBool("ota.extract.kernel") || viper.IsSet("ota.extract.pattern") {
if viper.GetString("ota.extract.cryptex") != "" || viper.GetBool("ota.extract.dyld") || viper.GetBool("ota.extract.kernel") || viper.GetString("ota.extract.pattern") != "" {
cwd, _ := os.Getwd()
/* CRYPTEX */
if viper.IsSet("ota.extract.cryptex") {
if viper.GetString("ota.extract.cryptex") != "" {
log.Infof("Extracting %s Cryptex", cryptex)
out, err := o.ExtractCryptex(cryptex, output)
if err != nil {
@@ -147,10 +150,10 @@ var otaExtractCmd = &cobra.Command{
if f.IsDir() {
continue
}
if re.MatchString(f.Path()) {
ff, err := o.Open(f.Path(), false)
if re.MatchString(f.Name()) {
ff, err := o.Open(f.Name(), false)
if err != nil {
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
}
data, err := io.ReadAll(ff)
if err != nil {
@@ -165,6 +168,9 @@ var otaExtractCmd = &cobra.Command{
return fmt.Errorf("failed to parse kernelcache compressed data: %v", err)
}
fname := filepath.Join(output, f.Name())
if flat {
fname = filepath.Join(output, filepath.Base(f.Name()))
}
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
}
@@ -180,7 +186,7 @@ var otaExtractCmd = &cobra.Command{
}
}
/* PATTERN */
if viper.IsSet("ota.extract.pattern") {
if viper.GetString("ota.extract.pattern") != "" {
re, err := regexp.Compile(viper.GetString("ota.extract.pattern"))
if err != nil {
return fmt.Errorf("failed to compile regex pattern '%s': %v", viper.GetString("ota.extract.pattern"), err)
@@ -190,12 +196,15 @@ var otaExtractCmd = &cobra.Command{
if f.IsDir() {
continue
}
if re.MatchString(f.Path()) {
ff, err := o.Open(f.Path(), decomp)
if re.MatchString(f.Name()) {
ff, err := o.Open(f.Name(), decomp)
if err != nil {
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
}
fname := filepath.Join(output, f.Name())
if flat {
fname = filepath.Join(output, filepath.Base(f.Name()))
}
fname := filepath.Join(output, f.Path())
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
}
@@ -215,8 +224,8 @@ var otaExtractCmd = &cobra.Command{
if f.IsDir() {
continue
}
if re.MatchString(f.Name()) {
utils.Indent(log.Warn, 2)(fmt.Sprintf("Found '%s' in post.bom (most likely in payloadv2 files)", f.Name()))
if re.MatchString(filepath.Base(f.Name())) {
utils.Indent(log.Warn, 2)(fmt.Sprintf("Found '%s' in post.bom (most likely in payloadv2 files)", filepath.Base(f.Name())))
bomFound = true
}
}
@@ -242,20 +251,23 @@ var otaExtractCmd = &cobra.Command{
}
/* ALL FILES */
if len(args) == 1 && !viper.IsSet("ota.extract.pattern") {
if len(args) == 1 && viper.GetString("ota.extract.pattern") == "" {
log.Info("Extracting All Files From OTA")
for _, f := range o.Files() {
if f.IsDir() {
continue
}
fname := filepath.Join(output, f.Path())
fname := filepath.Join(output, f.Name())
if flat {
fname = filepath.Join(output, filepath.Base(f.Name()))
}
if _, err := os.Stat(fname); err == nil {
log.Warnf("already exists: '%s' ", fname)
continue
}
ff, err := o.Open(f.Path(), decomp)
ff, err := o.Open(f.Name(), decomp)
if err != nil {
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Path(), err)
return fmt.Errorf("failed to open file '%s' in OTA: %v", f.Name(), err)
}
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create output directory: %v", err)
+1 -1
View File
@@ -89,7 +89,7 @@ var otaLsCmd = &cobra.Command{
fmt.Fprintf(w, "- [ OTA ASSETS FILES ] %s\n\n", strings.Repeat("-", 50))
for _, f := range ota.Files() {
if !f.IsDir() {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", colorMode(f.Mode()), colorModTime(f.ModTime().Format(time.RFC3339)), colorSize(humanize.Bytes(uint64(f.Size()))), colorName(f.Path()))
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", colorMode(f.Mode()), colorModTime(f.ModTime().Format(time.RFC3339)), colorSize(humanize.Bytes(uint64(f.Size()))), colorName(f.Name()))
}
}
w.Flush()
+1 -1
View File
@@ -126,7 +126,7 @@ var sbDiffCmd = &cobra.Command{
}
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting FS %s", dmgPath))
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return fmt.Errorf("failed to mount DMG: %v", err)
}
+1 -1
View File
@@ -59,7 +59,7 @@ var wpCmd = &cobra.Command{
pemDB := viper.GetString("wp.pem-db")
// output := viper.GetString("wp.output")
ctx, err := mount.DmgInIPSW(filepath.Clean(args[0]), "fs", pemDB, nil)
ctx, err := mount.DmgInIPSW(filepath.Clean(args[0]), "fs", pemDB, nil, "")
if err != nil {
return fmt.Errorf("failed to mount %s DMG: %v", args[0], err)
}
+39 -36
View File
@@ -15,11 +15,11 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/MakeNowJust/heredoc/v2 v2.0.1
github.com/PuerkitoBio/goquery v1.10.3
github.com/alecthomas/chroma/v2 v2.19.0
github.com/anthropics/anthropic-sdk-go v1.6.2
github.com/alecthomas/chroma/v2 v2.20.0
github.com/anthropics/anthropic-sdk-go v1.12.0
github.com/apex/log v1.9.0
github.com/aymanbagabas/go-udiff v0.3.1
github.com/blacktop/arm64-cgo v1.0.58
github.com/blacktop/arm64-cgo v1.0.59
github.com/blacktop/go-apfs v1.0.27
github.com/blacktop/go-dwarf v1.0.14
github.com/blacktop/go-macho v1.1.249
@@ -33,11 +33,11 @@ require (
github.com/caarlos0/ctrlc v1.2.0
github.com/caarlos0/env/v8 v8.0.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/cloudflare/circl v1.6.1
github.com/disintegration/imaging v1.6.2
github.com/docker/docker v28.3.3+incompatible
github.com/docker/docker v28.4.0+incompatible
github.com/dominikbraun/graph v0.23.0
github.com/dustin/go-humanize v1.0.1
github.com/fatih/color v1.18.0
@@ -45,13 +45,13 @@ require (
github.com/fsnotify/fsnotify v1.9.0
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa
github.com/gen2brain/beeep v0.11.1
github.com/gin-gonic/gin v1.10.1
github.com/gin-gonic/gin v1.11.0
github.com/glebarez/sqlite v1.11.0
github.com/go-git/go-git/v5 v5.16.2
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/gocolly/colly/v2 v2.2.0
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a
github.com/google/gousb v1.1.3
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.7.0
@@ -59,34 +59,34 @@ require (
github.com/invopop/jsonschema v0.13.0
github.com/mattn/go-mastodon v0.0.10
github.com/mitchellh/mapstructure v1.5.0
github.com/ollama/ollama v0.9.6
github.com/openai/openai-go v1.11.1
github.com/ollama/ollama v0.12.0
github.com/openai/openai-go v1.12.0
github.com/opencontainers/image-spec v1.1.1
github.com/pkg/errors v0.9.1
github.com/sergi/go-diff v1.4.0
github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7
github.com/spf13/cast v1.9.2
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/spf13/viper v1.20.1
github.com/spf13/cast v1.10.0
github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15
github.com/twmb/murmur3 v1.1.8
github.com/ulikunitz/xz v0.6.0-alpha.3
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c
github.com/vbauerster/mpb/v8 v8.10.2
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
golang.org/x/crypto v0.40.0
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792
golang.org/x/net v0.42.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.34.0
golang.org/x/term v0.33.0
google.golang.org/genai v1.17.0
google.golang.org/protobuf v1.36.6
golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20250911091902-df9299821621
golang.org/x/net v0.44.0
golang.org/x/oauth2 v0.31.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.36.0
golang.org/x/term v0.35.0
google.golang.org/genai v1.25.0
google.golang.org/protobuf v1.36.9
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.6.0
gorm.io/gorm v1.30.1
gorm.io/gorm v1.31.0
)
// replace github.com/blacktop/go-macho => ../go-macho
@@ -120,11 +120,11 @@ require (
github.com/caarlos0/go-version v0.2.1 // indirect
github.com/caarlos0/svu/v3 v3.2.3 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/mosaic v0.0.0-20250720010745-3615766e35a0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
@@ -154,9 +154,9 @@ require (
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.7.0 // indirect
@@ -205,10 +205,12 @@ require (
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.4.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
github.com/sergeymakinen/go-bmp v1.0.0 // indirect
@@ -216,8 +218,8 @@ require (
github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/soniakeys/quant v1.0.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cobra-cli v1.3.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect
@@ -240,12 +242,13 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.19.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/image v0.29.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/tools v0.35.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250721164621-a45f3dfb1074 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250721164621-a45f3dfb1074 // indirect
+83 -81
View File
@@ -87,10 +87,10 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@@ -106,8 +106,8 @@ github.com/antchfx/xmlquery v1.4.4/go.mod h1:AEPEEPYE9GnA2mj5Ur2L5Q5/2PycJ0N9Fus
github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.4 h1:1ixrW1VnXd4HurCj7qnqnR0jo14g8JMe20Fshg1Vgz4=
github.com/antchfx/xpath v1.3.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/anthropics/anthropic-sdk-go v1.6.2 h1:oORA212y0/zAxe7OPvdgIbflnn/x5PGk5uwjF60GqXM=
github.com/anthropics/anthropic-sdk-go v1.6.2/go.mod h1:3qSNQ5NrAmjC8A2ykuruSQttfqfdEYNZY5o8c0XSHB8=
github.com/anthropics/anthropic-sdk-go v1.12.0 h1:xPqlGnq7rWrTiHazIvCiumA0u7mGQnwDQtvA1M82h9U=
github.com/anthropics/anthropic-sdk-go v1.12.0/go.mod h1:WTz31rIUHUHqai2UslPpw5CwXrQP3geYBioRV4WOLvE=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
@@ -138,8 +138,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/blacktop/arm64-cgo v1.0.58 h1:ANTB5JUcLzFfsKxNsp0obn/lyvLMIBa1tKiNCAc0iMw=
github.com/blacktop/arm64-cgo v1.0.58/go.mod h1:3QC9E3mn9o8Xxf8fILONjGOC9Ss/OgutkYnmNnsu+X4=
github.com/blacktop/arm64-cgo v1.0.59 h1:svSvVGqvYvhs18qYXDHlduCEZKgWyB5ys/sUmjaleOo=
github.com/blacktop/arm64-cgo v1.0.59/go.mod h1:IrNNim89QQyCwVlUbFvz27DCEmy1xVWIblYIu7KjD20=
github.com/blacktop/go-apfs v1.0.27 h1:+noNImiLcTaxzDMpOsEwXOxLwgnSMZdcJH+YMNHZsbo=
github.com/blacktop/go-apfs v1.0.27/go.mod h1:2IvPE0B/wnekB5QoNXJrDzlTrPnfCQNxTfZaKu4zbYM=
github.com/blacktop/go-dwarf v1.0.14 h1:OjmzfSgg/qAKckn2tWFebcgKgJ7HOqCj7bS+CiE1lrY=
@@ -164,7 +164,6 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/caarlos0/ctrlc v1.2.0 h1:AtbThhmbeYx1WW3WXdWrd94EHKi+0NPRGS4/4pzrjwk=
@@ -184,14 +183,14 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
@@ -208,9 +207,8 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -250,8 +248,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -305,8 +303,8 @@ github.com/gen2brain/beeep v0.11.1/go.mod h1:jQVvuwnLuwOcdctHn/uyh8horSBNJ8uGb9C
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
@@ -350,6 +348,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gocolly/colly/v2 v2.2.0 h1:FQGxcqvTdFAvOpMRhk52o20Qsf6KtRU5HSf0bITS38I=
github.com/gocolly/colly/v2 v2.2.0/go.mod h1:YOQwv1ofoQOzJiELnkThDd6ObOfl6odUk2i6Czbx3Ws=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
@@ -358,10 +358,9 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0=
github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -398,8 +397,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a h1:l7A0loSszR5zHd/qK53ZIHMO8b3bBSmENnQ6eKnUT0A=
github.com/gomarkdown/markdown v0.0.0-20250810172220-2e2c11897d1a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -545,10 +544,8 @@ github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -647,14 +644,14 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nlnwa/whatwg-url v0.6.2 h1:jU61lU2ig4LANydbEJmA2nPrtCGiKdtgT0rmMd2VZ/Q=
github.com/nlnwa/whatwg-url v0.6.2/go.mod h1:x0FPXJzzOEieQtsBT/AKvbiBbQ46YlL6Xa7m02M1ECk=
github.com/ollama/ollama v0.9.6 h1:HZNJmB52pMt6zLkGkkheBuXBXM5478eiSAj7GR75AMc=
github.com/ollama/ollama v0.9.6/go.mod h1:zLwx3iZ3AI4Rc/egsrx3u1w4RU2MHQ/Ylxse48jvyt4=
github.com/ollama/ollama v0.12.0 h1:BRry7G2Skz7Mu+E6rz40tzBXNbLTEhheGT8umc1zvxo=
github.com/ollama/ollama v0.12.0/go.mod h1:9+1//yWPsDE2u+l1a5mpaKrYw4VdnSsRU3ioq5BvMms=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/openai/openai-go v1.11.1 h1:fTQ4Sr9eoRiWFAoHzXiZZpVi6KtLeoTMyGrcOCudjNU=
github.com/openai/openai-go v1.11.1/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/openai/openai-go v1.12.0 h1:NBQCnXzqOTv5wsgNC36PrFEiskGfO5wccfCWDo9S1U0=
github.com/openai/openai-go v1.12.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
@@ -688,6 +685,10 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -703,8 +704,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
@@ -733,30 +734,30 @@ github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/soniakeys/quant v1.0.0 h1:N1um9ktjbkZVcywBVAAYpZYSHxEfJGzshHCxx/DaI0Y=
github.com/soniakeys/quant v1.0.0/go.mod h1:HI1k023QuVbD4H8i9YdfZP2munIHU4QpjsImz6Y6zds=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/cobra-cli v1.3.0 h1:Y/qy0X40kDT+k7PCyBQrsjh/qOf9t/ZVScbn0OyZD84=
github.com/spf13/cobra-cli v1.3.0/go.mod h1:zq1KeHo/9SQm1tNdbJhwVDd9bVpokbQwuG6MR0TFCdE=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
@@ -773,8 +774,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15 h1:aFa2PvtQulG5uVQ8adH84JCwRZ2rjiZnRUU/mWxJRG8=
github.com/strukturag/libheif-go v0.0.0-20250130134905-55b3482bea15/go.mod h1:ZW0m/zWIvFqFSpPdiWRje8xdwyWJqt3Cnt6bVlDti8g=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -814,8 +815,8 @@ github.com/ulikunitz/lz v0.4.1 h1:H4rI9BePvqjmfmZgNZ6I8SEw+GkGNIFEUJnTxY9RHRU=
github.com/ulikunitz/lz v0.4.1/go.mod h1:kOcqmi51jCj0bKudgng90P1ho4uyQLKzkGaoPBhS+pE=
github.com/ulikunitz/xz v0.6.0-alpha.3 h1:7IGVhjpaTpgeNBUPQcbtooPhFXDgeGxzhFki5B8E2FY=
github.com/ulikunitz/xz v0.6.0-alpha.3/go.mod h1:ndJU5gy7q0Ffe1saQwmPKbMhIhJbwdu4+3t8eTxRF5U=
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420 h1:ITiFpOQk2X3lZJ79r9yTnV5z/INjDryB9f+56u5VxIw=
github.com/unicorn-engine/unicorn v0.0.0-20250410153552-f8c6db950420/go.mod h1:mcHBrigWSHlMZYol9QOFnK7sbltIt/OaKP5CQBZsC+4=
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c h1:qdw+wGIifeKs9EDiAVTQNcsLqKxdjlJCOAMFfP9RXfY=
github.com/unicorn-engine/unicorn v0.0.0-20250911131444-c24c9ebe773c/go.mod h1:mcHBrigWSHlMZYol9QOFnK7sbltIt/OaKP5CQBZsC+4=
github.com/vbauerster/mpb/v7 v7.5.3 h1:BkGfmb6nMrrBQDFECR/Q7RkKCw7ylMetCb4079CGs4w=
github.com/vbauerster/mpb/v7 v7.5.3/go.mod h1:i+h4QY6lmLvBNK2ah1fSreiw3ajskRlBp9AhY/PnuOE=
github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
@@ -866,12 +867,14 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU=
golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -890,8 +893,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -902,8 +905,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -938,8 +941,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -991,8 +994,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1010,8 +1013,8 @@ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo=
golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1029,8 +1032,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1113,8 +1116,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1125,8 +1128,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1144,8 +1147,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1209,8 +1212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1257,8 +1260,8 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genai v1.17.0 h1:lXYSnWShPYjxTouxRj0zF8RsNmSF+SKo7SQ7dM35NlI=
google.golang.org/genai v1.17.0/go.mod h1:QPj5NGJw+3wEOHg+PrsWwJKvG6UC84ex5FR7qAYsN/M=
google.golang.org/genai v1.25.0 h1:Cpyh2nmEoOS1eM3mT9XKuA/qWTEDoktfP2gsN3EduPE=
google.golang.org/genai v1.25.0/go.mod h1:OClfdf+r5aaD+sCd4aUSkPzJItmg2wD/WON9lQnRPaY=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1370,8 +1373,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -1399,8 +1402,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
@@ -1436,7 +1439,6 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+38 -26
View File
@@ -30,7 +30,15 @@
"webkitgtk-2.49.4",
"wpewebkit-2.49.4",
"webkitgtk-2.48.5",
"wpewebkit-2.48.5"
"wpewebkit-2.48.5",
"webkitgtk-2.49.90",
"wpewebkit-2.49.90",
"webkitgtk-2.48.6",
"wpewebkit-2.48.6",
"webkitgtk-2.50.0",
"wpewebkit-2.48.7",
"webkitgtk-2.48.7",
"wpewebkit-2.50.0"
]
},
"apple-oss-distributions/distribution-macOS": {
@@ -47,7 +55,8 @@
},
"tags": [
"macos-155",
"macos-154"
"macos-154",
"macos-156"
]
},
"fcs_keys": {
@@ -66,37 +75,32 @@
"fcs_keys_beta": {
"fcs_keys": {
"beta": {
"fingerprint": "22G6086_24G84_22O6785",
"ios_build": "22G6086",
"macos_build": "24G84",
"updated_at": "2025-08-05T19:09:17Z",
"visionos_build": "22O6785"
"fingerprint": "22H6020_24G222_22O785",
"ios_build": "22H6020",
"macos_build": "24G222",
"updated_at": "2025-09-20T19:05:17Z",
"visionos_build": "22O785"
}
}
},
"fcs_keys_rc": {
"fcs_keys": {
"beta": {
"updated_at": "0001-01-01T00:00:00Z"
},
"rc": {
"fingerprint": "21H221_24G84_22O785",
"ios_build": "21H221",
"macos_build": "24G84",
"updated_at": "2025-08-05T19:09:17Z",
"visionos_build": "22O785"
"fingerprint": "23A341_24G222_23M336",
"ios_build": "23A341",
"macos_build": "24G222",
"updated_at": "2025-09-15T19:06:03Z",
"visionos_build": "23M336"
},
"release": {
"updated_at": "0001-01-01T00:00:00Z"
}
}
},
"fcs_keys_stable": {
"fcs_keys": {
"release": {
"fingerprint": "21H221_24G84_22O785",
"ios_build": "21H221",
"macos_build": "24G84",
"updated_at": "2025-08-05T19:09:17Z",
"visionos_build": "22O785"
}
}
},
"latest_ipsw": {
"fcs_keys": {
"beta": {
"updated_at": "0001-01-01T00:00:00Z"
@@ -105,9 +109,17 @@
"updated_at": "0001-01-01T00:00:00Z"
},
"release": {
"updated_at": "0001-01-01T00:00:00Z"
"fingerprint": "23A341_24G222_23M336",
"ios_build": "23A341",
"macos_build": "24G222",
"updated_at": "2025-09-15T19:06:03Z",
"visionos_build": "23M336"
}
},
"url": "https://updates.cdn-apple.com/2025SpringFCS/fullrestores/082-45783/9A345601-C46E-4195-A367-4BCA31447055/iPhone17,1_18.5_22F76_Restore.ipsw"
}
},
"latest_ipsw": {
"ios_url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-40536/E62697A1-4565-4068-867C-A64017377220/iPhone17,1_26.0_23A341_Restore.ipsw",
"macos_url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-37622/CE01FAB2-7F26-48EE-AEE4-5E57A7F6D8BB/UniversalMac_26.0_25A354_Restore.ipsw",
"url": "https://updates.cdn-apple.com/2025FallFCS/fullrestores/093-40536/E62697A1-4565-4068-867C-A64017377220/iPhone17,1_26.0_23A341_Restore.ipsw"
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ func GetDDIInfo(c *DDIConfig) (info *DDIInfo, err error) {
if info.ManifestPath == "" {
// At this point we have a DMG path that needs to be mounted to find the BuildManifest.plist
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", c.DDIDmgPath))
mountPoint, alreadyMounted, err := utils.MountDMG(c.DDIDmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(c.DDIDmgPath, "")
if err != nil {
return nil, fmt.Errorf("failed to mount %s: %w", c.DDIDmgPath, err)
}
+1 -1
View File
@@ -765,7 +765,7 @@ func GetUserAgent(f *dyld.File, sysVer *plist.SystemVersion) (string, error) {
}
func OpenFromIPSW(ipswPath, pemDB string, driverKit, all bool) (*mount.Context, []*dyld.File, error) {
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB, nil)
ctx, err := mount.DmgInIPSW(ipswPath, "sys", pemDB, nil, "")
if err != nil {
return nil, nil, fmt.Errorf("failed to mount IPSW: %v", err)
}
+18 -8
View File
@@ -49,11 +49,15 @@ func (ds *DatabaseService) StoreEntitlements(ipswPath string, entDB map[string]s
return fmt.Errorf("failed to parse IPSW info: %v", err)
}
// Detect platform from IPSW
platform := DetectPlatformFromIPSW(ipswPath, ipswInfo)
ipswRecord = &model.Ipsw{
ID: generateIPSWID(ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
Name: filepath.Base(ipswPath),
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
ID: generateIPSWIDWithPlatform(platform, ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
Name: filepath.Base(ipswPath),
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
Platform: platform,
}
// Add devices
@@ -94,9 +98,10 @@ func (ds *DatabaseService) StoreEntitlements(ipswPath string, entDB map[string]s
} else {
// Create a minimal IPSW record for standalone usage
ipswRecord = &model.Ipsw{
ID: generateIPSWID("unknown", "unknown"),
Version: "unknown",
BuildID: "unknown",
ID: generateIPSWIDWithPlatform(model.PlatformIOS, "unknown", "unknown"),
Version: "unknown",
BuildID: "unknown",
Platform: model.PlatformIOS,
}
}
@@ -459,11 +464,16 @@ func (ds *DatabaseService) storeEntitlement(ipswRecord *model.Ipsw, filePath, en
return nil
}
// generateIPSWID creates a unique ID for an IPSW based on version and build
// generateIPSWID creates a unique ID for an IPSW based on version and build (legacy)
func generateIPSWID(version, build string) string {
return fmt.Sprintf("%s_%s", version, build)
}
// generateIPSWIDWithPlatform creates a unique ID for an IPSW based on platform, version and build
func generateIPSWIDWithPlatform(platform model.Platform, version, build string) string {
return fmt.Sprintf("%s_%s_%s", string(platform), version, build)
}
// createValueHash creates a hash for a value to ensure uniqueness
func createValueHash(valueType, value string) string {
hashInput := fmt.Sprintf("%s:%s", valueType, value)
+1 -1
View File
@@ -284,7 +284,7 @@ func scanEnts(ipswPath, dmgPath, dmgType, pemDbPath string) (map[string]string,
}
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgType, dmgPath))
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return nil, fmt.Errorf("failed to mount DMG: %v", err)
}
+134 -6
View File
@@ -9,6 +9,7 @@ import (
"github.com/apex/log"
"github.com/blacktop/ipsw/internal/db"
"github.com/blacktop/ipsw/pkg/info"
"github.com/dustin/go-humanize"
"github.com/fatih/color"
)
@@ -83,9 +84,9 @@ func CreateSQLiteDatabase(dbPath string, ipsws, inputs []string) error {
}
// CreatePostgreSQLDatabase creates or updates the PostgreSQL database
func CreatePostgreSQLDatabase(host, port, user, password, database, sslMode string, ipsws, inputs []string) error {
func CreatePostgreSQLDatabase(host, port, user, password, database, sslMode, poolMode string, ipsws, inputs []string) error {
// Create PostgreSQL database connection
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
if err != nil {
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
}
@@ -157,9 +158,9 @@ func SearchSQLiteEntitlements(dbPath, keyPattern, valuePattern, filePattern, ver
}
// SearchPostgreSQLEntitlements searches the PostgreSQL database for entitlements
func SearchPostgreSQLEntitlements(host, port, user, password, database, sslMode, keyPattern, valuePattern, filePattern, versionFilter string, fileOnly bool, limit int) error {
func SearchPostgreSQLEntitlements(host, port, user, password, database, sslMode, poolMode, keyPattern, valuePattern, filePattern, versionFilter string, fileOnly bool, limit int) error {
// Create database connection
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
if err != nil {
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
}
@@ -265,8 +266,8 @@ func ShowSQLiteStatistics(dbPath string) error {
}
// ShowPostgreSQLStatistics displays PostgreSQL database statistics
func ShowPostgreSQLStatistics(host, port, user, password, database, sslMode string) error {
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, 1000)
func ShowPostgreSQLStatistics(host, port, user, password, database, sslMode, poolMode string) error {
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
if err != nil {
return fmt.Errorf("failed to create PostgreSQL database: %v", err)
}
@@ -353,3 +354,130 @@ func contains(haystack, needle string) bool {
}
return strings.Contains(strings.ToLower(haystack), strings.ToLower(needle))
}
// CreateSQLiteDatabaseWithReplacement creates or updates SQLite database with replacement support
func CreateSQLiteDatabaseWithReplacement(dbPath string, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
// Ensure directory exists
if err := os.MkdirAll(filepath.Dir(dbPath), 0755); err != nil {
return fmt.Errorf("failed to create database directory: %v", err)
}
// Create SQLite database connection
dbConn, err := db.NewSqlite(dbPath, 1000)
if err != nil {
return fmt.Errorf("failed to create SQLite database: %v", err)
}
if err := dbConn.Connect(); err != nil {
return fmt.Errorf("failed to connect to SQLite database: %v", err)
}
defer dbConn.Close()
dbService := NewDatabaseService(dbConn)
strategy := NewSQLiteReplacementStrategy(dbService)
return processIPSWsWithReplacement(strategy, ipsws, inputs, replaceStrategy, dryRun)
}
// CreatePostgreSQLDatabaseWithReplacement creates or updates PostgreSQL database with replacement support
func CreatePostgreSQLDatabaseWithReplacement(host, port, user, password, database, sslMode, poolMode string, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
// Create PostgreSQL database connection
dbConn, err := db.NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode, 1000)
if err != nil {
return fmt.Errorf("failed to create PostgreSQL database connection: %v", err)
}
if err := dbConn.Connect(); err != nil {
return fmt.Errorf("failed to connect to PostgreSQL database: %v", err)
}
defer dbConn.Close()
dbService := NewDatabaseService(dbConn)
strategy := NewPostgreSQLReplacementStrategy(dbService)
return processIPSWsWithReplacement(strategy, ipsws, inputs, replaceStrategy, dryRun)
}
// processIPSWsWithReplacement processes IPSWs with replacement logic
func processIPSWsWithReplacement(strategy ReplacementStrategy, ipsws, inputs []string, replaceStrategy string, dryRun bool) error {
config := &ReplacementConfig{
Strategy: replaceStrategy,
DryRun: dryRun,
}
// Process IPSWs
for _, ipswPath := range ipsws {
log.WithField("ipsw", filepath.Base(ipswPath)).Info("Processing IPSW")
// Parse IPSW info for version comparison
ipswInfo, err := info.Parse(ipswPath)
if err != nil {
return fmt.Errorf("failed to parse IPSW info from %s: %v", ipswPath, err)
}
// Detect platform for replacement logic
platform := DetectPlatformFromIPSW(ipswPath, ipswInfo)
newIPSW := IPSWInfo{
ID: generateIPSWIDWithPlatform(platform, ipswInfo.Plists.BuildManifest.ProductVersion, ipswInfo.Plists.BuildManifest.ProductBuildVersion),
Name: filepath.Base(ipswPath),
Version: ipswInfo.Plists.BuildManifest.ProductVersion,
BuildID: ipswInfo.Plists.BuildManifest.ProductBuildVersion,
Platform: string(platform),
}
// Get existing IPSWs for comparison (platform-aware)
existingIPSWs, err := strategy.GetExistingIPSWs(string(platform), newIPSW.Version)
if err != nil {
return fmt.Errorf("failed to get existing IPSWs: %v", err)
}
// Create replacement plan
plan, err := strategy.CreateReplacementPlan(newIPSW, existingIPSWs, config)
if err != nil {
return fmt.Errorf("failed to create replacement plan: %v", err)
}
// Execute replacement if needed
if len(plan.ToReplace) > 0 || config.DryRun {
if err := strategy.ExecuteReplacement(plan); err != nil {
return fmt.Errorf("failed to execute replacement: %v", err)
}
}
// Extract and store entitlements (only if not dry run and plan was executed)
if !config.DryRun {
// Extract entitlements from IPSW
entDB, err := GetDatabase(&Config{
IPSW: ipswPath,
Database: "", // Don't create blob file
})
if err != nil {
return fmt.Errorf("failed to extract entitlements from %s: %v", ipswPath, err)
}
// Store entitlements using the strategy's database service
sqliteStrat, ok := strategy.(*SQLiteReplacementStrategy)
if ok {
if err := sqliteStrat.service.StoreEntitlements(ipswPath, entDB); err != nil {
return fmt.Errorf("failed to store entitlements: %v", err)
}
} else if pgStrat, ok := strategy.(*PostgreSQLReplacementStrategy); ok {
if err := pgStrat.service.StoreEntitlements(ipswPath, entDB); err != nil {
return fmt.Errorf("failed to store entitlements: %v", err)
}
}
}
}
// Process input folders if specified
for _, inputPath := range inputs {
log.WithField("input", inputPath).Info("Processing input folder")
// For inputs, we don't have version info so we use legacy replacement
// This would need to be implemented based on your input processing logic
return fmt.Errorf("input folder processing with replacement not yet implemented")
}
if !dryRun {
fmt.Printf("✓ Database creation/update completed successfully\n")
}
return nil
}
+191
View File
@@ -0,0 +1,191 @@
/*
Copyright © 2018-2025 blacktop
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ent
import (
"path/filepath"
"strings"
"github.com/blacktop/ipsw/internal/model"
"github.com/blacktop/ipsw/pkg/info"
)
// DetectPlatformFromIPSW detects the platform from IPSW filename and metadata
func DetectPlatformFromIPSW(ipswPath string, ipswInfo *info.Info) model.Platform {
filename := strings.ToLower(filepath.Base(ipswPath))
// First, try to detect from filename patterns
if platform := detectPlatformFromFilename(filename); platform != "" {
return platform
}
// If filename detection fails, try from device types in BuildManifest
if ipswInfo != nil && ipswInfo.Plists.BuildManifest != nil {
if platform := detectPlatformFromDevices(ipswInfo.Plists.BuildManifest.SupportedProductTypes); platform != "" {
return platform
}
}
// Default fallback to iOS
return model.PlatformIOS
}
// detectPlatformFromFilename detects platform from IPSW filename patterns
func detectPlatformFromFilename(filename string) model.Platform {
// macOS patterns (most specific first)
macOSPatterns := []string{
"universalmac", "macos", "mac_os", "macbook", "imac", "macmini", "macpro", "macstudio",
}
// visionOS patterns (check before general patterns to avoid conflicts)
visionOSPatterns := []string{
"apple_vision_pro", "vision_pro", "visionos", "vision_os", "applevision", "realityos", "realitydevice",
}
// watchOS patterns
watchOSPatterns := []string{
"watchos", "watch_os", "watch7", "watch6", "watch5", "applewatch",
}
// tvOS patterns
tvOSPatterns := []string{
"tvos", "tv_os", "appletv", "apple_tv",
}
// Check each platform pattern (order matters - most specific first)
for _, pattern := range visionOSPatterns {
if strings.Contains(filename, pattern) {
return model.PlatformVisionOS
}
}
for _, pattern := range macOSPatterns {
if strings.Contains(filename, pattern) {
return model.PlatformMacOS
}
}
for _, pattern := range watchOSPatterns {
if strings.Contains(filename, pattern) {
return model.PlatformWatchOS
}
}
for _, pattern := range tvOSPatterns {
if strings.Contains(filename, pattern) {
return model.PlatformTvOS
}
}
// If no patterns match, return empty (caller will try other methods)
return ""
}
// detectPlatformFromDevices detects platform from device identifiers
func detectPlatformFromDevices(devices []string) model.Platform {
if len(devices) == 0 {
return ""
}
// Count device types to determine platform
iosCount := 0
macOSCount := 0
watchOSCount := 0
tvOSCount := 0
visionOSCount := 0
for _, device := range devices {
deviceLower := strings.ToLower(device)
// macOS device patterns
if strings.Contains(deviceLower, "mac") ||
strings.Contains(deviceLower, "vmware") ||
strings.Contains(deviceLower, "parallels") {
macOSCount++
} else if strings.Contains(deviceLower, "watch") {
watchOSCount++
} else if strings.Contains(deviceLower, "appletv") ||
strings.Contains(deviceLower, "atv") {
tvOSCount++
} else if strings.Contains(deviceLower, "realitydevice") ||
strings.Contains(deviceLower, "vision") {
visionOSCount++
} else if strings.Contains(deviceLower, "iphone") ||
strings.Contains(deviceLower, "ipad") ||
strings.Contains(deviceLower, "ipod") ||
strings.Contains(deviceLower, "simulator") {
iosCount++
}
}
// Return platform with highest count
maxCount := iosCount
platform := model.PlatformIOS
if macOSCount > maxCount {
maxCount = macOSCount
platform = model.PlatformMacOS
}
if watchOSCount > maxCount {
maxCount = watchOSCount
platform = model.PlatformWatchOS
}
if tvOSCount > maxCount {
maxCount = tvOSCount
platform = model.PlatformTvOS
}
if visionOSCount > maxCount {
platform = model.PlatformVisionOS
}
return platform
}
// GetAllPlatforms returns all supported platforms
func GetAllPlatforms() []model.Platform {
return []model.Platform{
model.PlatformIOS,
model.PlatformMacOS,
model.PlatformWatchOS,
model.PlatformTvOS,
model.PlatformVisionOS,
}
}
// FormatPlatformForDisplay formats platform name for user display
func FormatPlatformForDisplay(platform model.Platform) string {
switch platform {
case model.PlatformIOS:
return "iOS"
case model.PlatformMacOS:
return "macOS"
case model.PlatformWatchOS:
return "watchOS"
case model.PlatformTvOS:
return "tvOS"
case model.PlatformVisionOS:
return "visionOS"
default:
return string(platform)
}
}
+555
View File
@@ -0,0 +1,555 @@
/*
Copyright © 2018-2025 blacktop
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package ent
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"gorm.io/gorm"
)
// VersionComparator represents an iOS version for comparison purposes
type VersionComparator struct {
MajorMinor string // e.g., "26.0", "18.5"
Build string // e.g., "22G87", "beta6", "22F76"
Raw string // Original version string
}
// ReplacementConfig holds configuration for replacement operations
type ReplacementConfig struct {
Strategy string // auto, prompt, force
DryRun bool
}
// ReplacementPlan represents a planned replacement operation
type ReplacementPlan struct {
ToReplace []IPSWInfo
NewIPSW IPSWInfo
Config *ReplacementConfig
ReasonMsg string
ConflictType string
}
// IPSWInfo represents basic information about an IPSW
type IPSWInfo struct {
Version string
BuildID string
ID string // Database ID
Name string // Filename
Platform string // Platform (iOS, macOS, etc.)
}
// ParseVersionComparator creates a VersionComparator from a version string
func ParseVersionComparator(version, build string) VersionComparator {
majorMinor := extractMajorMinorVersion(version)
return VersionComparator{
MajorMinor: majorMinor,
Build: build,
Raw: version,
}
}
// extractMajorMinorVersion extracts major.minor from version string
// Examples: "26.0.1" -> "26.0", "18.5" -> "18.5"
func extractMajorMinorVersion(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 {
return parts[0] + "." + parts[1]
}
return version
}
// ShouldReplace determines if an existing version should be replaced by a new version
func ShouldReplace(existing, new VersionComparator) bool {
// Only replace if same major.minor version
if existing.MajorMinor != new.MajorMinor {
return false
}
// If versions are identical, compare builds
if existing.Raw == new.Raw {
return CompareBuildVersions(existing.Build, new.Build) < 0
}
// Compare full versions (e.g., 26.0.1 vs 26.0.2)
return CompareVersionStrings(existing.Raw, new.Raw) < 0
}
// ShouldReplaceWithPlatform determines if an existing IPSW should be replaced by a new one
// considering both platform and version
func ShouldReplaceWithPlatform(existingIPSW, newIPSW IPSWInfo) bool {
// Only replace if same platform
if existingIPSW.Platform != newIPSW.Platform {
return false
}
// Use existing version comparison logic
existingComparator := ParseVersionComparator(existingIPSW.Version, existingIPSW.BuildID)
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
return ShouldReplace(existingComparator, newComparator)
}
// CompareBuildVersions compares two build version strings
// Returns: -1 if a < b, 0 if a == b, 1 if a > b
func CompareBuildVersions(a, b string) int {
// Handle beta versions
if strings.Contains(a, "beta") || strings.Contains(b, "beta") {
return compareBetaBuilds(a, b)
}
// Handle numeric build IDs (e.g., 22G87 vs 22G89)
if isNumericBuild(a) && isNumericBuild(b) {
return compareNumericBuilds(a, b)
}
// Fallback to string comparison
if a < b {
return -1
} else if a > b {
return 1
}
return 0
}
// CompareVersionStrings compares two version strings (e.g., "26.0.1" vs "26.0.2")
func CompareVersionStrings(a, b string) int {
aParts := parseVersionParts(a)
bParts := parseVersionParts(b)
maxLen := len(aParts)
if len(bParts) > maxLen {
maxLen = len(bParts)
}
for i := 0; i < maxLen; i++ {
aVal := 0
bVal := 0
if i < len(aParts) {
aVal = aParts[i]
}
if i < len(bParts) {
bVal = bParts[i]
}
if aVal < bVal {
return -1
} else if aVal > bVal {
return 1
}
}
return 0
}
// parseVersionParts parses a version string into numeric parts
func parseVersionParts(version string) []int {
parts := strings.Split(version, ".")
result := make([]int, len(parts))
for i, part := range parts {
if val, err := strconv.Atoi(part); err == nil {
result[i] = val
}
}
return result
}
// compareBetaBuilds compares beta build versions
func compareBetaBuilds(a, b string) int {
betaRegex := regexp.MustCompile(`beta(\d+)`)
aMatches := betaRegex.FindStringSubmatch(a)
bMatches := betaRegex.FindStringSubmatch(b)
// If both are beta versions
if len(aMatches) > 1 && len(bMatches) > 1 {
aNum, _ := strconv.Atoi(aMatches[1])
bNum, _ := strconv.Atoi(bMatches[1])
if aNum < bNum {
return -1
} else if aNum > bNum {
return 1
}
return 0
}
// Beta versions are considered "less than" release versions
if len(aMatches) > 1 && len(bMatches) == 0 {
return -1
}
if len(aMatches) == 0 && len(bMatches) > 1 {
return 1
}
// Fallback to string comparison
return strings.Compare(a, b)
}
// isNumericBuild checks if a build ID follows numeric pattern (e.g., 22G87)
func isNumericBuild(build string) bool {
match, _ := regexp.MatchString(`^\d+[A-Z]+\d+$`, build)
return match
}
// compareNumericBuilds compares numeric build IDs
func compareNumericBuilds(a, b string) int {
// Extract prefix numbers and suffix numbers
aPrefix, aSuffix := extractBuildParts(a)
bPrefix, bSuffix := extractBuildParts(b)
// Compare prefixes first
if aPrefix != bPrefix {
if aPrefix < bPrefix {
return -1
}
return 1
}
// Compare suffixes
if aSuffix < bSuffix {
return -1
} else if aSuffix > bSuffix {
return 1
}
return 0
}
// extractBuildParts extracts numeric parts from build ID (e.g., "22G87" -> 22, 87)
func extractBuildParts(build string) (int, int) {
regex := regexp.MustCompile(`^(\d+)[A-Z]+(\d+)$`)
matches := regex.FindStringSubmatch(build)
if len(matches) < 3 {
return 0, 0
}
prefix, _ := strconv.Atoi(matches[1])
suffix, _ := strconv.Atoi(matches[2])
return prefix, suffix
}
// CreateReplacementPlan analyzes what would be replaced and creates a plan
func CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) *ReplacementPlan {
var toReplace []IPSWInfo
var reasonMsg string
for _, existing := range existingIPSWs {
if ShouldReplaceWithPlatform(existing, newIPSW) {
toReplace = append(toReplace, existing)
}
}
if len(toReplace) > 0 {
if len(toReplace) == 1 {
reasonMsg = fmt.Sprintf("Replacing %s %s (build %s) with %s %s (build %s)",
toReplace[0].Platform, toReplace[0].Version, toReplace[0].BuildID,
newIPSW.Platform, newIPSW.Version, newIPSW.BuildID)
} else {
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
reasonMsg = fmt.Sprintf("Replacing %d older builds of %s %s with newer build %s",
len(toReplace), newIPSW.Platform, newComparator.MajorMinor, newIPSW.BuildID)
}
} else {
newComparator := ParseVersionComparator(newIPSW.Version, newIPSW.BuildID)
reasonMsg = fmt.Sprintf("No older builds found for %s %s - will add as new entry", newIPSW.Platform, newComparator.MajorMinor)
}
return &ReplacementPlan{
ToReplace: toReplace,
NewIPSW: newIPSW,
Config: config,
ReasonMsg: reasonMsg,
ConflictType: determineConflictType(toReplace, newIPSW),
}
}
// determineConflictType categorizes the type of replacement
func determineConflictType(toReplace []IPSWInfo, newIPSW IPSWInfo) string {
if len(toReplace) == 0 {
return "new"
}
if len(toReplace) == 1 {
existing := toReplace[0]
if existing.Version == newIPSW.Version {
return "build_update"
}
return "version_update"
}
return "multiple_replace"
}
// ReplacementStrategy interface for different replacement implementations
type ReplacementStrategy interface {
SupportsVersionBasedReplacement() bool
CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error)
ExecuteReplacement(plan *ReplacementPlan) error
GetExistingIPSWs(platform, version string) ([]IPSWInfo, error)
}
// SQLiteReplacementStrategy handles replacements for SQLite databases
type SQLiteReplacementStrategy struct {
service *DatabaseService
}
// NewSQLiteReplacementStrategy creates a new SQLite replacement strategy
func NewSQLiteReplacementStrategy(service *DatabaseService) *SQLiteReplacementStrategy {
return &SQLiteReplacementStrategy{service: service}
}
// SupportsVersionBasedReplacement returns true for SQLite strategy
func (s *SQLiteReplacementStrategy) SupportsVersionBasedReplacement() bool {
return true
}
// CreateReplacementPlan creates a replacement plan for SQLite
func (s *SQLiteReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
return CreateReplacementPlan(newIPSW, existingIPSWs, config), nil
}
// ExecuteReplacement executes the replacement plan atomically
func (s *SQLiteReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
if s.service.gormDB == nil {
return fmt.Errorf("GORM database required for replacement operations")
}
// If dry run, just log what would happen
if plan.Config.DryRun {
fmt.Printf("DRY RUN: %s\n", plan.ReasonMsg)
return nil
}
// Prompt user if strategy is prompt
if plan.Config.Strategy == "prompt" && len(plan.ToReplace) > 0 {
if !promptUserForReplacement(plan) {
fmt.Printf("Replacement cancelled by user\n")
return nil
}
}
// Execute atomic replacement transaction
return s.service.gormDB.Transaction(func(tx *gorm.DB) error {
// Step 1: Delete old IPSW data
for _, oldIPSW := range plan.ToReplace {
if err := s.deleteIPSWData(tx, oldIPSW.ID); err != nil {
return fmt.Errorf("failed to delete old IPSW %s: %w", oldIPSW.ID, err)
}
}
fmt.Printf("✓ %s\n", plan.ReasonMsg)
return nil
})
}
// GetExistingIPSWs retrieves existing IPSWs for version comparison
func (s *SQLiteReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
if s.service.gormDB == nil {
return nil, fmt.Errorf("GORM database required")
}
majorMinor := extractMajorMinorVersion(version)
// Query IPSWs with same platform and major.minor version
var ipsws []struct {
ID string
Name string
Version string
BuildID string
Platform string
}
err := s.service.gormDB.Table("ipsws").
Select("id, name, version, buildid as build_id, platform").
Where("platform = ? AND version LIKE ?", platform, majorMinor+"%").
Find(&ipsws).Error
if err != nil {
return nil, fmt.Errorf("failed to query existing IPSWs: %w", err)
}
result := make([]IPSWInfo, len(ipsws))
for i, ipsw := range ipsws {
result[i] = IPSWInfo{
ID: ipsw.ID,
Name: ipsw.Name,
Version: ipsw.Version,
BuildID: ipsw.BuildID,
Platform: ipsw.Platform,
}
}
return result, nil
}
// deleteIPSWData deletes all data associated with an IPSW
func (s *SQLiteReplacementStrategy) deleteIPSWData(tx *gorm.DB, ipswID string) error {
// Delete entitlements (cascading delete handles references)
if err := tx.Exec("DELETE FROM entitlements WHERE ipsw_id = ?", ipswID).Error; err != nil {
return fmt.Errorf("failed to delete entitlements: %w", err)
}
// Delete IPSW record
if err := tx.Exec("DELETE FROM ipsws WHERE id = ?", ipswID).Error; err != nil {
return fmt.Errorf("failed to delete IPSW record: %w", err)
}
// Clean up orphaned references
return s.cleanupOrphanedReferences(tx)
}
// cleanupOrphanedReferences removes unused paths, keys, and values
func (s *SQLiteReplacementStrategy) cleanupOrphanedReferences(tx *gorm.DB) error {
// Remove paths not referenced by any entitlements
if err := tx.Exec(`
DELETE FROM paths
WHERE id NOT IN (SELECT DISTINCT path_id FROM entitlements WHERE path_id IS NOT NULL)
`).Error; err != nil {
return fmt.Errorf("failed to cleanup orphaned paths: %w", err)
}
// Remove keys not referenced by any entitlements
if err := tx.Exec(`
DELETE FROM entitlement_keys
WHERE id NOT IN (SELECT DISTINCT key_id FROM entitlements WHERE key_id IS NOT NULL)
`).Error; err != nil {
return fmt.Errorf("failed to cleanup orphaned keys: %w", err)
}
// Remove values not referenced by any entitlements
if err := tx.Exec(`
DELETE FROM entitlement_values
WHERE id NOT IN (SELECT DISTINCT value_id FROM entitlements WHERE value_id IS NOT NULL)
`).Error; err != nil {
return fmt.Errorf("failed to cleanup orphaned values: %w", err)
}
return nil
}
// PostgreSQLReplacementStrategy handles replacements for PostgreSQL databases
type PostgreSQLReplacementStrategy struct {
service *DatabaseService
}
// NewPostgreSQLReplacementStrategy creates a new PostgreSQL replacement strategy
func NewPostgreSQLReplacementStrategy(service *DatabaseService) *PostgreSQLReplacementStrategy {
return &PostgreSQLReplacementStrategy{service: service}
}
// SupportsVersionBasedReplacement returns true for PostgreSQL strategy
func (p *PostgreSQLReplacementStrategy) SupportsVersionBasedReplacement() bool {
return true
}
// CreateReplacementPlan creates a replacement plan for PostgreSQL
func (p *PostgreSQLReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
return CreateReplacementPlan(newIPSW, existingIPSWs, config), nil
}
// ExecuteReplacement executes the replacement plan atomically
func (p *PostgreSQLReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
// Same implementation as SQLite for now since both use GORM
sqliteStrategy := &SQLiteReplacementStrategy{service: p.service}
return sqliteStrategy.ExecuteReplacement(plan)
}
// GetExistingIPSWs retrieves existing IPSWs for version comparison
func (p *PostgreSQLReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
// Same implementation as SQLite for now since both use GORM
sqliteStrategy := &SQLiteReplacementStrategy{service: p.service}
return sqliteStrategy.GetExistingIPSWs(platform, version)
}
// LegacyReplacementStrategy handles replacements for legacy database formats
type LegacyReplacementStrategy struct{}
// NewLegacyReplacementStrategy creates a new legacy replacement strategy
func NewLegacyReplacementStrategy() *LegacyReplacementStrategy {
return &LegacyReplacementStrategy{}
}
// SupportsVersionBasedReplacement returns false for legacy strategy (simple overwrite)
func (l *LegacyReplacementStrategy) SupportsVersionBasedReplacement() bool {
return false
}
// CreateReplacementPlan creates a simple replacement plan for legacy format
func (l *LegacyReplacementStrategy) CreateReplacementPlan(newIPSW IPSWInfo, existingIPSWs []IPSWInfo, config *ReplacementConfig) (*ReplacementPlan, error) {
return &ReplacementPlan{
ToReplace: existingIPSWs, // Replace all existing data
NewIPSW: newIPSW,
Config: config,
ReasonMsg: "Legacy format: replacing entire database with new data",
ConflictType: "legacy_overwrite",
}, nil
}
// ExecuteReplacement executes simple overwrite for legacy format
func (l *LegacyReplacementStrategy) ExecuteReplacement(plan *ReplacementPlan) error {
if plan.Config.DryRun {
fmt.Printf("DRY RUN: %s\n", plan.ReasonMsg)
return nil
}
fmt.Printf("✓ %s\n", plan.ReasonMsg)
// For legacy format, the actual overwrite is handled by the calling code
return nil
}
// GetExistingIPSWs returns empty list for legacy format
func (l *LegacyReplacementStrategy) GetExistingIPSWs(platform, version string) ([]IPSWInfo, error) {
return []IPSWInfo{}, nil
}
// promptUserForReplacement prompts the user to confirm replacement
func promptUserForReplacement(plan *ReplacementPlan) bool {
fmt.Printf("\n%s\n", plan.ReasonMsg)
if len(plan.ToReplace) > 0 {
fmt.Printf("This will delete the following existing data:\n")
for _, ipsw := range plan.ToReplace {
fmt.Printf(" - %s (iOS %s, build %s)\n", ipsw.Name, ipsw.Version, ipsw.BuildID)
}
}
fmt.Printf("\nDo you want to continue? (y/N): ")
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
response := strings.ToLower(strings.TrimSpace(scanner.Text()))
return response == "y" || response == "yes"
}
return false
}
+2 -2
View File
@@ -45,7 +45,7 @@ func (c Context) Unmount() error {
}
// DmgInIPSW will mount a DMG from an IPSW
func DmgInIPSW(path, typ, pemDbPath string, keys any) (*Context, error) {
func DmgInIPSW(path, typ, pemDbPath string, keys any, customMountPoint string) (*Context, error) {
var err error
ipswPath := filepath.Clean(path)
@@ -154,7 +154,7 @@ func DmgInIPSW(path, typ, pemDbPath string, keys any) (*Context, error) {
}
}
mp, am, err := utils.MountDMG(extractedDMG)
mp, am, err := utils.MountDMG(extractedDMG, customMountPoint)
if err != nil {
return nil, fmt.Errorf("failed to mount %s: %v", extractedDMG, err)
}
+7 -1
View File
@@ -20,6 +20,7 @@ type Postgres struct {
Password string
Database string
SSLMode string // SSL mode for connection (disable, require, verify-ca, verify-full)
PoolMode string // Connection pool mode (session, transaction)
// Config
BatchSize int
@@ -43,7 +44,7 @@ func NewPostgres(host, port, user, password, database string, batchSize int) (Da
}
// NewPostgresWithSSL creates a new Postgres database with SSL configuration.
func NewPostgresWithSSL(host, port, user, password, database, sslMode string, batchSize int) (Database, error) {
func NewPostgresWithSSL(host, port, user, password, database, sslMode, poolMode string, batchSize int) (Database, error) {
if host == "" || port == "" || user == "" || database == "" {
return nil, fmt.Errorf("'host', 'port', 'user' and 'database' are required")
}
@@ -57,6 +58,7 @@ func NewPostgresWithSSL(host, port, user, password, database, sslMode string, ba
Password: password,
Database: database,
SSLMode: sslMode,
PoolMode: poolMode,
BatchSize: batchSize,
}, nil
}
@@ -69,6 +71,10 @@ func (p *Postgres) Connect() (err error) {
p.Host, p.Port, p.User, p.Database, p.Password, p.SSLMode,
)
if p.PoolMode != "" {
dsn += fmt.Sprintf(" pool_mode=%s", p.PoolMode)
}
p.db, err = gorm.Open(postgres.New(postgres.Config{
DSN: dsn,
PreferSimpleProtocol: true, // Use simple protocol to avoid prepared statements
+1 -1
View File
@@ -365,7 +365,7 @@ func mountDMG(ctx *Context) (err error) {
}
}
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", ctx.SystemOsDmgPath))
ctx.MountPath, ctx.IsMounted, err = utils.MountDMG(ctx.SystemOsDmgPath)
ctx.MountPath, ctx.IsMounted, err = utils.MountDMG(ctx.SystemOsDmgPath, "")
if err != nil {
if !errors.Is(err, utils.ErrMountResourceBusy) {
return fmt.Errorf("failed to mount DMG: %v", err)
Binary file not shown.
File diff suppressed because one or more lines are too long
+16 -8
View File
@@ -49,6 +49,8 @@ const (
developerURL = "https://developer.apple.com"
downloadURLNew = "https://download.developer.apple.com"
downloadURL = "https://developer.apple.com/download/"
downloadOSesURL = "https://developer.apple.com/download/os/"
downloadMoreURL = "https://developer.apple.com/download/all/"
downloadAppsURL = "https://developer.apple.com/download/applications/"
downloadProfilesURL = "https://developer.apple.com/bug-reporting/profiles-and-logs/"
@@ -450,12 +452,14 @@ func (dp *DevPortal) Login(username, password string) error {
if err != nil {
return fmt.Errorf("failed to marshal keychain credentials: %v", err)
}
dp.Vault.Set(keyring.Item{
if err := dp.Vault.Set(keyring.Item{
Key: VaultName,
Data: dat,
Label: AppName,
Description: "application password",
})
}); err != nil {
return fmt.Errorf("failed to save credentials to vault: %v", err)
}
} else { // credentials found in vault
var auth AppleAccountAuth
if err := json.Unmarshal(creds.Data, &auth); err != nil {
@@ -601,11 +605,13 @@ func (dp *DevPortal) generateSRP(username, password string) (*http.Response, err
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(&auth{
if err := json.NewEncoder(buf).Encode(&auth{
AccountName: username,
A: base64.StdEncoding.EncodeToString(s.A.Bytes()),
Protocols: []string{"s2k", "s2k_fo"},
})
}); err != nil {
return nil, fmt.Errorf("failed to encode auth request: %v", err)
}
req, err := http.NewRequest("POST", initURL, buf)
if err != nil {
@@ -1121,12 +1127,14 @@ func (dp *DevPortal) storeSession() error {
// clear dev auth mem
auth = AppleAccountAuth{}
dp.Vault.Set(keyring.Item{
if err := dp.Vault.Set(keyring.Item{
Key: VaultName,
Data: data,
Label: AppName,
Description: "application password",
})
}); err != nil {
return fmt.Errorf("failed to save session to vault: %v", err)
}
return nil
}
@@ -1616,7 +1624,7 @@ func (dp *DevPortal) getDownloads() (*Downloads, error) {
func (dp *DevPortal) getDevDownloads() (map[string][]DevDownload, error) {
ipsws := make(map[string][]DevDownload)
req, err := http.NewRequest("GET", downloadURL, nil)
req, err := http.NewRequest("GET", downloadOSesURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create http GET request: %v", err)
}
@@ -1628,7 +1636,7 @@ func (dp *DevPortal) getDevDownloads() (map[string][]DevDownload, error) {
defer response.Body.Close()
if response.StatusCode != 200 {
return nil, fmt.Errorf("failed to GET %s: response received %s", downloadURL, response.Status)
return nil, fmt.Errorf("failed to GET %s: response received %s", downloadOSesURL, response.Status)
}
doc, err := goquery.NewDocumentFromReader(response.Body)
+13 -1
View File
@@ -14,12 +14,24 @@ var (
ErrSymExists = errors.New("symbol exists")
)
// Platform represents supported Apple platforms
type Platform string
const (
PlatformIOS Platform = "iOS"
PlatformMacOS Platform = "macOS"
PlatformWatchOS Platform = "watchOS"
PlatformTvOS Platform = "tvOS"
PlatformVisionOS Platform = "visionOS"
)
// Ipsw is the model for an Ipsw file.
type Ipsw struct {
ID string `gorm:"primaryKey" json:"id"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Version string `gorm:"index:idx_platform_version,priority:2" json:"version,omitempty"`
BuildID string `gorm:"column:buildid" json:"buildid,omitempty"`
Platform Platform `gorm:"type:varchar(20);index:idx_platform_version,priority:1" json:"platform,omitempty"`
Devices []*Device `gorm:"many2many:ipsw_devices;" json:"devices,omitempty"`
Kernels []*Kernelcache `gorm:"many2many:ipsw_kernels;" json:"kernels,omitempty"`
DSCs []*DyldSharedCache `gorm:"many2many:ipsw_dscs;" json:"dscs,omitempty"`
+1 -1
View File
@@ -54,7 +54,7 @@ func scanDmg(ipswPath, dmgPath, dmgType, pemDB string, handler func(string, stri
defer os.Remove(dmgPath)
}
utils.Indent(log.Debug, 2)(fmt.Sprintf("Mounting %s %s", dmgType, dmgPath))
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return fmt.Errorf("failed to mount DMG: %v", err)
}
+8 -3
View File
@@ -644,8 +644,13 @@ func IsAlreadyMounted(image, mountPoint string) (string, bool, error) {
return "", false, nil
}
func MountDMG(image string) (string, bool, error) {
mountPoint := fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
func MountDMG(image string, customMountPoint string) (string, bool, error) {
var mountPoint string
if customMountPoint != "" {
mountPoint = customMountPoint
} else {
mountPoint = fmt.Sprintf("/tmp/%s.mount", filepath.Base(image))
}
if runtime.GOOS == "darwin" {
// check if already mounted
@@ -794,7 +799,7 @@ func ExtractFromDMG(ipswPath, dmgPath, destPath, pemDB string, pattern *regexp.R
}
Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
mountPoint, alreadyMounted, err := MountDMG(dmgPath)
mountPoint, alreadyMounted, err := MountDMG(dmgPath, "")
if err != nil {
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
}
Binary file not shown.
File diff suppressed because one or more lines are too long
+16 -4
View File
@@ -717,9 +717,15 @@ func Parse(ipswPath string, keys ...string) (map[string]*DeviceTree, error) {
for _, f := range zr.File {
if regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(f.Name) {
dtData := make([]byte, f.UncompressedSize64)
rc, _ := f.Open()
io.ReadFull(rc, dtData)
rc, err := f.Open()
if err != nil {
return nil, fmt.Errorf("failed to open %s: %v", f.Name, err)
}
_, err = io.ReadFull(rc, dtData)
rc.Close()
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", f.Name, err)
}
if len(keys) > 0 {
ivkey, err := hex.DecodeString(keys[0])
if err != nil {
@@ -777,9 +783,15 @@ func ParseZipFiles(files []*zip.File, keys ...string) (dt map[string]*DeviceTree
for _, f := range files {
if regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(f.Name) {
dtData := make([]byte, f.UncompressedSize64)
rc, _ := f.Open()
io.ReadFull(rc, dtData)
rc, err := f.Open()
if err != nil {
return nil, fmt.Errorf("failed to open %s: %v", f.Name, err)
}
_, err = io.ReadFull(rc, dtData)
rc.Close()
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", f.Name, err)
}
if len(keys) > 0 {
ivkey, err := hex.DecodeString(keys[0])
if err != nil {
+1 -1
View File
@@ -88,7 +88,7 @@ func ExtractFromDMG(i *info.Info, dmgPath, destPath, pemDB string, arches []stri
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting DMG %s", dmgPath))
var alreadyMounted bool
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath)
mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath, "")
if err != nil {
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
}
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+27 -27
View File
@@ -157,18 +157,18 @@ func (a *AA) Info() (*info.Info, error) {
var pfiles []fs.File
for _, file := range a.Files() {
switch {
case regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(file.Path()):
case regexp.MustCompile(`.*DeviceTree.*im4p$`).MatchString(file.Name()):
fallthrough
case regexp.MustCompile(`^Info.plist$`).MatchString(file.Path()):
case regexp.MustCompile(`^Info.plist$`).MatchString(file.Name()):
fallthrough
case regexp.MustCompile(`^AssetData/Info.plist$`).MatchString(file.Path()):
case regexp.MustCompile(`^AssetData/Info.plist$`).MatchString(file.Name()):
fallthrough
case regexp.MustCompile(`Restore.plist$`).MatchString(file.Path()):
case regexp.MustCompile(`Restore.plist$`).MatchString(file.Name()):
fallthrough
case regexp.MustCompile(`BuildManifest.plist$`).MatchString(file.Path()):
case regexp.MustCompile(`BuildManifest.plist$`).MatchString(file.Name()):
fallthrough
case regexp.MustCompile(`SystemVersion.plist$`).MatchString(file.Path()):
f, err := a.Open(file.Path(), true)
case regexp.MustCompile(`SystemVersion.plist$`).MatchString(file.Name()):
f, err := a.Open(file.Name(), true)
if err != nil {
return nil, err
}
@@ -360,8 +360,8 @@ func (r *Reader) initPayloadMap() (perr error) {
if file.isDir {
continue
}
if pre.MatchString(file.Name()) {
f, err := r.Open(file.Path(), false)
if pre.MatchString(file.Base()) {
f, err := r.Open(file.Name(), false)
if err != nil {
perr = err
return
@@ -436,9 +436,9 @@ func (r *Reader) GetPayloadFiles(pattern, payloadRange, output string) error {
if file.isDir {
continue
}
if pre.MatchString(file.Name()) {
if pre.MatchString(file.Base()) {
eg.Go(func() error {
f, err := r.Open(file.Path(), false)
f, err := r.Open(file.Name(), false)
if err != nil {
return err
}
@@ -460,7 +460,7 @@ func (r *Reader) GetPayloadFiles(pattern, payloadRange, output string) error {
if err := os.MkdirAll(filepath.Dir(fname), 0o750); err != nil {
return fmt.Errorf("failed to create dir %s: %v", filepath.Dir(fname), err)
}
utils.Indent(log.Info, 2)(fmt.Sprintf("Extracting from '%s' -> %s\t%s", file.Name(), humanize.Bytes(uint64(f.Size())), fname))
utils.Indent(log.Info, 2)(fmt.Sprintf("Extracting from '%s' -> %s\t%s", file.Base(), humanize.Bytes(uint64(f.Size())), fname))
if err := os.Rename(path, fname); err != nil {
return fmt.Errorf("failed to mv file %s to %s: %v", strings.TrimPrefix(path, tmpdir), fname, err)
}
@@ -488,9 +488,9 @@ func (r *Reader) PayloadFiles(pattern string, json bool) error {
if file.isDir {
continue
}
if pre.MatchString(file.Name()) {
if pre.MatchString(file.Base()) {
eg.Go(func() error {
f, err := r.Open(file.Path(), false)
f, err := r.Open(file.Name(), false)
if err != nil {
return err
}
@@ -574,19 +574,19 @@ func (r *Reader) ExtractCryptex(cryptex, output string) (string, error) {
defer os.RemoveAll(tmpdir)
for _, file := range r.Files() {
if re.MatchString(file.Name()) {
cryptexFile, err := r.Open(file.Path(), false)
if re.MatchString(file.Base()) {
cryptexFile, err := r.Open(file.Name(), false)
if err != nil {
return "", fmt.Errorf("failed to open cryptex file: %v", err)
}
defer cryptexFile.Close()
// create a temp file to hold the OTA cryptex
cf, err := os.Create(filepath.Join(tmpdir, file.Name()))
cf, err := os.Create(filepath.Join(tmpdir, file.Base()))
if err != nil {
return "", fmt.Errorf("failed to create file: %v", err)
}
// create a temp file to hold the PATCHED OTA cryptex DMG
dcf, err := os.Create(filepath.Join(output, file.Name()+".dmg"))
dcf, err := os.Create(filepath.Join(output, file.Base()+".dmg"))
if err != nil {
return "", fmt.Errorf("failed to create file: %v", err)
}
@@ -597,7 +597,7 @@ func (r *Reader) ExtractCryptex(cryptex, output string) (string, error) {
cf.Close()
// patch the cryptex
if err := ridiff.RawImagePatch("", cf.Name(), dcf.Name(), 0); err != nil {
return "", fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Path()), err)
return "", fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Name()), err)
}
return dcf.Name(), nil
}
@@ -623,19 +623,19 @@ func (r *Reader) ExtractFromCryptexes(pattern, output string) ([]string, error)
for _, cryptex := range []string{"cryptex-system-(arm64e?|x86_64h?)$"} {
re := regexp.MustCompile(cryptex)
for _, file := range r.Files() {
if re.MatchString(file.Name()) {
cryptexFile, err := r.Open(file.Path(), false)
if re.MatchString(file.Base()) {
cryptexFile, err := r.Open(file.Name(), false)
if err != nil {
return nil, fmt.Errorf("failed to open cryptex file: %v", err)
}
defer cryptexFile.Close()
// create a temp file to hold the OTA cryptex
cf, err := os.Create(filepath.Join(tmpdir, file.Name()))
cf, err := os.Create(filepath.Join(tmpdir, file.Base()))
if err != nil {
return nil, fmt.Errorf("failed to create file: %v", err)
}
// create a temp file to hold the PATCHED OTA cryptex DMG
dcf, err := os.Create(filepath.Join(tmpdir, file.Name()+".dmg"))
dcf, err := os.Create(filepath.Join(tmpdir, file.Base()+".dmg"))
if err != nil {
return nil, fmt.Errorf("failed to create file: %v", err)
}
@@ -645,12 +645,12 @@ func (r *Reader) ExtractFromCryptexes(pattern, output string) ([]string, error)
cf.Close()
// patch the cryptex
if err := ridiff.RawImagePatch("", cf.Name(), dcf.Name(), 0); err != nil {
return nil, fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Path()), err)
return nil, fmt.Errorf("failed to patch %s: %v", filepath.Base(file.Name()), err)
}
dcf.Close()
// mount the patched cryptex
utils.Indent(log.Info, 4)(fmt.Sprintf("Mounting DMG %s", dcf.Name()))
mountPoint, alreadyMounted, err := utils.MountDMG(dcf.Name())
mountPoint, alreadyMounted, err := utils.MountDMG(dcf.Name(), "")
if err != nil {
return nil, fmt.Errorf("failed to IPSW FS dmg: %v", err)
}
@@ -792,8 +792,8 @@ func (r *Reader) payloadLookuo(name string) string {
return ""
}
func (f *File) Name() string { _, elem, _ := split(f.name); return elem }
func (f *File) Path() string { return f.name }
func (f *File) Base() string { _, elem, _ := split(f.name); return elem }
func (f *File) Name() string { return f.name }
func (f *File) Size() int64 {
if f.zfile != nil {
return int64(f.zfile.UncompressedSize64)
+15 -7
View File
@@ -16,6 +16,7 @@ import (
"time"
"github.com/apex/log"
util "github.com/blacktop/ipsw/internal/magic"
"github.com/blacktop/ipsw/pkg/bom"
"github.com/blacktop/ipsw/pkg/ota/pbzx"
"github.com/dustin/go-humanize"
@@ -707,13 +708,20 @@ func (y *YAA) Parse(r io.ReadSeeker) error {
if _, err := io.ReadFull(r, data); err != nil {
return fmt.Errorf("YAA.Parse: failed to read aa pbzx patch data: %w", err)
}
var pbuf bytes.Buffer
if err := pbzx.Extract(context.Background(), bytes.NewReader(data), &pbuf, runtime.NumCPU()); err != nil {
return fmt.Errorf("YAA.Parse: failed to extract aa pbzx patch data: %w", err)
}
if err := y.Parse(bytes.NewReader(pbuf.Bytes())); err != nil {
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
if isPBZX, err := util.IsPBZXData(bytes.NewReader(data)); err != nil {
return fmt.Errorf("YAA.Parse: failed to check if data is pbzx: %w", err)
} else if isPBZX {
var pbuf bytes.Buffer
if err := pbzx.Extract(context.Background(), bytes.NewReader(data), &pbuf, runtime.NumCPU()); err != nil {
return fmt.Errorf("YAA.Parse: failed to extract aa pbzx patch data: %w", err)
}
if err := y.Parse(bytes.NewReader(pbuf.Bytes())); err != nil {
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
}
} else {
if err := y.Parse(bytes.NewReader(data)); err != nil {
return fmt.Errorf("YAA.Parse: failed to parse aa patch: %w", err)
}
}
default:
log.Warnf("YAA.Parse: unsupported YOP operation: %s (let author know)", ent.Yop)
BIN
View File
Binary file not shown.
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/css/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "src/components",
"utils": "src/lib/utils"
}
}
@@ -39,6 +39,6 @@ ipsw appstore profile [flags]
* [ipsw appstore profile create](/docs/cli/ipsw/appstore/profile/create) - Create a new provisioning profile
* [ipsw appstore profile info](/docs/cli/ipsw/appstore/profile/info) - Dump provisioning profile information
* [ipsw appstore profile ls](/docs/cli/ipsw/appstore/profile/ls) - List provisioning profiles and download their data
* [ipsw appstore profile renew](/docs/cli/ipsw/appstore/profile/renew) - Renew and expired or invalide provisioning profile
* [ipsw appstore profile renew](/docs/cli/ipsw/appstore/profile/renew) - Renew an expired or invalid provisioning profile
* [ipsw appstore profile rm](/docs/cli/ipsw/appstore/profile/rm) - Delete a provisioning profile that is used for app development or distribution
+2 -2
View File
@@ -4,11 +4,11 @@ title: renew
hide_title: true
hide_table_of_contents: true
sidebar_label: renew
description: Renew and expired or invalide provisioning profile
description: Renew an expired or invalid provisioning profile
---
## ipsw appstore profile renew
Renew and expired or invalide provisioning profile
Renew an expired or invalid provisioning profile
```
ipsw appstore profile renew <NAME> [flags]
-3
View File
@@ -23,8 +23,6 @@ ipsw diff <IPSW> <IPSW> [flags]
ipsw diff <old.ipsw> <new.ipsw> --output <output/folder> --markdown
--kdk /Library/Developer/KDKs/KDK_15.0_24A5264n.kdk/System/Library/Kernels/kernel.release.t6031
--kdk /Library/Developer/KDKs/KDK_15.0_24A5279h.kdk/System/Library/Kernels/kernel.release.t6031
# Use a previously saved .idiff file
ipsw diff --in <path/to/.idiff> --output <output/folder> --markdown
```
### Options
@@ -38,7 +36,6 @@ ipsw diff <IPSW> <IPSW> [flags]
--fw Diff other firmwares
-h, --help help for diff
--html Save diff as HTML file
-i, --in string Path to IPSW .idiff file
--json Save diff as JSON file
-k, --kdk stringArray Path to KDKs to diff
--launchd Diff launchd configs
+1 -1
View File
@@ -44,7 +44,7 @@ ipsw download appledb [flags]
--dyld Extract dyld_shared_cache(s) from remote OTA
--fcs-keys Download AEA1 DMG fcs-key pem files
--fcs-keys-json Download AEA1 DMG fcs-keys as JSON
-f, --flat Do NOT perserve directory structure when downloading with --pattern
-f, --flat Do NOT preserve directory structure when downloading with --pattern
-h, --help help for appledb
--insecure do not verify ssl certs
-j, --json Dump DB query results as JSON
+1 -1
View File
@@ -40,7 +40,7 @@ ipsw download ipsw [flags]
-a, --dyld-arch stringArray dyld_shared_cache architecture(s) to remote extract
--fcs-keys Download AEA1 DMG fcs-key pem files
--fcs-keys-json Download AEA1 DMG fcs-keys as JSON
-f, --flat Do NOT perserve directory structure when downloading with --pattern
-f, --flat Do NOT preserve directory structure when downloading with --pattern
-h, --help help for ipsw
--ibridge Download iBridge IPSWs
--insecure do not verify ssl certs
+1 -1
View File
@@ -43,7 +43,7 @@ ipsw download ota [options] [flags]
--driver-kit Extract DriverKit dyld_shared_cache(s) from remote OTA zip
--dyld Extract dyld_shared_cache(s) from remote OTA zip
-a, --dyld-arch stringArray dyld_shared_cache architecture(s) to remote extract
-f, --flat Do NOT perserve directory structure when downloading with --pattern
-f, --flat Do NOT preserve directory structure when downloading with --pattern
-h, --help help for ota
--info Show all the latest OTAs available
--insecure do not verify ssl certs
+1 -1
View File
@@ -39,7 +39,7 @@ ipsw download wiki [flags]
-y, --confirm do not prompt user for confirmation
--db string Path to local JSON database (will use CWD by default) (default "wiki_db.json")
-d, --device string iOS Device (i.e. iPhone11,2)
-f, --flat Do NOT perserve directory structure when downloading with --pattern
-f, --flat Do NOT preserve directory structure when downloading with --pattern
-h, --help help for wiki
--insecure do not verify ssl certs
--ipsw Download IPSWs
+6 -6
View File
@@ -17,12 +17,12 @@ ipsw dyld a2f <DSC> <ADDR> [flags]
### Options
```
-c, --cache string Path to .a2s addr to sym cache file (speeds up analysis)
-h, --help help for a2f
-i, --in string Path to file containing list of addresses to lookup
-j, --json Output as JSON
-o, --out string Path to output JSON file
-s, --slide uint dyld_shared_cache slide to apply
-c, --cache string Path to .a2s addr to sym cache file (speeds up analysis)
-h, --help help for a2f
-i, --in string Path to file containing list of addresses to lookup
-j, --json Output as JSON
-o, --output string Path to output JSON file
-s, --slide uint dyld_shared_cache slide to apply
```
### Options inherited from parent commands
+6 -6
View File
@@ -17,12 +17,12 @@ ipsw dyld symaddr <DSC> [flags]
### Options
```
-a, --all Find all symbol matches
-b, --binds Also search LC_DYLD_INFO binds
-h, --help help for symaddr
-i, --image string dylib image to search
--in string Path to JSON file containing list of symbols to lookup
--out string Path to output JSON file
-a, --all Find all symbol matches
-b, --binds Also search LC_DYLD_INFO binds
-h, --help help for symaddr
-i, --image string dylib image to search
--in string Path to JSON file containing list of symbols to lookup
-o, --output string Path to output JSON file
```
### Options inherited from parent commands
+1 -1
View File
@@ -55,7 +55,7 @@ $ ipsw extract --dyld --driverkit macOS.ipsw
-x, --exclave Extract Exclave Bundle
--fcs-key Extract AEA1 DMG fcs-key pem files
-f, --files Extract File System files
--flat Do NOT perserve directory structure when extracting
--flat Do NOT preserve directory structure when extracting
-h, --help help for extract
--iboot Extract iBoot
--insecure do not verify ssl certs
+1
View File
@@ -22,6 +22,7 @@ ipsw fw aea [flags]
-h, --help help for aea
--id Print AEA file ID
-i, --info Print info
--insecure Allow insecure connections
-k, --key Get archive decryption key
-b, --key-val string Base64 encoded symmetric encryption key
-o, --output string Folder to extract files to
+1 -1
View File
@@ -33,5 +33,5 @@ ipsw idev amfi [flags]
### SEE ALSO
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
* [ipsw idev amfi dev](/docs/cli/ipsw/idev/amfi/dev) - Enabled Developer Mode on device
* [ipsw idev amfi dev](/docs/cli/ipsw/idev/amfi/dev) - Enable Developer Mode on device
+2 -2
View File
@@ -4,11 +4,11 @@ title: dev
hide_title: true
hide_table_of_contents: true
sidebar_label: dev
description: Enabled Developer Mode on device
description: Enable Developer Mode on device
---
## ipsw idev amfi dev
Enabled Developer Mode on device
Enable Developer Mode on device
```
ipsw idev amfi dev [flags]
+2 -2
View File
@@ -4,11 +4,11 @@ title: ls
hide_title: true
hide_table_of_contents: true
sidebar_label: ls
description: List installed provision profiles
description: List installed provisioning profiles
---
## ipsw idev prof ls
List installed provision profiles
List installed provisioning profiles
```
ipsw idev prof ls [flags]
+1 -1
View File
@@ -35,7 +35,7 @@ ipsw idev prof [flags]
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
* [ipsw idev prof cloud](/docs/cli/ipsw/idev/prof/cloud) - Get cloud configuration
* [ipsw idev prof install](/docs/cli/ipsw/idev/prof/install) - Install profile
* [ipsw idev prof ls](/docs/cli/ipsw/idev/prof/ls) - List installed provision profiles
* [ipsw idev prof ls](/docs/cli/ipsw/idev/prof/ls) - List installed provisioning profiles
* [ipsw idev prof rm](/docs/cli/ipsw/idev/prof/rm) - Remove profile by name
* [ipsw idev prof wifi](/docs/cli/ipsw/idev/prof/wifi) - Change Wi-Fi power state
+2 -2
View File
@@ -4,11 +4,11 @@ title: clear
hide_title: true
hide_table_of_contents: true
sidebar_label: clear
description: Remove all provision profiles
description: Remove all provisioning profiles
---
## ipsw idev prov clear
Remove all provision profiles
Remove all provisioning profiles
```
ipsw idev prov clear [flags]
+2 -2
View File
@@ -4,11 +4,11 @@ title: dump
hide_title: true
hide_table_of_contents: true
sidebar_label: dump
description: Dump installed provision profiles
description: Dump installed provisioning profiles
---
## ipsw idev prov dump
Dump installed provision profiles
Dump installed provisioning profiles
```
ipsw idev prov dump [flags]
+2 -2
View File
@@ -4,11 +4,11 @@ title: install
hide_title: true
hide_table_of_contents: true
sidebar_label: install
description: Install a provision profile (.mobileprovision file)
description: Install a provisioning profile (.mobileprovision file)
---
## ipsw idev prov install
Install a provision profile (.mobileprovision file)
Install a provisioning profile (.mobileprovision file)
```
ipsw idev prov install <PROV> [flags]
+2 -2
View File
@@ -4,11 +4,11 @@ title: ls
hide_title: true
hide_table_of_contents: true
sidebar_label: ls
description: List installed provision profiles
description: List installed provisioning profiles
---
## ipsw idev prov ls
List installed provision profiles
List installed provisioning profiles
```
ipsw idev prov ls [flags]
+5 -5
View File
@@ -33,9 +33,9 @@ ipsw idev prov [flags]
### SEE ALSO
* [ipsw idev](/docs/cli/ipsw/idev) - USB connected device commands
* [ipsw idev prov clear](/docs/cli/ipsw/idev/prov/clear) - Remove all provision profiles
* [ipsw idev prov dump](/docs/cli/ipsw/idev/prov/dump) - Dump installed provision profiles
* [ipsw idev prov install](/docs/cli/ipsw/idev/prov/install) - Install a provision profile (.mobileprovision file)
* [ipsw idev prov ls](/docs/cli/ipsw/idev/prov/ls) - List installed provision profiles
* [ipsw idev prov rm](/docs/cli/ipsw/idev/prov/rm) - Remove a provision profile
* [ipsw idev prov clear](/docs/cli/ipsw/idev/prov/clear) - Remove all provisioning profiles
* [ipsw idev prov dump](/docs/cli/ipsw/idev/prov/dump) - Dump installed provisioning profiles
* [ipsw idev prov install](/docs/cli/ipsw/idev/prov/install) - Install a provisioning profile (.mobileprovision file)
* [ipsw idev prov ls](/docs/cli/ipsw/idev/prov/ls) - List installed provisioning profiles
* [ipsw idev prov rm](/docs/cli/ipsw/idev/prov/rm) - Remove a provisioning profile
+2 -2
View File
@@ -4,11 +4,11 @@ title: rm
hide_title: true
hide_table_of_contents: true
sidebar_label: rm
description: Remove a provision profile
description: Remove a provisioning profile
---
## ipsw idev prov rm
Remove a provision profile
Remove a provisioning profile
```
ipsw idev prov rm <PROV> [flags]
+1 -1
View File
@@ -46,7 +46,7 @@ Download and Parse IPSWs (and SO much more)
* [ipsw mdevs](/docs/cli/ipsw/mdevs) - List all MobileDevices in IPSW
* [ipsw mount](/docs/cli/ipsw/mount) - Mount DMG from IPSW
* [ipsw ota](/docs/cli/ipsw/ota) - Parse OTAs
* [ipsw pbzx](/docs/cli/ipsw/pbzx) - Decompess pbzx files
* [ipsw pbzx](/docs/cli/ipsw/pbzx) - Decompress pbzx files
* [ipsw pkg](/docs/cli/ipsw/pkg) - 🚧 List contents of a DMG/PKG file
* [ipsw plist](/docs/cli/ipsw/plist) - Dump plist as JSON
* [ipsw ssh](/docs/cli/ipsw/ssh) - SSH into a jailbroken device
+1 -1
View File
@@ -37,7 +37,7 @@ ipsw kernel [flags]
* [ipsw kernel dwarf](/docs/cli/ipsw/kernel/dwarf) - 🚧 Dump DWARF debug information
* [ipsw kernel extract](/docs/cli/ipsw/kernel/extract) - Extract KEXT(s) from kernelcache
* [ipsw kernel ida](/docs/cli/ipsw/kernel/ida) - 🚧 Analyze kernelcache in IDA Pro
* [ipsw kernel kexts](/docs/cli/ipsw/kernel/kexts) - List kernel extentions
* [ipsw kernel kexts](/docs/cli/ipsw/kernel/kexts) - List kernel extensions
* [ipsw kernel mach](/docs/cli/ipsw/kernel/mach) - Dump kernelcache mach_traps
* [ipsw kernel mig](/docs/cli/ipsw/kernel/mig) - Dump kernelcache mig subsystem
* [ipsw kernel symbolicate](/docs/cli/ipsw/kernel/symbolicate) - Symbolicate kernelcache
+2 -2
View File
@@ -4,11 +4,11 @@ title: kexts
hide_title: true
hide_table_of_contents: true
sidebar_label: kexts
description: List kernel extentions
description: List kernel extensions
---
## ipsw kernel kexts
List kernel extentions
List kernel extensions
```
ipsw kernel kexts <kernelcache> [flags]
+1 -1
View File
@@ -27,7 +27,7 @@ ipsw macho patch add <MACHO> <LC> <LC_FIELDS...> [flags]
-h, --help help for add
-o, --output string Output new file
-f, --overwrite Overwrite file
-s, --re-sign Adhoc sign file
-s, --re-sign Ad-hoc sign file
```
### Options inherited from parent commands
+1 -1
View File
@@ -27,7 +27,7 @@ ipsw macho patch rm <MACHO> <LC> <LC_FIELDS...> [flags]
-h, --help help for rm
-o, --output string Output new file
-f, --overwrite Overwrite file
-s, --re-sign Adhoc sign file
-s, --re-sign Ad-hoc sign file
```
### Options inherited from parent commands
+8 -4
View File
@@ -29,15 +29,19 @@ $ ipsw mount fs iPod5,1_7.1.2_11D257_Restore.ipsw --lookup
# Mount dyld shared cache (exc) DMG with AEA pem DB
$ ipsw mount exc iPhone.ipsw --pem-db /path/to/pem.json
# Mount to a custom mount point
$ ipsw mount fs iPhone.ipsw --mount-point /mnt/ios-filesystem
```
### Options
```
-h, --help help for mount
-k, --key string DMG key
--lookup Lookup DMG keys on theapplewiki.com
--pem-db string AEA pem DB JSON file
-h, --help help for mount
-k, --key string DMG key
--lookup Lookup DMG keys on theapplewiki.com
-m, --mount-point string Custom mount point (default: /tmp/<dmg>.mount)
--pem-db string AEA pem DB JSON file
```
### Options inherited from parent commands
+2 -2
View File
@@ -4,11 +4,11 @@ title: pbzx
hide_title: true
hide_table_of_contents: true
sidebar_label: pbzx
description: Decompess pbzx files
description: Decompress pbzx files
---
## ipsw pbzx
Decompess pbzx files
Decompress pbzx files
```
ipsw pbzx [flags]
+2 -2
View File
@@ -4,11 +4,11 @@ title: debugserver
hide_title: true
hide_table_of_contents: true
sidebar_label: debugserver
description: Prep device for remote debugging
description: Prepare device for remote debugging
---
## ipsw ssh debugserver
Prep device for remote debugging
Prepare device for remote debugging
```
ipsw ssh debugserver [flags]
+1 -1
View File
@@ -38,6 +38,6 @@ ipsw ssh [flags]
### SEE ALSO
* [ipsw](/docs/cli/ipsw) - Download and Parse IPSWs (and SO much more)
* [ipsw ssh debugserver](/docs/cli/ipsw/ssh/debugserver) - Prep device for remote debugging
* [ipsw ssh debugserver](/docs/cli/ipsw/ssh/debugserver) - Prepare device for remote debugging
* [ipsw ssh shsh](/docs/cli/ipsw/ssh/shsh) - Get SHSH blobs from device
-19900
View File
File diff suppressed because it is too large Load Diff
+35 -2
View File
@@ -23,18 +23,51 @@
"@docusaurus/theme-mermaid": "^3.8.1",
"@docusaurus/theme-search-algolia": "^3.8.1",
"@mdx-js/react": "^3.1.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-hover-card": "^1.0.7",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.1.2",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@supabase/supabase-js": "^2.50.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.263.1",
"prism-react-renderer": "^2.4.1",
"prism-themes": "^1.9.0",
"react": "19.1.0",
"react-dom": "19.1.0",
"redocusaurus": "^2.5.0"
"redocusaurus": "^2.5.0",
"tailwind-merge": "^1.14.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.8.1",
"@docusaurus/tsconfig": "^3.8.1",
"@docusaurus/types": "^3.8.1"
"@docusaurus/types": "^3.8.1",
"@tailwindcss/typography": "^0.5.16",
"@types/node": "^20.5.2",
"autoprefixer": "^10.4.15",
"postcss": "^8.4.29",
"tailwindcss": "^3.3.3",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.1.6"
},
"browserslist": {
"production": [
+1796 -57
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
+36
View File
@@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../../lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
+56
View File
@@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "../../lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
+79
View File
@@ -0,0 +1,79 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
+12
View File
@@ -0,0 +1,12 @@
"use client"
import * as React from "react"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
const Collapsible = CollapsiblePrimitive.Root
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
+25
View File
@@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "../../lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
+48
View File
@@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "../../lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }
+160
View File
@@ -0,0 +1,160 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import { cn } from "../../lib/utils"
const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

Some files were not shown because too many files have changed in this diff Show More