Compare commits

...

6 commits

Author SHA1 Message Date
Claude
a4ad18fa2a chore: add /cli to .gitignore, clean up stale entries
Some checks are pending
Security Scan / Go Vulnerability Check (push) Waiting to run
Security Scan / Secret Detection (push) Waiting to run
Security Scan / Dependency & Config Scan (push) Waiting to run
Add compiled binary to gitignore. Remove entries for deleted paths
(cmd/bugseti/bugseti, internal/core-ide/core-ide).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 15:01:18 +00:00
Claude
8167f66fa8 refactor: remove BugSETI and i18n tools (moved to own repos)
Remove internal/bugseti/ (now core/bugseti repo), cmd/bugseti/
(now core/bugseti/cmd/), and internal/tools/ (i18n-validate
moved to core/go). core/cli internal/ is now empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 14:57:59 +00:00
c340303314 refactor: flatten commands, extract php/ci to own repos (#2)
## Summary
- Extract PHP/Laravel commands to `core/php` repo (42 files, standalone module)
- Extract CI/release + SDK commands to `core/ci` repo (10 files)
- Remove `internal/variants/` build tag system entirely
- Move all 30 remaining command packages from `internal/cmd/` to top-level `cmd/`
- Rewrite `main.go` with direct imports — no more variant selection
- PHP and CI are now optional via commented import lines in main.go

Co-authored-by: Claude <developers@lethean.io>
Reviewed-on: #2
Co-authored-by: Charon <charon@lthn.ai>
Co-committed-by: Charon <charon@lthn.ai>
2026-02-16 14:45:06 +00:00
abe74a1a3d refactor: split CLI from monorepo, import core/go as library (#1)
- Change module from forge.lthn.ai/core/go to forge.lthn.ai/core/cli
- Remove pkg/ directory (now served from core/go)
- Add require + replace for forge.lthn.ai/core/go => ../go
- Update go.work to include ../go workspace module
- Fix all internal/cmd/* imports: pkg/ refs → forge.lthn.ai/core/go/pkg/
- Rename internal/cmd/sdk package to sdkcmd (avoids conflict with pkg/sdk)
- Remove SDK library files from internal/cmd/sdk/ (now in core/go/pkg/sdk/)
- Remove duplicate RAG helper functions from internal/cmd/rag/
- Remove stale cmd/core-ide/ (now in core/ide repo)
- Update IDE variant to remove core-ide import
- Fix test assertion for new module name
- Run go mod tidy to sync dependencies

core/cli is now a pure CLI application importing core/go for packages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Co-authored-by: Claude <developers@lethean.io>
Reviewed-on: #1
2026-02-16 14:24:37 +00:00
Snider
4e47485efe refactor: update import paths from cli to go package structure 2026-02-16 13:47:52 +00:00
a1306bc321 feat/ml-integration (#2)
Co-authored-by: Charon (snider-linux) <charon@lethean.io>
Co-authored-by: Snider <snider@host.uk.com>
Co-authored-by: Virgil <virgil@lethean.io>
Co-authored-by: Claude <developers@lethean.io>
Reviewed-on: #2
Co-authored-by: Snider <snider@lethean.io>
Co-committed-by: Snider <snider@lethean.io>
2026-02-16 06:19:09 +00:00
1202 changed files with 866 additions and 241726 deletions

View file

@ -1,5 +1,4 @@
# CodeRabbit Configuration
# Inherits from: https://github.com/host-uk/coderabbit/.coderabbit.yaml
# Manual trigger only: @coderabbitai review
reviews:

View file

@ -6,7 +6,7 @@ tap: host-uk/tap
formula: core
# Scoop (Windows)
scoop_bucket: https://github.com/host-uk/scoop-bucket.git
scoop_bucket: https://https://forge.lthn.ai/core/scoop-bucket.git
# Chocolatey (Windows)
chocolatey_pkg: core-cli

View file

@ -30,7 +30,7 @@ core/
package domain
import (
"github.com/host-uk/core/pkg/cli"
"forge.lthn.ai/core/cli/pkg/cli"
"github.com/spf13/cobra"
)
@ -53,7 +53,7 @@ func NewNameCmd() *cobra.Command {
## CLI Output Helpers
```go
import "github.com/host-uk/core/pkg/cli"
import "forge.lthn.ai/core/cli/pkg/cli"
cli.Success("Operation completed") // Green check
cli.Warning("Something to note") // Yellow warning

View file

@ -1,7 +1,7 @@
# Issue 258: Smart Test Detection
## Original Issue
<https://github.com/host-uk/core/issues/258>
<https://forge.lthn.ai/core/cli/issues/258>
## Summary
Make `core test` smart — detect changed Go files and run only relevant tests.

View file

@ -1,58 +0,0 @@
name: Bug Report
description: Report a problem with the core CLI
title: "[Bug]: "
labels: ["bug", "triage"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting! Please fill out the details below.
- type: dropdown
id: os
attributes:
label: Operating System
options:
- macOS
- Windows
- Linux (Ubuntu/Debian)
- Linux (Other)
validations:
required: true
- type: input
id: command
attributes:
label: Command
description: Which command failed?
placeholder: "e.g., core dev work, core php test"
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: Output of `core version`
placeholder: "e.g., core v0.1.0"
- type: textarea
id: description
attributes:
label: What happened?
description: Describe the issue
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behaviour
description: What should have happened?
- type: textarea
id: logs
attributes:
label: Error output
description: Paste any error messages
render: shell

View file

@ -1,8 +0,0 @@
blank_issues_enabled: true
contact_links:
- name: Host UK Documentation
url: https://github.com/host-uk/core-devops
about: Setup guides and workspace documentation
- name: Discussions
url: https://github.com/orgs/host-uk/discussions
about: Ask questions and share ideas

View file

@ -1,58 +0,0 @@
name: Feature Request
description: Suggest a new feature or enhancement
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Thanks for the suggestion! Please describe your idea below.
- type: dropdown
id: area
attributes:
label: Area
options:
- dev commands (work, commit, push, pull)
- php commands (test, lint, stan)
- GitHub integration (issues, reviews, ci)
- New command
- Documentation
- Other
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem or use case
description: What problem does this solve?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed solution
description: How would you like it to work?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Any other approaches you've thought about?
- type: dropdown
id: complexity
attributes:
label: Estimated complexity
description: How much work do you think this requires?
options:
- "Small - Quick fix, single file, < 1 hour"
- "Medium - Multiple files, few hours to a day"
- "Large - Significant changes, multiple days"
- "Unknown - Not sure"
validations:
required: false

View file

@ -1,24 +0,0 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "type:dependencies"
- "priority:low"
commit-message:
prefix: "deps(go):"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "type:dependencies"
- "priority:low"
commit-message:
prefix: "deps(actions):"

View file

@ -1,10 +0,0 @@
name: Agent Verification
on:
issues:
types: [labeled]
jobs:
verify:
uses: host-uk/.github/.github/workflows/agent-verify.yml@main
secrets: inherit

View file

@ -1,92 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
name: "Alpha Release: Manual"
on:
workflow_dispatch:
permissions:
contents: write
id-token: write
attestations: write
env:
NEXT_VERSION: "0.0.4"
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
platform: linux/amd64
- os: ubuntu-latest
platform: linux/arm64
- os: macos-latest
platform: darwin/universal
- os: windows-latest
platform: windows/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Build
uses: host-uk/build@v3
with:
build-name: core
build-platform: ${{ matrix.platform }}
build: true
package: true
sign: false
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create alpha release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="v${{ env.NEXT_VERSION }}-alpha.${{ github.run_number }}"
gh release create "$VERSION" \
--title "Alpha: $VERSION" \
--notes "Canary build from dev branch.
**Version:** $VERSION
**Commit:** ${{ github.sha }}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Alpha (Canary)
This is an automated pre-release for early testing.
- Systems and early adopters can test breaking changes
- Quality scoring determines promotion to beta
- Use stable releases for production
## Installation
\`\`\`bash
# macOS/Linux
curl -fsSL https://github.com/host-uk/core/releases/download/$VERSION/core-linux-amd64 -o core
chmod +x core && sudo mv core /usr/local/bin/
\`\`\`
" \
--prerelease \
--target dev \
release/*

View file

@ -1,93 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "Alpha Release: Push"
on:
push:
branches: [dev]
permissions:
contents: write
id-token: write
attestations: write
env:
NEXT_VERSION: "0.0.4"
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
platform: linux/amd64
- os: ubuntu-latest
platform: linux/arm64
- os: macos-latest
platform: darwin/universal
- os: windows-latest
platform: windows/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Build
uses: host-uk/build@v3
with:
build-name: core
build-platform: ${{ matrix.platform }}
build: true
package: true
sign: false
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create alpha release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="v${{ env.NEXT_VERSION }}-alpha.${{ github.run_number }}"
gh release create "$VERSION" \
--title "Alpha: $VERSION" \
--notes "Canary build from dev branch.
**Version:** $VERSION
**Commit:** ${{ github.sha }}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Alpha (Canary)
This is an automated pre-release for early testing.
- Systems and early adopters can test breaking changes
- Quality scoring determines promotion to beta
- Use stable releases for production
## Installation
\`\`\`bash
# macOS/Linux
curl -fsSL https://github.com/host-uk/core/releases/download/$VERSION/core-linux-amd64 -o core
chmod +x core && sudo mv core /usr/local/bin/
\`\`\`
" \
--prerelease \
--target dev \
release/*

View file

@ -1,500 +0,0 @@
name: Alpha Release
on:
push:
branches: [dev]
workflow_dispatch:
permissions:
contents: write
id-token: write
attestations: write
env:
# Next version - update when releasing
NEXT_VERSION: "0.0.4"
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-latest
goos: linux
goarch: arm64
- os: macos-latest
goos: darwin
goarch: arm64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
steps:
- uses: actions/checkout@v6
# GUI build disabled until build action supports Wails v3
# - name: Wails Build Action
# uses: host-uk/build@v4.0.0
# with:
# build-name: core
# build-platform: ${{ matrix.goos }}/${{ matrix.goarch }}
# build: true
# package: true
# sign: false
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Build CLI
shell: bash
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
BINARY="core${EXT}"
ARCHIVE_PREFIX="core-${GOOS}-${GOARCH}"
APP_VERSION="${{ env.NEXT_VERSION }}-alpha.${{ github.run_number }}"
go build -ldflags "-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${APP_VERSION}" -o "./bin/${BINARY}" .
# Create tar.gz for Homebrew (non-Windows)
if [ "$GOOS" != "windows" ]; then
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "${BINARY}"
fi
# Create zip for Scoop (Windows)
if [ "$GOOS" = "windows" ]; then
cd ./bin && zip "${ARCHIVE_PREFIX}.zip" "${BINARY}" && cd ..
fi
# Rename raw binary to platform-specific name for release
mv "./bin/${BINARY}" "./bin/${ARCHIVE_PREFIX}${EXT}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: core-${{ matrix.goos }}-${{ matrix.goarch }}
path: ./bin/core-*
build-ide:
strategy:
matrix:
include:
- os: macos-latest
goos: darwin
goarch: arm64
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
defaults:
run:
working-directory: internal/core-ide
steps:
- uses: actions/checkout@v6
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Install frontend dependencies
working-directory: internal/core-ide/frontend
run: npm ci
- name: Generate bindings
run: wails3 generate bindings -f '-tags production' -clean=false -ts -i
- name: Build frontend
working-directory: internal/core-ide/frontend
run: npm run build
- name: Install Linux dependencies
if: matrix.goos == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
- name: Build IDE
shell: bash
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
BINARY="core-ide${EXT}"
ARCHIVE_PREFIX="core-ide-${GOOS}-${GOARCH}"
BUILD_FLAGS="-tags production -trimpath -buildvcs=false"
if [ "$GOOS" = "windows" ]; then
# Windows: no CGO, use windowsgui linker flag
export CGO_ENABLED=0
LDFLAGS="-w -s -H windowsgui"
# Generate Windows syso resource
cd build
wails3 generate syso -arch ${GOARCH} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_${GOARCH}.syso
cd ..
elif [ "$GOOS" = "darwin" ]; then
export CGO_ENABLED=1
export CGO_CFLAGS="-mmacosx-version-min=10.15"
export CGO_LDFLAGS="-mmacosx-version-min=10.15"
export MACOSX_DEPLOYMENT_TARGET="10.15"
LDFLAGS="-w -s"
else
export CGO_ENABLED=1
LDFLAGS="-w -s"
fi
go build ${BUILD_FLAGS} -ldflags="${LDFLAGS}" -o "./bin/${BINARY}"
# Clean up syso files
rm -f *.syso
# Package
if [ "$GOOS" = "darwin" ]; then
# Create .app bundle
mkdir -p "./bin/Core IDE.app/Contents/"{MacOS,Resources}
cp build/darwin/icons.icns "./bin/Core IDE.app/Contents/Resources/"
cp "./bin/${BINARY}" "./bin/Core IDE.app/Contents/MacOS/"
cp build/darwin/Info.plist "./bin/Core IDE.app/Contents/"
codesign --force --deep --sign - "./bin/Core IDE.app"
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "Core IDE.app"
elif [ "$GOOS" = "windows" ]; then
cd ./bin && zip "${ARCHIVE_PREFIX}.zip" "${BINARY}" && cd ..
else
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "${BINARY}"
fi
# Rename raw binary
mv "./bin/${BINARY}" "./bin/${ARCHIVE_PREFIX}${EXT}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: core-ide-${{ matrix.goos }}-${{ matrix.goarch }}
path: internal/core-ide/bin/core-ide-*
release:
needs: [build, build-ide]
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Set version
id: version
run: echo "version=v${{ env.NEXT_VERSION }}-alpha.${{ github.run_number }}" >> "$GITHUB_OUTPUT"
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create alpha release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
run: |
gh release create "$VERSION" \
--title "Alpha: $VERSION" \
--notes "Canary build from dev branch.
**Version:** $VERSION
**Commit:** ${{ github.sha }}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Alpha (Canary)
This is an automated pre-release for early testing.
- Systems and early adopters can test breaking changes
- Quality scoring determines promotion to beta
- Use stable releases for production
## Installation
\`\`\`bash
# Homebrew (macOS/Linux)
brew install host-uk/tap/core
# Scoop (Windows)
scoop bucket add host-uk https://github.com/host-uk/scoop-bucket
scoop install core
# Direct download (example: Linux amd64)
curl -fsSL https://github.com/host-uk/core/releases/download/$VERSION/core-linux-amd64 -o core
chmod +x core && sudo mv core /usr/local/bin/
\`\`\`
" \
--prerelease \
--target dev \
release/*
update-tap:
needs: release
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
for f in *.tar.gz; do
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
done
echo "=== Checksums ==="
cat *.sha256
- name: Update Homebrew formula
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ needs.release.outputs.version }}
run: |
# Strip leading 'v' for formula version
FORMULA_VERSION="${VERSION#v}"
# Read checksums
DARWIN_ARM64=$(cat dist/core-darwin-arm64.tar.gz.sha256)
LINUX_AMD64=$(cat dist/core-linux-amd64.tar.gz.sha256)
LINUX_ARM64=$(cat dist/core-linux-arm64.tar.gz.sha256)
# Clone tap repo (configure auth for push)
gh repo clone host-uk/homebrew-tap /tmp/tap -- --depth=1
cd /tmp/tap
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/host-uk/homebrew-tap.git"
cd -
mkdir -p /tmp/tap/Formula
# Write formula
cat > /tmp/tap/Formula/core.rb << FORMULA
# typed: false
# frozen_string_literal: true
class Core < Formula
desc "Host UK development CLI"
homepage "https://github.com/host-uk/core"
version "${FORMULA_VERSION}"
license "EUPL-1.2"
on_macos do
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-darwin-arm64.tar.gz"
sha256 "${DARWIN_ARM64}"
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-linux-arm64.tar.gz"
sha256 "${LINUX_ARM64}"
else
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-linux-amd64.tar.gz"
sha256 "${LINUX_AMD64}"
end
end
def install
bin.install "core"
end
test do
system "\#{bin}/core", "--version"
end
end
FORMULA
# Remove leading whitespace from heredoc
sed -i 's/^ //' /tmp/tap/Formula/core.rb
# Read IDE checksums (may not exist if build-ide failed)
IDE_DARWIN_ARM64=$(cat dist/core-ide-darwin-arm64.tar.gz.sha256 2>/dev/null || echo "")
IDE_LINUX_AMD64=$(cat dist/core-ide-linux-amd64.tar.gz.sha256 2>/dev/null || echo "")
# Write core-ide Formula (Linux binary)
if [ -n "${IDE_LINUX_AMD64}" ]; then
cat > /tmp/tap/Formula/core-ide.rb << FORMULA
# typed: false
# frozen_string_literal: true
class CoreIde < Formula
desc "Host UK desktop development environment"
homepage "https://github.com/host-uk/core"
version "${FORMULA_VERSION}"
license "EUPL-1.2"
on_linux do
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-linux-amd64.tar.gz"
sha256 "${IDE_LINUX_AMD64}"
end
def install
bin.install "core-ide"
end
end
FORMULA
sed -i 's/^ //' /tmp/tap/Formula/core-ide.rb
fi
# Write core-ide Cask (macOS .app bundle)
if [ -n "${IDE_DARWIN_ARM64}" ]; then
mkdir -p /tmp/tap/Casks
cat > /tmp/tap/Casks/core-ide.rb << CASK
cask "core-ide" do
version "${FORMULA_VERSION}"
sha256 "${IDE_DARWIN_ARM64}"
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-darwin-arm64.tar.gz"
name "Core IDE"
desc "Host UK desktop development environment"
homepage "https://github.com/host-uk/core"
app "Core IDE.app"
end
CASK
sed -i 's/^ //' /tmp/tap/Casks/core-ide.rb
fi
cd /tmp/tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git diff --cached --quiet && echo "No changes to tap" && exit 0
git commit -m "Update core to ${FORMULA_VERSION}"
git push
update-scoop:
needs: release
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
for f in *.zip; do
[ -f "$f" ] || continue
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
done
echo "=== Checksums ==="
cat *.sha256 2>/dev/null || echo "No zip checksums"
- name: Update Scoop manifests
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ needs.release.outputs.version }}
run: |
# Strip leading 'v' for manifest version
MANIFEST_VERSION="${VERSION#v}"
# Read checksums
WIN_AMD64=$(cat dist/core-windows-amd64.zip.sha256 2>/dev/null || echo "")
IDE_WIN_AMD64=$(cat dist/core-ide-windows-amd64.zip.sha256 2>/dev/null || echo "")
# Clone scoop bucket
gh repo clone host-uk/scoop-bucket /tmp/scoop -- --depth=1
cd /tmp/scoop
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/host-uk/scoop-bucket.git"
# Write core.json manifest
cat > core.json << 'MANIFEST'
{
"version": "VERSION_PLACEHOLDER",
"description": "Host UK development CLI",
"homepage": "https://github.com/host-uk/core",
"license": "EUPL-1.2",
"architecture": {
"64bit": {
"url": "URL_PLACEHOLDER",
"hash": "HASH_PLACEHOLDER",
"bin": "core.exe"
}
},
"checkver": "github",
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/host-uk/core/releases/download/v$version/core-windows-amd64.zip"
}
}
}
}
MANIFEST
sed -i "s|VERSION_PLACEHOLDER|${MANIFEST_VERSION}|g" core.json
sed -i "s|URL_PLACEHOLDER|https://github.com/host-uk/core/releases/download/${VERSION}/core-windows-amd64.zip|g" core.json
sed -i "s|HASH_PLACEHOLDER|${WIN_AMD64}|g" core.json
sed -i 's/^ //' core.json
# Write core-ide.json manifest
if [ -n "${IDE_WIN_AMD64}" ]; then
cat > core-ide.json << 'MANIFEST'
{
"version": "VERSION_PLACEHOLDER",
"description": "Host UK desktop development environment",
"homepage": "https://github.com/host-uk/core",
"license": "EUPL-1.2",
"architecture": {
"64bit": {
"url": "URL_PLACEHOLDER",
"hash": "HASH_PLACEHOLDER",
"bin": "core-ide.exe"
}
},
"checkver": "github",
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/host-uk/core/releases/download/v$version/core-ide-windows-amd64.zip"
}
}
}
}
MANIFEST
sed -i "s|VERSION_PLACEHOLDER|${MANIFEST_VERSION}|g" core-ide.json
sed -i "s|URL_PLACEHOLDER|https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-windows-amd64.zip|g" core-ide.json
sed -i "s|HASH_PLACEHOLDER|${IDE_WIN_AMD64}|g" core-ide.json
sed -i 's/^ //' core-ide.json
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git diff --cached --quiet && echo "No changes to scoop bucket" && exit 0
git commit -m "Update core to ${MANIFEST_VERSION}"
git push

View file

@ -1,113 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issues
name: "Auto Label: Issue Created/Edited"
on:
issues:
types: [opened, edited]
permissions:
issues: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Auto-label based on content
uses: actions/github-script@v8
with:
script: |
const issue = context.payload.issue;
const title = issue.title.toLowerCase();
const body = (issue.body || '').toLowerCase();
const content = title + ' ' + body;
const labelsToAdd = [];
// Type labels based on title prefix
if (title.includes('[bug]')) {
labelsToAdd.push('bug');
} else if (title.includes('[feature]') || title.includes('feat(') || title.includes('feat:')) {
labelsToAdd.push('enhancement');
} else if (title.includes('[docs]') || title.includes('docs(') || title.includes('docs:')) {
labelsToAdd.push('documentation');
}
// Project labels based on content
if (content.includes('core dev') || content.includes('core work') || content.includes('core commit') || content.includes('core push')) {
labelsToAdd.push('project:core-cli');
}
if (content.includes('core php') || content.includes('composer') || content.includes('pest') || content.includes('phpstan')) {
labelsToAdd.push('project:core-php');
}
// Language labels
if (content.includes('.go') || content.includes('golang') || content.includes('go mod')) {
labelsToAdd.push('go');
}
// Priority detection
if (content.includes('critical') || content.includes('urgent') || content.includes('breaking')) {
labelsToAdd.push('priority:high');
}
// Agent labels
if (content.includes('agent') || content.includes('ai ') || content.includes('claude') || content.includes('agentic')) {
labelsToAdd.push('agentic');
}
// Complexity - from template dropdown or heuristics
if (body.includes('small - quick fix')) {
labelsToAdd.push('complexity:small');
labelsToAdd.push('good first issue');
} else if (body.includes('medium - multiple files')) {
labelsToAdd.push('complexity:medium');
} else if (body.includes('large - significant')) {
labelsToAdd.push('complexity:large');
} else if (!body.includes('unknown - not sure')) {
// Heuristic complexity detection
const checklistCount = (body.match(/- \[ \]/g) || []).length;
const codeBlocks = (body.match(/```/g) || []).length / 2;
const sections = (body.match(/^##/gm) || []).length;
const fileRefs = (body.match(/\.(go|php|js|ts|yml|yaml|json|md)\b/g) || []).length;
const complexKeywords = ['refactor', 'rewrite', 'migration', 'breaking change', 'across repos', 'architecture'];
const simpleKeywords = ['simple', 'quick fix', 'typo', 'minor', 'trivial'];
const hasComplexKeyword = complexKeywords.some(k => content.includes(k));
const hasSimpleKeyword = simpleKeywords.some(k => content.includes(k));
let score = checklistCount * 2 + codeBlocks + sections + fileRefs;
score += hasComplexKeyword ? 5 : 0;
score -= hasSimpleKeyword ? 3 : 0;
if (hasSimpleKeyword || score <= 2) {
labelsToAdd.push('complexity:small');
labelsToAdd.push('good first issue');
} else if (score <= 6) {
labelsToAdd.push('complexity:medium');
} else {
labelsToAdd.push('complexity:large');
}
}
// Apply labels if any detected
if (labelsToAdd.length > 0) {
// Filter to only existing labels
const existingLabels = await github.rest.issues.listLabelsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
const validLabels = existingLabels.data.map(l => l.name);
const filteredLabels = labelsToAdd.filter(l => validLabels.includes(l));
if (filteredLabels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: filteredLabels
});
console.log(`Added labels: ${filteredLabels.join(', ')}`);
}
}

View file

@ -1,54 +0,0 @@
name: Auto Merge
on:
pull_request:
types: [opened, reopened, ready_for_review]
permissions:
contents: write
pull-requests: write
env:
GH_REPO: ${{ github.repository }}
jobs:
merge:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Enable auto-merge
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const author = context.payload.pull_request.user.login;
const association = context.payload.pull_request.author_association;
// Trusted bot accounts (act as org members)
const trustedBots = ['google-labs-jules[bot]'];
const isTrustedBot = trustedBots.includes(author);
// Check author association from webhook payload
const trusted = ['MEMBER', 'OWNER', 'COLLABORATOR'];
if (!isTrustedBot && !trusted.includes(association)) {
core.info(`${author} is ${association} — skipping auto-merge`);
return;
}
try {
await exec.exec('gh', [
'pr', 'merge', process.env.PR_NUMBER,
'--auto',
'--merge',
'-R', `${context.repo.owner}/${context.repo.repo}`
]);
core.info(`Auto-merge enabled for #${process.env.PR_NUMBER}`);
} catch (error) {
core.error(`Failed to enable auto-merge: ${error.message}`);
throw error;
}

View file

@ -1,10 +0,0 @@
name: Auto Project
on:
issues:
types: [opened, labeled]
jobs:
project:
uses: host-uk/.github/.github/workflows/auto-project.yml@main
secrets: inherit

View file

@ -1,309 +0,0 @@
# BugSETI Release Workflow
# Builds for all platforms and creates GitHub releases
name: "BugSETI Release"
on:
push:
tags:
- 'bugseti-v*.*.*' # Stable: bugseti-v1.0.0
- 'bugseti-v*.*.*-beta.*' # Beta: bugseti-v1.0.0-beta.1
- 'bugseti-nightly-*' # Nightly: bugseti-nightly-20260205
permissions:
contents: write
env:
APP_NAME: bugseti
WAILS_VERSION: "3"
jobs:
# Determine release channel from tag
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
channel: ${{ steps.version.outputs.channel }}
prerelease: ${{ steps.version.outputs.prerelease }}
steps:
- name: Determine version and channel
id: version
env:
TAG: ${{ github.ref_name }}
run: |
if [[ "$TAG" == bugseti-nightly-* ]]; then
VERSION="${TAG#bugseti-}"
CHANNEL="nightly"
PRERELEASE="true"
elif [[ "$TAG" == *-beta.* ]]; then
VERSION="${TAG#bugseti-v}"
CHANNEL="beta"
PRERELEASE="true"
else
VERSION="${TAG#bugseti-v}"
CHANNEL="stable"
PRERELEASE="false"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT"
echo "prerelease=${PRERELEASE}" >> "$GITHUB_OUTPUT"
echo "Tag: $TAG"
echo "Version: $VERSION"
echo "Channel: $CHANNEL"
echo "Prerelease: $PRERELEASE"
build:
needs: prepare
strategy:
fail-fast: false
matrix:
include:
# macOS ARM64 (Apple Silicon)
- os: macos-latest
goos: darwin
goarch: arm64
ext: ""
archive: tar.gz
# macOS AMD64 (Intel)
- os: macos-13
goos: darwin
goarch: amd64
ext: ""
archive: tar.gz
# Linux AMD64
- os: ubuntu-latest
goos: linux
goarch: amd64
ext: ""
archive: tar.gz
# Linux ARM64
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
ext: ""
archive: tar.gz
# Windows AMD64
- os: windows-latest
goos: windows
goarch: amd64
ext: ".exe"
archive: zip
runs-on: ${{ matrix.os }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
VERSION: ${{ needs.prepare.outputs.version }}
CHANNEL: ${{ needs.prepare.outputs.channel }}
defaults:
run:
working-directory: cmd/bugseti
steps:
- uses: actions/checkout@v6
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Install frontend dependencies
working-directory: cmd/bugseti/frontend
run: npm ci
- name: Generate bindings
run: wails3 generate bindings -f '-tags production' -clean=false -ts -i
- name: Build frontend
working-directory: cmd/bugseti/frontend
run: npm run build
- name: Install Linux dependencies
if: matrix.goos == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev
- name: Build BugSETI
shell: bash
env:
EXT: ${{ matrix.ext }}
ARCHIVE: ${{ matrix.archive }}
COMMIT_SHA: ${{ github.sha }}
run: |
BINARY="${APP_NAME}${EXT}"
ARCHIVE_PREFIX="${APP_NAME}-${GOOS}-${GOARCH}"
BUILD_FLAGS="-tags production -trimpath -buildvcs=false"
# Version injection via ldflags
LDFLAGS="-s -w"
LDFLAGS="${LDFLAGS} -X github.com/host-uk/core/internal/bugseti.Version=${VERSION}"
LDFLAGS="${LDFLAGS} -X github.com/host-uk/core/internal/bugseti.Channel=${CHANNEL}"
LDFLAGS="${LDFLAGS} -X github.com/host-uk/core/internal/bugseti.Commit=${COMMIT_SHA}"
LDFLAGS="${LDFLAGS} -X github.com/host-uk/core/internal/bugseti.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
if [ "$GOOS" = "windows" ]; then
export CGO_ENABLED=0
LDFLAGS="${LDFLAGS} -H windowsgui"
# Generate Windows syso resource
cd build
wails3 generate syso -arch ${GOARCH} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_${GOARCH}.syso 2>/dev/null || true
cd ..
elif [ "$GOOS" = "darwin" ]; then
export CGO_ENABLED=1
export CGO_CFLAGS="-mmacosx-version-min=10.15"
export CGO_LDFLAGS="-mmacosx-version-min=10.15"
export MACOSX_DEPLOYMENT_TARGET="10.15"
else
export CGO_ENABLED=1
fi
mkdir -p bin
go build ${BUILD_FLAGS} -ldflags="${LDFLAGS}" -o "./bin/${BINARY}"
# Clean up syso files
rm -f *.syso
# Package based on platform
if [ "$GOOS" = "darwin" ]; then
# Create .app bundle
mkdir -p "./bin/BugSETI.app/Contents/"{MacOS,Resources}
cp build/darwin/icons.icns "./bin/BugSETI.app/Contents/Resources/" 2>/dev/null || true
cp "./bin/${BINARY}" "./bin/BugSETI.app/Contents/MacOS/"
cp build/darwin/Info.plist "./bin/BugSETI.app/Contents/"
codesign --force --deep --sign - "./bin/BugSETI.app" 2>/dev/null || true
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "BugSETI.app"
elif [ "$GOOS" = "windows" ]; then
cd ./bin && zip "${ARCHIVE_PREFIX}.zip" "${BINARY}" && cd ..
else
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "${BINARY}"
fi
# Rename raw binary for individual download
mv "./bin/${BINARY}" "./bin/${ARCHIVE_PREFIX}${EXT}"
# Generate checksum
cd ./bin
sha256sum "${ARCHIVE_PREFIX}.${ARCHIVE}" > "${ARCHIVE_PREFIX}.${ARCHIVE}.sha256"
sha256sum "${ARCHIVE_PREFIX}${EXT}" > "${ARCHIVE_PREFIX}${EXT}.sha256"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: bugseti-${{ matrix.goos }}-${{ matrix.goarch }}
path: |
cmd/bugseti/bin/bugseti-*
retention-days: 7
release:
needs: [prepare, build]
runs-on: ubuntu-latest
env:
TAG_NAME: ${{ github.ref_name }}
VERSION: ${{ needs.prepare.outputs.version }}
CHANNEL: ${{ needs.prepare.outputs.channel }}
PRERELEASE: ${{ needs.prepare.outputs.prerelease }}
REPO: ${{ github.repository }}
steps:
- uses: actions/checkout@v6
- name: Download all artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: List release files
run: |
echo "=== Release files ==="
ls -la dist/
echo "=== Checksums ==="
cat dist/*.sha256
- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Determine release title
if [ "$CHANNEL" = "nightly" ]; then
TITLE="BugSETI Nightly (${VERSION})"
elif [ "$CHANNEL" = "beta" ]; then
TITLE="BugSETI v${VERSION} (Beta)"
else
TITLE="BugSETI v${VERSION}"
fi
# Create release notes
cat > release-notes.md << EOF
## BugSETI ${VERSION}
**Channel:** ${CHANNEL}
### Downloads
| Platform | Architecture | Binary | Archive |
|----------|-------------|--------|---------|
| macOS | ARM64 (Apple Silicon) | [bugseti-darwin-arm64](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-darwin-arm64) | [tar.gz](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-darwin-arm64.tar.gz) |
| macOS | AMD64 (Intel) | [bugseti-darwin-amd64](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-darwin-amd64) | [tar.gz](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-darwin-amd64.tar.gz) |
| Linux | AMD64 | [bugseti-linux-amd64](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-linux-amd64) | [tar.gz](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-linux-amd64.tar.gz) |
| Linux | ARM64 | [bugseti-linux-arm64](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-linux-arm64) | [tar.gz](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-linux-arm64.tar.gz) |
| Windows | AMD64 | [bugseti-windows-amd64.exe](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-windows-amd64.exe) | [zip](https://github.com/${REPO}/releases/download/${TAG_NAME}/bugseti-windows-amd64.zip) |
### Checksums (SHA256)
\`\`\`
$(cat dist/*.sha256)
\`\`\`
---
*BugSETI - Distributed Bug Fixing, like SETI@home but for code*
EOF
# Build release command
RELEASE_ARGS=(
--title "$TITLE"
--notes-file release-notes.md
)
if [ "$PRERELEASE" = "true" ]; then
RELEASE_ARGS+=(--prerelease)
fi
# Create the release
gh release create "$TAG_NAME" \
"${RELEASE_ARGS[@]}" \
dist/*
# Scheduled nightly builds
nightly:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Create nightly tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DATE=$(date -u +%Y%m%d)
TAG="bugseti-nightly-${DATE}"
# Delete existing nightly tag for today if it exists
gh release delete "$TAG" --yes 2>/dev/null || true
git push origin ":refs/tags/$TAG" 2>/dev/null || true
# Create new tag
git tag "$TAG"
git push origin "$TAG"

View file

@ -1,41 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
name: "CI: Manual"
on:
workflow_dispatch:
env:
CORE_VERSION: dev
jobs:
qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run QA
# Skip lint until golangci-lint supports Go 1.25
run: core go qa --skip=lint
- name: Verify build
run: |
core build --targets=linux/amd64 --ci
dist/linux_amd64/core --version

View file

@ -1,42 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
name: "CI: Pull Request"
on:
pull_request:
branches: [dev, main]
env:
CORE_VERSION: dev
jobs:
qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run QA
# Skip lint until golangci-lint supports Go 1.25
run: core go qa --skip=lint
- name: Verify build
run: |
core build --targets=linux/amd64 --ci
dist/linux_amd64/core --version

View file

@ -1,42 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "CI: Push"
on:
push:
branches: [dev, main]
env:
CORE_VERSION: dev
jobs:
qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run QA
# Skip lint until golangci-lint supports Go 1.25
run: core go qa --skip=lint
- name: Verify build
run: |
core build --targets=linux/amd64 --ci
dist/linux_amd64/core --version

View file

@ -1,49 +0,0 @@
name: CI
on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
workflow_dispatch:
permissions:
contents: read
env:
CORE_VERSION: dev
jobs:
qa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
# Try 4.1 first (Ubuntu 22.04+), fall back to 4.0 (Ubuntu 20.04)
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev || \
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run QA
# Skip lint until golangci-lint supports Go 1.25
run: core go qa --skip=lint
- name: Verify build
run: |
core build --targets=linux/amd64 --ci
dist/linux_amd64/core --version

View file

@ -1,32 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
name: "CodeQL: Pull Request"
on:
pull_request:
branches: [dev, main]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"

View file

@ -1,32 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "CodeQL: Push"
on:
push:
branches: [dev, main]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"

View file

@ -1,32 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
name: "CodeQL: Schedule"
on:
schedule:
- cron: "0 6 * * 1"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: go
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:go"

View file

@ -1,30 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
name: "Code Scanning: Pull Request"
on:
pull_request:
branches: ["dev"]
jobs:
CodeQL:
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: "Checkout Repository"
uses: actions/checkout@v6
- name: "Initialize CodeQL"
uses: github/codeql-action/init@v4
with:
languages: go,javascript,typescript
- name: "Autobuild"
uses: github/codeql-action/autobuild@v4
- name: "Perform CodeQL Analysis"
uses: github/codeql-action/analyze@v4

View file

@ -1,30 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "Code Scanning: Push"
on:
push:
branches: ["dev"]
jobs:
CodeQL:
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: "Checkout Repository"
uses: actions/checkout@v6
- name: "Initialize CodeQL"
uses: github/codeql-action/init@v4
with:
languages: go,javascript,typescript
- name: "Autobuild"
uses: github/codeql-action/autobuild@v4
- name: "Perform CodeQL Analysis"
uses: github/codeql-action/analyze@v4

View file

@ -1,30 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
name: "Code Scanning: Schedule"
on:
schedule:
- cron: "0 2 * * 1-5"
jobs:
CodeQL:
runs-on: ubuntu-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: "Checkout Repository"
uses: actions/checkout@v6
- name: "Initialize CodeQL"
uses: github/codeql-action/init@v4
with:
languages: go,javascript,typescript
- name: "Autobuild"
uses: github/codeql-action/autobuild@v4
- name: "Perform CodeQL Analysis"
uses: github/codeql-action/analyze@v4

View file

@ -1,46 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
name: "Coverage: Manual"
on:
workflow_dispatch:
env:
CORE_VERSION: dev
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run coverage
run: core go cov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage report
uses: actions/upload-artifact@v6
with:
name: coverage-report
path: coverage.txt

View file

@ -1,47 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
name: "Coverage: Pull Request"
on:
pull_request:
branches: [dev, main]
env:
CORE_VERSION: dev
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run coverage
run: core go cov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage report
uses: actions/upload-artifact@v6
with:
name: coverage-report
path: coverage.txt

View file

@ -1,47 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "Coverage: Push"
on:
push:
branches: [dev, main]
env:
CORE_VERSION: dev
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run coverage
run: core go cov
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage report
uses: actions/upload-artifact@v6
with:
name: coverage-report
path: coverage.txt

View file

@ -1,54 +0,0 @@
name: Coverage
on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
workflow_dispatch:
permissions:
contents: read
env:
CORE_VERSION: dev
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
- name: Install system dependencies
run: |
sudo apt-get update
# Try 4.1 first (Ubuntu 22.04+), fall back to 4.0 (Ubuntu 20.04)
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev || \
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
- name: Build core CLI
run: |
go build -ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=${{ env.CORE_VERSION }}" -o /usr/local/bin/core .
core --version
- name: Generate code
run: go generate ./internal/cmd/updater/...
- name: Run coverage
run: core go cov --output coverage.txt --threshold 40 --branch-threshold 35
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage report
uses: actions/upload-artifact@v6
with:
name: coverage-report
path: coverage.txt

View file

@ -1,89 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch
name: "PR Build: Manual"
on:
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to build'
required: true
type: number
permissions:
contents: write
pull-requests: read
env:
NEXT_VERSION: "0.0.4"
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
platform: linux/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- name: Build
uses: host-uk/build@v3
with:
build-name: core
build-platform: ${{ matrix.platform }}
build: true
package: true
sign: false
draft-release:
needs: build
runs-on: ubuntu-latest
env:
PR_NUM: ${{ inputs.pr_number }}
PR_SHA: ${{ github.sha }}
steps:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ env.NEXT_VERSION }}.pr.${PR_NUM}.bid.${{ github.run_id }}"
# Delete existing draft for this PR if it exists
gh release delete "$TAG" -y 2>/dev/null || true
git push origin ":refs/tags/$TAG" 2>/dev/null || true
gh release create "$TAG" \
--title "Draft: PR #${PR_NUM}" \
--notes "Draft build for PR #${PR_NUM}.
**Version:** $TAG
**PR:** #${PR_NUM}
**Commit:** ${PR_SHA}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Draft
This is a draft build for testing PR changes before merge.
Not intended for production use.
Build artifacts available for download and testing.
" \
--draft \
--prerelease \
release/*

View file

@ -1,89 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request
name: "PR Build: Pull Request"
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: write
pull-requests: read
env:
NEXT_VERSION: "0.0.4"
jobs:
build:
# Only build if PR is from the same repo (not forks)
if: github.event.pull_request.head.repo.full_name == github.repository
strategy:
matrix:
include:
- os: ubuntu-latest
platform: linux/amd64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Build
uses: host-uk/build@v3
with:
build-name: core
build-platform: ${{ matrix.platform }}
build: true
package: true
sign: false
draft-release:
needs: build
runs-on: ubuntu-latest
env:
PR_NUM: ${{ github.event.pull_request.number }}
PR_SHA: ${{ github.event.pull_request.head.sha }}
steps:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="v${{ env.NEXT_VERSION }}.pr.${PR_NUM}.bid.${{ github.run_id }}"
# Delete existing draft for this PR if it exists
gh release delete "$TAG" -y 2>/dev/null || true
git push origin ":refs/tags/$TAG" 2>/dev/null || true
gh release create "$TAG" \
--title "Draft: PR #${PR_NUM}" \
--notes "Draft build for PR #${PR_NUM}.
**Version:** $TAG
**PR:** #${PR_NUM}
**Commit:** ${PR_SHA}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Draft
This is a draft build for testing PR changes before merge.
Not intended for production use.
Build artifacts available for download and testing.
" \
--draft \
--prerelease \
release/*

View file

@ -1,113 +0,0 @@
name: PR Build
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to build'
required: true
type: number
permissions:
contents: write
pull-requests: read
env:
# Next version - update when releasing
NEXT_VERSION: "0.0.4"
jobs:
build:
# Only build if PR is from the same repo (not forks) or manually triggered
if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'workflow_dispatch'
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# GUI build disabled until build action supports Wails v3
# - name: Wails Build Action
# uses: host-uk/build@v4.0.0
# with:
# build-name: core
# build-platform: ${{ matrix.goos }}/${{ matrix.goarch }}
# build: true
# package: true
# sign: false
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Build CLI
run: go build -o ./bin/core .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: core-${{ matrix.goos }}-${{ matrix.goarch }}
path: ./bin/core
draft-release:
needs: build
runs-on: ubuntu-latest
env:
# Safe: PR number is numeric, not user-controlled string
PR_NUM: ${{ github.event.pull_request.number || inputs.pr_number }}
PR_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
steps:
- uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Use dots for build metadata (semver v1 compatible)
TAG="v${{ env.NEXT_VERSION }}.pr.${PR_NUM}.bid.${{ github.run_id }}"
# Delete existing draft for this PR if it exists
gh release delete "$TAG" -y 2>/dev/null || true
git push origin ":refs/tags/$TAG" 2>/dev/null || true
gh release create "$TAG" \
--title "Draft: PR #${PR_NUM}" \
--notes "Draft build for PR #${PR_NUM}.
**Version:** $TAG
**PR:** #${PR_NUM}
**Commit:** ${PR_SHA}
**Built:** $(date -u +'%Y-%m-%d %H:%M:%S UTC')
**Run:** ${{ github.run_id }}
## Channel: Draft
This is a draft build for testing PR changes before merge.
Not intended for production use.
Build artifacts available for download and testing.
" \
--draft \
--prerelease \
release/*

View file

@ -1,45 +0,0 @@
name: PR Gate
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
pull-requests: read
jobs:
org-gate:
runs-on: ubuntu-latest
steps:
- name: Check org membership or approval label
uses: actions/github-script@v7
with:
script: |
const author = context.payload.pull_request.user.login;
const association = context.payload.pull_request.author_association;
// Trusted accounts
const trustedAuthors = ['google-labs-jules[bot]', 'Snider'];
if (trustedAuthors.includes(author)) {
core.info(`${author} is trusted — gate passed`);
return;
}
// Check author association
const trustedAssociations = ['MEMBER', 'OWNER', 'COLLABORATOR'];
if (trustedAssociations.includes(association)) {
core.info(`${author} is ${association} — gate passed`);
return;
}
// Check for external-approved label
const labels = context.payload.pull_request.labels.map(l => l.name);
if (labels.includes('external-approved')) {
core.info('external-approved label present — gate passed');
return;
}
core.setFailed(
`External PR from ${author} requires an org member to add the "external-approved" label before merge.`
);

View file

@ -1,454 +0,0 @@
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push
name: "Release: Tag Push"
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: write
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-latest
goos: linux
goarch: arm64
- os: macos-latest
goos: darwin
goarch: arm64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
steps:
- uses: actions/checkout@v6
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Build CLI
shell: bash
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
BINARY="core${EXT}"
ARCHIVE_PREFIX="core-${GOOS}-${GOARCH}"
APP_VERSION="${GITHUB_REF_NAME#v}"
go build -ldflags "-s -w -X github.com/host-uk/core/pkg/cli.AppVersion=${APP_VERSION}" -o "./bin/${BINARY}" .
# Create tar.gz for Homebrew (non-Windows)
if [ "$GOOS" != "windows" ]; then
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "${BINARY}"
fi
# Create zip for Scoop (Windows)
if [ "$GOOS" = "windows" ]; then
cd ./bin && zip "${ARCHIVE_PREFIX}.zip" "${BINARY}" && cd ..
fi
# Rename raw binary to platform-specific name for release
mv "./bin/${BINARY}" "./bin/${ARCHIVE_PREFIX}${EXT}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: core-${{ matrix.goos }}-${{ matrix.goarch }}
path: ./bin/core-*
build-ide:
strategy:
matrix:
include:
- os: macos-latest
goos: darwin
goarch: arm64
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: windows-latest
goos: windows
goarch: amd64
runs-on: ${{ matrix.os }}
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
defaults:
run:
working-directory: internal/core-ide
steps:
- uses: actions/checkout@v6
- name: Setup Go
uses: host-uk/build/actions/setup/go@v4.0.0
with:
go-version: "1.25"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Wails CLI
run: go install github.com/wailsapp/wails/v3/cmd/wails3@latest
- name: Install frontend dependencies
working-directory: internal/core-ide/frontend
run: npm ci
- name: Generate bindings
run: wails3 generate bindings -f '-tags production' -clean=false -ts -i
- name: Build frontend
working-directory: internal/core-ide/frontend
run: npm run build
- name: Install Linux dependencies
if: matrix.goos == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev
- name: Build IDE
shell: bash
run: |
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
BINARY="core-ide${EXT}"
ARCHIVE_PREFIX="core-ide-${GOOS}-${GOARCH}"
BUILD_FLAGS="-tags production -trimpath -buildvcs=false"
if [ "$GOOS" = "windows" ]; then
# Windows: no CGO, use windowsgui linker flag
export CGO_ENABLED=0
LDFLAGS="-w -s -H windowsgui"
# Generate Windows syso resource
cd build
wails3 generate syso -arch ${GOARCH} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_${GOARCH}.syso
cd ..
elif [ "$GOOS" = "darwin" ]; then
export CGO_ENABLED=1
export CGO_CFLAGS="-mmacosx-version-min=10.15"
export CGO_LDFLAGS="-mmacosx-version-min=10.15"
export MACOSX_DEPLOYMENT_TARGET="10.15"
LDFLAGS="-w -s"
else
export CGO_ENABLED=1
LDFLAGS="-w -s"
fi
go build ${BUILD_FLAGS} -ldflags="${LDFLAGS}" -o "./bin/${BINARY}"
# Clean up syso files
rm -f *.syso
# Package
if [ "$GOOS" = "darwin" ]; then
# Create .app bundle
mkdir -p "./bin/Core IDE.app/Contents/"{MacOS,Resources}
cp build/darwin/icons.icns "./bin/Core IDE.app/Contents/Resources/"
cp "./bin/${BINARY}" "./bin/Core IDE.app/Contents/MacOS/"
cp build/darwin/Info.plist "./bin/Core IDE.app/Contents/"
codesign --force --deep --sign - "./bin/Core IDE.app"
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "Core IDE.app"
elif [ "$GOOS" = "windows" ]; then
cd ./bin && zip "${ARCHIVE_PREFIX}.zip" "${BINARY}" && cd ..
else
tar czf "./bin/${ARCHIVE_PREFIX}.tar.gz" -C ./bin "${BINARY}"
fi
# Rename raw binary
mv "./bin/${BINARY}" "./bin/${ARCHIVE_PREFIX}${EXT}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: core-ide-${{ matrix.goos }}-${{ matrix.goarch }}
path: internal/core-ide/bin/core-ide-*
release:
needs: [build, build-ide]
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Set version
id: version
run: echo "version=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Prepare release files
run: |
mkdir -p release
cp dist/* release/ 2>/dev/null || true
ls -la release/
- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ github.ref_name }}
run: |
gh release create "$TAG_NAME" \
--title "Release $TAG_NAME" \
--generate-notes \
release/*
update-tap:
needs: release
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
for f in *.tar.gz; do
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
done
echo "=== Checksums ==="
cat *.sha256
- name: Update Homebrew formula
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ needs.release.outputs.version }}
run: |
# Strip leading 'v' for formula version
FORMULA_VERSION="${VERSION#v}"
# Read checksums
DARWIN_ARM64=$(cat dist/core-darwin-arm64.tar.gz.sha256)
LINUX_AMD64=$(cat dist/core-linux-amd64.tar.gz.sha256)
LINUX_ARM64=$(cat dist/core-linux-arm64.tar.gz.sha256)
# Clone tap repo (configure auth for push)
gh repo clone host-uk/homebrew-tap /tmp/tap -- --depth=1
cd /tmp/tap
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/host-uk/homebrew-tap.git"
cd -
mkdir -p /tmp/tap/Formula
# Write formula
cat > /tmp/tap/Formula/core.rb << FORMULA
# typed: false
# frozen_string_literal: true
class Core < Formula
desc "Host UK development CLI"
homepage "https://github.com/host-uk/core"
version "${FORMULA_VERSION}"
license "EUPL-1.2"
on_macos do
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-darwin-arm64.tar.gz"
sha256 "${DARWIN_ARM64}"
end
on_linux do
if Hardware::CPU.arm?
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-linux-arm64.tar.gz"
sha256 "${LINUX_ARM64}"
else
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-linux-amd64.tar.gz"
sha256 "${LINUX_AMD64}"
end
end
def install
bin.install "core"
end
test do
system "\#{bin}/core", "--version"
end
end
FORMULA
# Remove leading whitespace from heredoc
sed -i 's/^ //' /tmp/tap/Formula/core.rb
# Read IDE checksums (may not exist if build-ide failed)
IDE_DARWIN_ARM64=$(cat dist/core-ide-darwin-arm64.tar.gz.sha256 2>/dev/null || echo "")
IDE_LINUX_AMD64=$(cat dist/core-ide-linux-amd64.tar.gz.sha256 2>/dev/null || echo "")
# Write core-ide Formula (Linux binary)
if [ -n "${IDE_LINUX_AMD64}" ]; then
cat > /tmp/tap/Formula/core-ide.rb << FORMULA
# typed: false
# frozen_string_literal: true
class CoreIde < Formula
desc "Host UK desktop development environment"
homepage "https://github.com/host-uk/core"
version "${FORMULA_VERSION}"
license "EUPL-1.2"
on_linux do
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-linux-amd64.tar.gz"
sha256 "${IDE_LINUX_AMD64}"
end
def install
bin.install "core-ide"
end
end
FORMULA
sed -i 's/^ //' /tmp/tap/Formula/core-ide.rb
fi
# Write core-ide Cask (macOS .app bundle)
if [ -n "${IDE_DARWIN_ARM64}" ]; then
mkdir -p /tmp/tap/Casks
cat > /tmp/tap/Casks/core-ide.rb << CASK
cask "core-ide" do
version "${FORMULA_VERSION}"
sha256 "${IDE_DARWIN_ARM64}"
url "https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-darwin-arm64.tar.gz"
name "Core IDE"
desc "Host UK desktop development environment"
homepage "https://github.com/host-uk/core"
app "Core IDE.app"
end
CASK
sed -i 's/^ //' /tmp/tap/Casks/core-ide.rb
fi
cd /tmp/tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git diff --cached --quiet && echo "No changes to tap" && exit 0
git commit -m "Update core to ${FORMULA_VERSION}"
git push
update-scoop:
needs: release
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: dist
merge-multiple: true
- name: Generate checksums
run: |
cd dist
for f in *.zip; do
[ -f "$f" ] || continue
sha256sum "$f" | awk '{print $1}' > "${f}.sha256"
done
echo "=== Checksums ==="
cat *.sha256 2>/dev/null || echo "No zip checksums"
- name: Update Scoop manifests
env:
GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ needs.release.outputs.version }}
run: |
# Strip leading 'v' for manifest version
MANIFEST_VERSION="${VERSION#v}"
# Read checksums
WIN_AMD64=$(cat dist/core-windows-amd64.zip.sha256 2>/dev/null || echo "")
IDE_WIN_AMD64=$(cat dist/core-ide-windows-amd64.zip.sha256 2>/dev/null || echo "")
# Clone scoop bucket
gh repo clone host-uk/scoop-bucket /tmp/scoop -- --depth=1
cd /tmp/scoop
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/host-uk/scoop-bucket.git"
# Write core.json manifest
cat > core.json << 'MANIFEST'
{
"version": "VERSION_PLACEHOLDER",
"description": "Host UK development CLI",
"homepage": "https://github.com/host-uk/core",
"license": "EUPL-1.2",
"architecture": {
"64bit": {
"url": "URL_PLACEHOLDER",
"hash": "HASH_PLACEHOLDER",
"bin": "core.exe"
}
},
"checkver": "github",
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/host-uk/core/releases/download/v$version/core-windows-amd64.zip"
}
}
}
}
MANIFEST
sed -i "s|VERSION_PLACEHOLDER|${MANIFEST_VERSION}|g" core.json
sed -i "s|URL_PLACEHOLDER|https://github.com/host-uk/core/releases/download/${VERSION}/core-windows-amd64.zip|g" core.json
sed -i "s|HASH_PLACEHOLDER|${WIN_AMD64}|g" core.json
sed -i 's/^ //' core.json
# Write core-ide.json manifest
if [ -n "${IDE_WIN_AMD64}" ]; then
cat > core-ide.json << 'MANIFEST'
{
"version": "VERSION_PLACEHOLDER",
"description": "Host UK desktop development environment",
"homepage": "https://github.com/host-uk/core",
"license": "EUPL-1.2",
"architecture": {
"64bit": {
"url": "URL_PLACEHOLDER",
"hash": "HASH_PLACEHOLDER",
"bin": "core-ide.exe"
}
},
"checkver": "github",
"autoupdate": {
"architecture": {
"64bit": {
"url": "https://github.com/host-uk/core/releases/download/v$version/core-ide-windows-amd64.zip"
}
}
}
}
MANIFEST
sed -i "s|VERSION_PLACEHOLDER|${MANIFEST_VERSION}|g" core-ide.json
sed -i "s|URL_PLACEHOLDER|https://github.com/host-uk/core/releases/download/${VERSION}/core-ide-windows-amd64.zip|g" core-ide.json
sed -i "s|HASH_PLACEHOLDER|${IDE_WIN_AMD64}|g" core-ide.json
sed -i 's/^ //' core-ide.json
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .
git diff --cached --quiet && echo "No changes to scoop bucket" && exit 0
git commit -m "Update core to ${MANIFEST_VERSION}"
git push

3
.gitignore vendored
View file

@ -15,10 +15,9 @@ coverage.html
bin/
dist/
tasks
/cli
/core
/i18n-validate
cmd/bugseti/bugseti
internal/core-ide/core-ide
.angular/
patch_cov.*

View file

@ -9,9 +9,9 @@ steps:
- go mod download
- >-
go build
-ldflags "-X github.com/host-uk/core/pkg/cli.AppVersion=ci
-X github.com/host-uk/core/pkg/cli.BuildCommit=${CI_COMMIT_SHA:0:7}
-X github.com/host-uk/core/pkg/cli.BuildDate=$(date -u +%Y%m%d)"
-ldflags "-X forge.lthn.ai/core/cli/pkg/cli.AppVersion=ci
-X forge.lthn.ai/core/cli/pkg/cli.BuildCommit=${CI_COMMIT_SHA:0:7}
-X forge.lthn.ai/core/cli/pkg/cli.BuildDate=$(date -u +%Y%m%d)"
-o ./bin/core .
- ./bin/core --version

View file

@ -1,14 +1,14 @@
# Core
[![codecov](https://codecov.io/gh/host-uk/core/branch/dev/graph/badge.svg)](https://codecov.io/gh/host-uk/core)
[![Go Test Coverage](https://github.com/host-uk/core/actions/workflows/coverage.yml/badge.svg)](https://github.com/host-uk/core/actions/workflows/coverage.yml)
[![Code Scanning](https://github.com/host-uk/core/actions/workflows/codescan.yml/badge.svg)](https://github.com/host-uk/core/actions/workflows/codescan.yml)
[![Go Test Coverage](https://forge.lthn.ai/core/cli/actions/workflows/coverage.yml/badge.svg)](https://forge.lthn.ai/core/cli/actions/workflows/coverage.yml)
[![Code Scanning](https://forge.lthn.ai/core/cli/actions/workflows/codescan.yml/badge.svg)](https://forge.lthn.ai/core/cli/actions/workflows/codescan.yml)
[![Go Version](https://img.shields.io/github/go-mod/go-version/host-uk/core)](https://go.dev/)
[![License](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://opensource.org/licenses/EUPL-1.2)
Core is a Web3 Framework, written in Go using Wails.io to replace Electron and the bloat of browsers that, at their core, still live in their mum's basement.
- Repo: https://github.com/host-uk/core
- Repo: https://forge.lthn.ai/core/cli
## Vision
@ -26,7 +26,7 @@ Core is an **opinionated Web3 desktop application framework** providing:
```bash
# 1. Install Core
go install github.com/host-uk/core/cmd/core@latest
go install forge.lthn.ai/core/cli/cmd/core@latest
# 2. Verify environment
core doctor
@ -44,7 +44,7 @@ For more details, see the [User Guide](docs/user-guide.md).
## Framework Quick Start (Go)
```go
import core "github.com/host-uk/core/pkg/framework/core"
import core "forge.lthn.ai/core/cli/pkg/framework/core"
app, err := core.New(
core.WithServiceLock(),
@ -210,7 +210,7 @@ app.RegisterService(application.NewService(coreService)) // Only Core is regist
**Currently exposed** (see `cmd/core-gui/public/bindings/`):
```typescript
// From frontend:
import { ACTION, Config, Service } from './bindings/github.com/host-uk/core/pkg/core'
import { ACTION, Config, Service } from './bindings/forge.lthn.ai/core/cli/pkg/core'
ACTION(msg) // Broadcast IPC message
Config() // Get config service reference
@ -259,7 +259,7 @@ Sub-services are accessed via Core's **IPC/ACTION system**, not direct Wails bin
```typescript
// Frontend calls Core.ACTION() with typed messages
import { ACTION } from './bindings/github.com/host-uk/core/pkg/core'
import { ACTION } from './bindings/forge.lthn.ai/core/cli/pkg/core'
// Open a window
ACTION({ action: "display.open_window", name: "settings", options: { Title: "Settings", Width: 800 } })

View file

@ -15,7 +15,7 @@ vars:
SEMVER_PRERELEASE:
sh: '[ "{{.SEMVER_COMMITS}}" = "0" ] && echo "" || echo "dev.{{.SEMVER_COMMITS}}"'
# ldflags
PKG: "github.com/host-uk/core/pkg/cli"
PKG: "forge.lthn.ai/core/go/pkg/cli"
LDFLAGS_BASE: >-
-X {{.PKG}}.AppVersion={{.SEMVER_VERSION}}
-X {{.PKG}}.BuildCommit={{.SEMVER_COMMIT}}

View file

@ -7,9 +7,9 @@ import (
"path/filepath"
"strings"
"forge.lthn.ai/core/cli/pkg/agentci"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/config"
"forge.lthn.ai/core/go/pkg/agentci"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/config"
)
// AddAgentCommands registers the 'agent' subcommand group under 'ai'.

View file

@ -3,7 +3,7 @@
package ai
import (
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/go/pkg/cli"
)
// Style aliases from shared package

View file

@ -13,9 +13,9 @@
package ai
import (
ragcmd "forge.lthn.ai/core/cli/internal/cmd/rag"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/i18n"
ragcmd "forge.lthn.ai/core/cli/cmd/rag"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
)
func init() {

View file

@ -16,8 +16,8 @@ import (
"syscall"
"time"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/log"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/log"
)
// AddDispatchCommands registers the 'dispatch' subcommand group under 'ai'.

View file

@ -10,9 +10,9 @@ import (
"strings"
"time"
"forge.lthn.ai/core/cli/pkg/agentic"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/agentic"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
)
// task:commit command flags

View file

@ -7,9 +7,9 @@ import (
"fmt"
"time"
"forge.lthn.ai/core/cli/pkg/ai"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/ai"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
)
var (

View file

@ -7,9 +7,9 @@ import (
"text/tabwriter"
"time"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/config"
"forge.lthn.ai/core/cli/pkg/ratelimit"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/config"
"forge.lthn.ai/core/go/pkg/ratelimit"
)
// AddRateLimitCommands registers the 'ratelimits' subcommand group under 'ai'.

View file

@ -9,10 +9,10 @@ import (
"strings"
"time"
"forge.lthn.ai/core/cli/pkg/agentic"
"forge.lthn.ai/core/cli/pkg/ai"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/agentic"
"forge.lthn.ai/core/go/pkg/ai"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
)
// tasks command flags

View file

@ -6,10 +6,10 @@ import (
"context"
"time"
"forge.lthn.ai/core/cli/pkg/agentic"
"forge.lthn.ai/core/cli/pkg/ai"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/agentic"
"forge.lthn.ai/core/go/pkg/ai"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/i18n"
)
// task:update command flags

View file

@ -3,8 +3,8 @@ package ai
import (
"context"
"forge.lthn.ai/core/cli/pkg/log"
"forge.lthn.ai/core/cli/pkg/ratelimit"
"forge.lthn.ai/core/go/pkg/log"
"forge.lthn.ai/core/go/pkg/ratelimit"
)
// executeWithRateLimit wraps an agent execution with rate limiting logic.

View file

@ -1,31 +0,0 @@
# Build output
bin/
frontend/dist/
frontend/node_modules/
frontend/.angular/
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Go
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test
*.test
*.out
coverage/
# Wails
wails.json

View file

@ -1,186 +0,0 @@
# BugSETI
**Distributed Bug Fixing - like SETI@home but for code**
BugSETI is a system tray application that helps developers contribute to open source by fixing bugs in their spare CPU cycles. It fetches issues from GitHub repositories, prepares context using AI, and guides you through the fix-and-submit workflow.
## Features
- **System Tray Integration**: Runs quietly in the background, ready when you are
- **Issue Queue**: Automatically fetches and queues issues from configured repositories
- **AI Context Seeding**: Prepares relevant code context for each issue using pattern matching
- **Workbench UI**: Full-featured interface for reviewing issues and submitting fixes
- **Automated PR Submission**: Streamlined workflow from fix to pull request
- **Stats & Leaderboard**: Track your contributions and compete with the community
## Installation
### From Source
```bash
# Clone the repository
git clone https://github.com/host-uk/core.git
cd core
# Build BugSETI
task bugseti:build
# The binary will be in build/bin/bugseti
```
### Prerequisites
- Go 1.25 or later
- Node.js 18+ and npm (for frontend)
- GitHub CLI (`gh`) authenticated
- Chrome/Chromium (optional, for webview features)
## Configuration
On first launch, BugSETI will show an onboarding wizard to configure:
1. **GitHub Token**: For fetching issues and submitting PRs
2. **Repositories**: Which repos to fetch issues from
3. **Filters**: Issue labels, difficulty levels, languages
4. **Notifications**: How to alert you about new issues
### Configuration File
Settings are stored in `~/.config/bugseti/config.json`:
```json
{
"github_token": "ghp_...",
"repositories": [
"host-uk/core",
"example/repo"
],
"filters": {
"labels": ["good first issue", "help wanted", "bug"],
"languages": ["go", "typescript"],
"max_age_days": 30
},
"notifications": {
"enabled": true,
"sound": true
},
"fetch_interval_minutes": 30
}
```
## Usage
### Starting BugSETI
```bash
# Run the application
./bugseti
# Or use task runner
task bugseti:run
```
The app will appear in your system tray. Click the icon to see the quick menu or open the workbench.
### Workflow
1. **Browse Issues**: Click the tray icon to see available issues
2. **Select an Issue**: Choose one to work on from the queue
3. **Review Context**: BugSETI shows relevant files and patterns
4. **Fix the Bug**: Make your changes in your preferred editor
5. **Submit PR**: Use the workbench to create and submit your pull request
### Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| `Ctrl+Shift+B` | Open workbench |
| `Ctrl+Shift+N` | Next issue |
| `Ctrl+Shift+S` | Submit PR |
## Architecture
```
cmd/bugseti/
main.go # Application entry point
tray.go # System tray service
icons/ # Tray icons (light/dark/template)
frontend/ # Angular frontend
src/
app/
tray/ # Tray panel component
workbench/ # Main workbench
settings/ # Settings panel
onboarding/ # First-run wizard
internal/bugseti/
config.go # Configuration service
fetcher.go # GitHub issue fetcher
queue.go # Issue queue management
seeder.go # Context seeding via AI
submit.go # PR submission
notify.go # Notification service
stats.go # Statistics tracking
```
## Contributing
We welcome contributions! Here's how to get involved:
### Development Setup
```bash
# Install dependencies
cd cmd/bugseti/frontend
npm install
# Run in development mode
task bugseti:dev
```
### Running Tests
```bash
# Go tests
go test ./cmd/bugseti/... ./internal/bugseti/...
# Frontend tests
cd cmd/bugseti/frontend
npm test
```
### Submitting Changes
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/my-feature`
3. Make your changes and add tests
4. Run the test suite: `task test`
5. Submit a pull request
### Code Style
- Go: Follow standard Go conventions, run `go fmt`
- TypeScript/Angular: Follow Angular style guide
- Commits: Use conventional commit messages
## Roadmap
- [ ] Auto-update mechanism
- [ ] Team/organization support
- [ ] Integration with more issue trackers (GitLab, Jira)
- [ ] AI-assisted code review
- [ ] Mobile companion app
## License
MIT License - see [LICENSE](../../LICENSE) for details.
## Acknowledgments
- Inspired by SETI@home and distributed computing projects
- Built with [Wails v3](https://wails.io/) for native desktop integration
- Uses [Angular](https://angular.io/) for the frontend
---
**Happy Bug Hunting!**

View file

@ -1,134 +0,0 @@
version: '3'
includes:
common: ./build/Taskfile.yml
windows: ./build/windows/Taskfile.yml
darwin: ./build/darwin/Taskfile.yml
linux: ./build/linux/Taskfile.yml
vars:
APP_NAME: "bugseti"
BIN_DIR: "bin"
VITE_PORT: '{{.WAILS_VITE_PORT | default 9246}}'
tasks:
build:
summary: Builds the application
cmds:
- task: "{{OS}}:build"
package:
summary: Packages a production build of the application
cmds:
- task: "{{OS}}:package"
run:
summary: Runs the application
cmds:
- task: "{{OS}}:run"
dev:
summary: Runs the application in development mode
cmds:
- wails3 dev -config ./build/config.yml -port {{.VITE_PORT}}
build:all:
summary: Builds for all platforms
cmds:
- task: darwin:build
vars:
PRODUCTION: "true"
- task: linux:build
vars:
PRODUCTION: "true"
- task: windows:build
vars:
PRODUCTION: "true"
package:all:
summary: Packages for all platforms
cmds:
- task: darwin:package
- task: linux:package
- task: windows:package
clean:
summary: Cleans build artifacts
cmds:
- rm -rf bin/
- rm -rf frontend/dist/
- rm -rf frontend/node_modules/
# Release targets
release:stable:
summary: Creates a stable release tag
desc: |
Creates a stable release tag (bugseti-vX.Y.Z).
Usage: task release:stable VERSION=1.0.0
preconditions:
- sh: '[ -n "{{.VERSION}}" ]'
msg: "VERSION is required. Usage: task release:stable VERSION=1.0.0"
cmds:
- git tag -a "bugseti-v{{.VERSION}}" -m "BugSETI v{{.VERSION}} stable release"
- echo "Created tag bugseti-v{{.VERSION}}"
- echo "To push: git push origin bugseti-v{{.VERSION}}"
release:beta:
summary: Creates a beta release tag
desc: |
Creates a beta release tag (bugseti-vX.Y.Z-beta.N).
Usage: task release:beta VERSION=1.0.0 BETA=1
preconditions:
- sh: '[ -n "{{.VERSION}}" ]'
msg: "VERSION is required. Usage: task release:beta VERSION=1.0.0 BETA=1"
- sh: '[ -n "{{.BETA}}" ]'
msg: "BETA number is required. Usage: task release:beta VERSION=1.0.0 BETA=1"
cmds:
- git tag -a "bugseti-v{{.VERSION}}-beta.{{.BETA}}" -m "BugSETI v{{.VERSION}} beta {{.BETA}}"
- echo "Created tag bugseti-v{{.VERSION}}-beta.{{.BETA}}"
- echo "To push: git push origin bugseti-v{{.VERSION}}-beta.{{.BETA}}"
release:nightly:
summary: Creates a nightly release tag
desc: Creates a nightly release tag (bugseti-nightly-YYYYMMDD)
vars:
DATE:
sh: date -u +%Y%m%d
cmds:
- git tag -a "bugseti-nightly-{{.DATE}}" -m "BugSETI nightly build {{.DATE}}"
- echo "Created tag bugseti-nightly-{{.DATE}}"
- echo "To push: git push origin bugseti-nightly-{{.DATE}}"
release:push:
summary: Pushes the latest release tag
desc: |
Pushes the most recent bugseti-* tag to origin.
Usage: task release:push
vars:
TAG:
sh: git tag -l 'bugseti-*' | sort -V | tail -1
preconditions:
- sh: '[ -n "{{.TAG}}" ]'
msg: "No bugseti-* tags found"
cmds:
- echo "Pushing tag {{.TAG}}..."
- git push origin {{.TAG}}
- echo "Tag {{.TAG}} pushed. GitHub Actions will build and release."
release:list:
summary: Lists all BugSETI release tags
cmds:
- echo "=== BugSETI Release Tags ==="
- git tag -l 'bugseti-*' | sort -V
version:
summary: Shows current version info
cmds:
- |
echo "=== BugSETI Version Info ==="
echo "Latest stable tag:"
git tag -l 'bugseti-v*' | grep -v beta | sort -V | tail -1 || echo " (none)"
echo "Latest beta tag:"
git tag -l 'bugseti-v*-beta.*' | sort -V | tail -1 || echo " (none)"
echo "Latest nightly tag:"
git tag -l 'bugseti-nightly-*' | sort -V | tail -1 || echo " (none)"

View file

@ -1,90 +0,0 @@
version: '3'
tasks:
go:mod:tidy:
summary: Runs `go mod tidy`
internal: true
cmds:
- go mod tidy
install:frontend:deps:
summary: Install frontend dependencies
dir: frontend
sources:
- package.json
- package-lock.json
generates:
- node_modules/*
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"
cmds:
- npm install
build:frontend:
label: build:frontend (PRODUCTION={{.PRODUCTION}})
summary: Build the frontend project
dir: frontend
sources:
- "**/*"
generates:
- dist/**/*
deps:
- task: install:frontend:deps
- task: generate:bindings
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
cmds:
- npm run {{.BUILD_COMMAND}} -q
env:
PRODUCTION: '{{.PRODUCTION | default "false"}}'
vars:
BUILD_COMMAND: '{{if eq .PRODUCTION "true"}}build{{else}}build:dev{{end}}'
generate:bindings:
label: generate:bindings (BUILD_FLAGS={{.BUILD_FLAGS}})
summary: Generates bindings for the frontend
deps:
- task: go:mod:tidy
sources:
- "**/*.[jt]s"
- exclude: frontend/**/*
- frontend/bindings/**/*
- "**/*.go"
- go.mod
- go.sum
generates:
- frontend/bindings/**/*
cmds:
- wails3 generate bindings -f '{{.BUILD_FLAGS}}' -clean=false -ts -i
generate:icons:
summary: Generates Windows `.ico` and Mac `.icns` files from an image
dir: build
sources:
- "appicon.png"
generates:
- "darwin/icons.icns"
- "windows/icon.ico"
cmds:
- wails3 generate icons -input appicon.png -macfilename darwin/icons.icns -windowsfilename windows/icon.ico
dev:frontend:
summary: Runs the frontend in development mode
dir: frontend
deps:
- task: install:frontend:deps
cmds:
- npm run dev -- --port {{.VITE_PORT}}
vars:
VITE_PORT: '{{.VITE_PORT | default "5173"}}'
update:build-assets:
summary: Updates the build assets
dir: build
preconditions:
- sh: '[ -n "{{.APP_NAME}}" ]'
msg: "APP_NAME variable is required"
cmds:
- wails3 update build-assets -name "{{.APP_NAME}}" -binaryname "{{.APP_NAME}}" -config config.yml -dir .

View file

@ -1,38 +0,0 @@
# BugSETI Wails v3 Build Configuration
version: '3'
# Build metadata
info:
companyName: "Lethean"
productName: "BugSETI"
productIdentifier: "io.lethean.bugseti"
description: "Distributed Bug Fixing - like SETI@home but for code"
copyright: "Copyright 2026 Lethean"
comments: "Distributed OSS bug fixing application"
version: "0.1.0"
# Dev mode configuration
dev_mode:
root_path: .
log_level: warn
debounce: 1000
ignore:
dir:
- .git
- node_modules
- frontend
- bin
file:
- .DS_Store
- .gitignore
- .gitkeep
watched_extension:
- "*.go"
git_ignore: true
executes:
- cmd: go build -buildvcs=false -gcflags=all=-l -o bin/bugseti .
type: blocking
- cmd: cd frontend && npx ng serve --port ${WAILS_FRONTEND_PORT:-9246}
type: background
- cmd: bin/bugseti
type: primary

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>BugSETI (Dev)</string>
<key>CFBundleExecutable</key>
<string>bugseti</string>
<key>CFBundleIdentifier</key>
<string>io.lethean.bugseti.dev</string>
<key>CFBundleVersion</key>
<string>0.1.0-dev</string>
<key>CFBundleGetInfoString</key>
<string>Distributed Bug Fixing - like SETI@home but for code (Development)</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0-dev</string>
<key>CFBundleIconFile</key>
<string>icons.icns</string>
<key>LSMinimumSystemVersion</key>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSUIElement</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>

View file

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>BugSETI</string>
<key>CFBundleExecutable</key>
<string>bugseti</string>
<key>CFBundleIdentifier</key>
<string>io.lethean.bugseti</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CFBundleGetInfoString</key>
<string>Distributed Bug Fixing - like SETI@home but for code</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<key>CFBundleIconFile</key>
<string>icons.icns</string>
<key>LSMinimumSystemVersion</key>
<string>10.15.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSUIElement</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.developer-tools</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>

View file

@ -1,84 +0,0 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Creates a production build of the application
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.OUTPUT}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
DEFAULT_OUTPUT: '{{.BIN_DIR}}/{{.APP_NAME}}'
OUTPUT: '{{ .OUTPUT | default .DEFAULT_OUTPUT }}'
env:
GOOS: darwin
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
CGO_CFLAGS: "-mmacosx-version-min=10.15"
CGO_LDFLAGS: "-mmacosx-version-min=10.15"
MACOSX_DEPLOYMENT_TARGET: "10.15"
PRODUCTION: '{{.PRODUCTION | default "false"}}'
build:universal:
summary: Builds darwin universal binary (arm64 + amd64)
deps:
- task: build
vars:
ARCH: amd64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-amd64"
PRODUCTION: '{{.PRODUCTION | default "true"}}'
- task: build
vars:
ARCH: arm64
OUTPUT: "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
PRODUCTION: '{{.PRODUCTION | default "true"}}'
cmds:
- lipo -create -output "{{.BIN_DIR}}/{{.APP_NAME}}" "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
- rm "{{.BIN_DIR}}/{{.APP_NAME}}-amd64" "{{.BIN_DIR}}/{{.APP_NAME}}-arm64"
package:
summary: Packages a production build of the application into a `.app` bundle
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:app:bundle
package:universal:
summary: Packages darwin universal binary (arm64 + amd64)
deps:
- task: build:universal
cmds:
- task: create:app:bundle
create:app:bundle:
summary: Creates an `.app` bundle
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents/MacOS
- cp build/darwin/Info.plist {{.BIN_DIR}}/{{.APP_NAME}}.app/Contents
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.app
run:
deps:
- task: build
cmds:
- mkdir -p {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/{MacOS,Resources}
- cp build/darwin/icons.icns {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Resources
- cp {{.BIN_DIR}}/{{.APP_NAME}} {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS
- cp build/darwin/Info.dev.plist {{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/Info.plist
- codesign --force --deep --sign - {{.BIN_DIR}}/{{.APP_NAME}}.dev.app
- '{{.BIN_DIR}}/{{.APP_NAME}}.dev.app/Contents/MacOS/{{.APP_NAME}}'

View file

@ -1,103 +0,0 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Builds the application for Linux
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: linux
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application for Linux
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:appimage
- task: create:deb
- task: create:rpm
create:appimage:
summary: Creates an AppImage
dir: build/linux/appimage
deps:
- task: build
vars:
PRODUCTION: "true"
- task: generate:dotdesktop
cmds:
- cp {{.APP_BINARY}} {{.APP_NAME}}
- cp ../../appicon.png {{.APP_NAME}}.png
- wails3 generate appimage -binary {{.APP_NAME}} -icon {{.ICON}} -desktopfile {{.DESKTOP_FILE}} -outputdir {{.OUTPUT_DIR}} -builddir {{.ROOT_DIR}}/build/linux/appimage/build
vars:
APP_NAME: '{{.APP_NAME}}'
APP_BINARY: '../../../bin/{{.APP_NAME}}'
ICON: '{{.APP_NAME}}.png'
DESKTOP_FILE: '../{{.APP_NAME}}.desktop'
OUTPUT_DIR: '../../../bin'
create:deb:
summary: Creates a deb package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:deb
create:rpm:
summary: Creates a rpm package
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: generate:dotdesktop
- task: generate:rpm
generate:deb:
summary: Creates a deb package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format deb -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:rpm:
summary: Creates a rpm package
cmds:
- wails3 tool package -name {{.APP_NAME}} -format rpm -config ./build/linux/nfpm/nfpm.yaml -out {{.ROOT_DIR}}/bin
generate:dotdesktop:
summary: Generates a `.desktop` file
dir: build
cmds:
- mkdir -p {{.ROOT_DIR}}/build/linux/appimage
- wails3 generate .desktop -name "{{.APP_NAME}}" -exec "{{.EXEC}}" -icon "{{.ICON}}" -outputfile {{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop -categories "{{.CATEGORIES}}"
vars:
APP_NAME: 'BugSETI'
EXEC: '{{.APP_NAME}}'
ICON: 'bugseti'
CATEGORIES: 'Development;'
OUTPUTFILE: '{{.ROOT_DIR}}/build/linux/{{.APP_NAME}}.desktop'
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}'

View file

@ -1,34 +0,0 @@
# nfpm configuration for BugSETI
name: "bugseti"
arch: "${GOARCH}"
platform: "linux"
version: "0.1.0"
section: "devel"
priority: "optional"
maintainer: "Lethean <developers@lethean.io>"
description: |
BugSETI - Distributed Bug Fixing
Like SETI@home but for code. Install the system tray app,
it pulls OSS issues from GitHub, AI prepares context,
you fix bugs, and it auto-submits PRs.
vendor: "Lethean"
homepage: "https://github.com/host-uk/core"
license: "MIT"
contents:
- src: ./bin/bugseti
dst: /usr/bin/bugseti
- src: ./build/linux/bugseti.desktop
dst: /usr/share/applications/bugseti.desktop
- src: ./build/appicon.png
dst: /usr/share/icons/hicolor/256x256/apps/bugseti.png
overrides:
deb:
dependencies:
- libwebkit2gtk-4.1-0
- libgtk-3-0
rpm:
dependencies:
- webkit2gtk4.1
- gtk3

View file

@ -1,49 +0,0 @@
version: '3'
includes:
common: ../Taskfile.yml
tasks:
build:
summary: Builds the application for Windows
deps:
- task: common:go:mod:tidy
- task: common:build:frontend
vars:
BUILD_FLAGS:
ref: .BUILD_FLAGS
PRODUCTION:
ref: .PRODUCTION
- task: common:generate:icons
cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}.exe
vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui"{{else}}-buildvcs=false -gcflags=all="-l"{{end}}'
env:
GOOS: windows
CGO_ENABLED: 1
GOARCH: '{{.ARCH | default ARCH}}'
PRODUCTION: '{{.PRODUCTION | default "false"}}'
package:
summary: Packages a production build of the application for Windows
deps:
- task: build
vars:
PRODUCTION: "true"
cmds:
- task: create:nsis
create:nsis:
summary: Creates an NSIS installer
cmds:
- wails3 tool package -name {{.APP_NAME}} -format nsis -config ./build/windows/nsis/installer.nsi -out {{.ROOT_DIR}}/bin
create:msi:
summary: Creates an MSI installer
cmds:
- wails3 tool package -name {{.APP_NAME}} -format msi -config ./build/windows/wix/main.wxs -out {{.ROOT_DIR}}/bin
run:
cmds:
- '{{.BIN_DIR}}/{{.APP_NAME}}.exe'

View file

@ -1,94 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"bugseti": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"standalone": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/bugseti",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "bugseti:build:production"
},
"development": {
"buildTarget": "bugseti:build:development"
}
},
"defaultConfiguration": "development"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,41 +0,0 @@
{
"name": "bugseti",
"version": "0.1.0",
"private": true,
"scripts": {
"ng": "ng",
"start": "ng serve",
"dev": "ng serve --configuration development",
"build": "ng build --configuration production",
"build:dev": "ng build --configuration development",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"lint": "ng lint"
},
"dependencies": {
"@angular/animations": "^19.1.0",
"@angular/common": "^19.1.0",
"@angular/compiler": "^19.1.0",
"@angular/core": "^19.1.0",
"@angular/forms": "^19.1.0",
"@angular/platform-browser": "^19.1.0",
"@angular/platform-browser-dynamic": "^19.1.0",
"@angular/router": "^19.1.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.1.0",
"@angular/cli": "^21.1.2",
"@angular/compiler-cli": "^19.1.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.5.2"
}
}

View file

@ -1,18 +0,0 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet],
template: '<router-outlet></router-outlet>',
styles: [`
:host {
display: block;
height: 100%;
}
`]
})
export class AppComponent {
title = 'BugSETI';
}

View file

@ -1,9 +0,0 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withHashLocation } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withHashLocation())
]
};

View file

@ -1,29 +0,0 @@
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
redirectTo: 'tray',
pathMatch: 'full'
},
{
path: 'tray',
loadComponent: () => import('./tray/tray.component').then(m => m.TrayComponent)
},
{
path: 'workbench',
loadComponent: () => import('./workbench/workbench.component').then(m => m.WorkbenchComponent)
},
{
path: 'settings',
loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent)
},
{
path: 'onboarding',
loadComponent: () => import('./onboarding/onboarding.component').then(m => m.OnboardingComponent)
},
{
path: 'jellyfin',
loadComponent: () => import('./jellyfin/jellyfin.component').then(m => m.JellyfinComponent)
}
];

View file

@ -1,189 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
type Mode = 'web' | 'stream';
@Component({
selector: 'app-jellyfin',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="jellyfin">
<header class="jellyfin__header">
<div>
<h1>Jellyfin Player</h1>
<p class="text-muted">Quick embed for media.lthn.ai or any Jellyfin host.</p>
</div>
<div class="mode-switch">
<button class="btn btn--secondary" [class.is-active]="mode === 'web'" (click)="mode = 'web'">Web</button>
<button class="btn btn--secondary" [class.is-active]="mode === 'stream'" (click)="mode = 'stream'">Stream</button>
</div>
</header>
<div class="card jellyfin__config">
<div class="form-group">
<label class="form-label">Jellyfin Server URL</label>
<input class="form-input" [(ngModel)]="serverUrl" placeholder="https://media.lthn.ai" />
</div>
<div *ngIf="mode === 'stream'" class="stream-grid">
<div class="form-group">
<label class="form-label">Item ID</label>
<input class="form-input" [(ngModel)]="itemId" placeholder="Jellyfin library item ID" />
</div>
<div class="form-group">
<label class="form-label">API Key</label>
<input class="form-input" [(ngModel)]="apiKey" placeholder="Jellyfin API key" />
</div>
<div class="form-group">
<label class="form-label">Media Source ID (optional)</label>
<input class="form-input" [(ngModel)]="mediaSourceId" placeholder="Source ID for multi-source items" />
</div>
</div>
<div class="actions">
<button class="btn btn--primary" (click)="load()">Load Player</button>
<button class="btn btn--secondary" (click)="reset()">Reset</button>
</div>
</div>
<div class="card jellyfin__viewer" *ngIf="loaded && mode === 'web'">
<iframe
class="jellyfin-frame"
title="Jellyfin Web"
[src]="safeWebUrl"
loading="lazy"
referrerpolicy="no-referrer"
></iframe>
</div>
<div class="card jellyfin__viewer" *ngIf="loaded && mode === 'stream'">
<video class="jellyfin-video" controls [src]="streamUrl"></video>
<p class="text-muted stream-hint" *ngIf="!streamUrl">Set Item ID and API key to build stream URL.</p>
</div>
</div>
`,
styles: [`
.jellyfin {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-md);
height: 100%;
overflow: auto;
background: var(--bg-secondary);
}
.jellyfin__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
}
.jellyfin__header h1 {
margin-bottom: var(--spacing-xs);
}
.mode-switch {
display: flex;
gap: var(--spacing-xs);
}
.mode-switch .btn.is-active {
border-color: var(--accent-primary);
color: var(--accent-primary);
}
.jellyfin__config {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.stream-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--spacing-sm);
}
.actions {
display: flex;
gap: var(--spacing-sm);
}
.jellyfin__viewer {
flex: 1;
min-height: 420px;
padding: 0;
overflow: hidden;
}
.jellyfin-frame,
.jellyfin-video {
border: 0;
width: 100%;
height: 100%;
min-height: 420px;
background: #000;
}
.stream-hint {
padding: var(--spacing-md);
margin: 0;
}
`]
})
export class JellyfinComponent {
mode: Mode = 'web';
loaded = false;
serverUrl = 'https://media.lthn.ai';
itemId = '';
apiKey = '';
mediaSourceId = '';
safeWebUrl!: SafeResourceUrl;
streamUrl = '';
constructor(private sanitizer: DomSanitizer) {
this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl('https://media.lthn.ai/web/index.html');
}
load(): void {
const base = this.normalizeBase(this.serverUrl);
this.safeWebUrl = this.sanitizer.bypassSecurityTrustResourceUrl(`${base}/web/index.html`);
this.streamUrl = this.buildStreamUrl(base);
this.loaded = true;
}
reset(): void {
this.loaded = false;
this.itemId = '';
this.apiKey = '';
this.mediaSourceId = '';
this.streamUrl = '';
}
private normalizeBase(value: string): string {
const raw = value.trim() || 'https://media.lthn.ai';
const withProtocol = raw.startsWith('http://') || raw.startsWith('https://') ? raw : `https://${raw}`;
return withProtocol.replace(/\/+$/, '');
}
private buildStreamUrl(base: string): string {
if (!this.itemId.trim() || !this.apiKey.trim()) {
return '';
}
const url = new URL(`${base}/Videos/${encodeURIComponent(this.itemId.trim())}/stream`);
url.searchParams.set('api_key', this.apiKey.trim());
url.searchParams.set('static', 'true');
if (this.mediaSourceId.trim()) {
url.searchParams.set('MediaSourceId', this.mediaSourceId.trim());
}
return url.toString();
}
}

View file

@ -1,457 +0,0 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-onboarding',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="onboarding">
<div class="onboarding-content">
<!-- Step 1: Welcome -->
<div class="step" *ngIf="step === 1">
<div class="step-icon">B</div>
<h1>Welcome to BugSETI</h1>
<p class="subtitle">Distributed Bug Fixing - like SETI&#64;home but for code</p>
<div class="feature-list">
<div class="feature">
<span class="feature-icon">[1]</span>
<div>
<strong>Find Issues</strong>
<p>We pull beginner-friendly issues from OSS projects you care about.</p>
</div>
</div>
<div class="feature">
<span class="feature-icon">[2]</span>
<div>
<strong>Get Context</strong>
<p>AI prepares relevant context to help you understand each issue.</p>
</div>
</div>
<div class="feature">
<span class="feature-icon">[3]</span>
<div>
<strong>Submit PRs</strong>
<p>Fix bugs and submit PRs with minimal friction.</p>
</div>
</div>
</div>
<button class="btn btn--primary btn--lg" (click)="nextStep()">Get Started</button>
</div>
<!-- Step 2: GitHub Auth -->
<div class="step" *ngIf="step === 2">
<h2>Connect GitHub</h2>
<p>BugSETI uses the GitHub CLI (gh) to interact with repositories.</p>
<div class="auth-status" [class.auth-success]="ghAuthenticated">
<span class="status-icon">{{ ghAuthenticated ? '[OK]' : '[!]' }}</span>
<span>{{ ghAuthenticated ? 'GitHub CLI authenticated' : 'GitHub CLI not detected' }}</span>
</div>
<div class="auth-instructions" *ngIf="!ghAuthenticated">
<p>To authenticate with GitHub CLI, run:</p>
<code>gh auth login</code>
<p class="note">After authenticating, click "Check Again".</p>
</div>
<div class="step-actions">
<button class="btn btn--secondary" (click)="checkGhAuth()">Check Again</button>
<button class="btn btn--primary" (click)="nextStep()" [disabled]="!ghAuthenticated">Continue</button>
</div>
</div>
<!-- Step 3: Select Repos -->
<div class="step" *ngIf="step === 3">
<h2>Choose Repositories</h2>
<p>Add repositories you want to contribute to.</p>
<div class="repo-input">
<input type="text" class="form-input" [(ngModel)]="newRepo"
placeholder="owner/repo (e.g., facebook/react)">
<button class="btn btn--secondary" (click)="addRepo()" [disabled]="!newRepo">Add</button>
</div>
<div class="selected-repos" *ngIf="selectedRepos.length">
<h3>Selected Repositories</h3>
<div class="repo-chip" *ngFor="let repo of selectedRepos; let i = index">
{{ repo }}
<button class="repo-remove" (click)="removeRepo(i)">x</button>
</div>
</div>
<div class="suggested-repos">
<h3>Suggested Repositories</h3>
<div class="suggested-list">
<button class="suggestion" *ngFor="let repo of suggestedRepos" (click)="addSuggested(repo)">
{{ repo }}
</button>
</div>
</div>
<div class="step-actions">
<button class="btn btn--secondary" (click)="prevStep()">Back</button>
<button class="btn btn--primary" (click)="nextStep()" [disabled]="selectedRepos.length === 0">Continue</button>
</div>
</div>
<!-- Step 4: Complete -->
<div class="step" *ngIf="step === 4">
<div class="complete-icon">[OK]</div>
<h2>You're All Set!</h2>
<p>BugSETI is ready to help you contribute to open source.</p>
<div class="summary">
<p><strong>{{ selectedRepos.length }}</strong> repositories selected</p>
<p>Looking for issues with these labels:</p>
<div class="label-list">
<span class="badge badge--primary">good first issue</span>
<span class="badge badge--primary">help wanted</span>
<span class="badge badge--primary">beginner-friendly</span>
</div>
</div>
<button class="btn btn--success btn--lg" (click)="complete()">Start Finding Issues</button>
</div>
</div>
<div class="step-indicators">
<span class="indicator" [class.active]="step >= 1" [class.current]="step === 1"></span>
<span class="indicator" [class.active]="step >= 2" [class.current]="step === 2"></span>
<span class="indicator" [class.active]="step >= 3" [class.current]="step === 3"></span>
<span class="indicator" [class.active]="step >= 4" [class.current]="step === 4"></span>
</div>
</div>
`,
styles: [`
.onboarding {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--bg-primary);
}
.onboarding-content {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
}
.step {
max-width: 500px;
text-align: center;
}
.step-icon, .complete-icon {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--spacing-lg);
background: linear-gradient(135deg, var(--accent-primary), var(--accent-success));
border-radius: var(--radius-lg);
font-size: 32px;
font-weight: bold;
color: white;
}
.complete-icon {
background: var(--accent-success);
}
h1 {
font-size: 28px;
margin-bottom: var(--spacing-sm);
}
h2 {
font-size: 24px;
margin-bottom: var(--spacing-sm);
}
.subtitle {
color: var(--text-secondary);
margin-bottom: var(--spacing-xl);
}
.feature-list {
text-align: left;
margin-bottom: var(--spacing-xl);
}
.feature {
display: flex;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
padding: var(--spacing-md);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
}
.feature-icon {
font-family: var(--font-mono);
color: var(--accent-primary);
font-weight: bold;
}
.feature strong {
display: block;
margin-bottom: var(--spacing-xs);
}
.feature p {
color: var(--text-secondary);
font-size: 13px;
margin: 0;
}
.auth-status {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-md);
background-color: var(--bg-tertiary);
border-radius: var(--radius-md);
margin: var(--spacing-lg) 0;
}
.auth-status.auth-success {
background-color: rgba(63, 185, 80, 0.15);
color: var(--accent-success);
}
.status-icon {
font-family: var(--font-mono);
font-weight: bold;
}
.auth-instructions {
text-align: left;
padding: var(--spacing-md);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
}
.auth-instructions code {
display: block;
margin: var(--spacing-md) 0;
padding: var(--spacing-md);
background-color: var(--bg-tertiary);
}
.auth-instructions .note {
color: var(--text-muted);
font-size: 13px;
margin: 0;
}
.step-actions {
display: flex;
gap: var(--spacing-md);
justify-content: center;
margin-top: var(--spacing-xl);
}
.repo-input {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
}
.repo-input .form-input {
flex: 1;
}
.selected-repos, .suggested-repos {
text-align: left;
margin-bottom: var(--spacing-lg);
}
.selected-repos h3, .suggested-repos h3 {
font-size: 12px;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: var(--spacing-sm);
}
.repo-chip {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-sm);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
margin-right: var(--spacing-xs);
margin-bottom: var(--spacing-xs);
}
.repo-remove {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0;
}
.suggested-list {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
}
.suggestion {
padding: var(--spacing-xs) var(--spacing-sm);
background-color: var(--bg-tertiary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-secondary);
cursor: pointer;
font-size: 13px;
}
.suggestion:hover {
background-color: var(--bg-secondary);
border-color: var(--accent-primary);
}
.summary {
padding: var(--spacing-lg);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-xl);
}
.summary p {
margin-bottom: var(--spacing-sm);
}
.label-list {
display: flex;
gap: var(--spacing-xs);
justify-content: center;
flex-wrap: wrap;
}
.step-indicators {
display: flex;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-lg);
}
.indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--border-color);
}
.indicator.active {
background-color: var(--accent-primary);
}
.indicator.current {
width: 24px;
border-radius: 4px;
}
.btn--lg {
padding: var(--spacing-md) var(--spacing-xl);
font-size: 16px;
}
`]
})
export class OnboardingComponent {
step = 1;
ghAuthenticated = false;
newRepo = '';
selectedRepos: string[] = [];
suggestedRepos = [
'facebook/react',
'microsoft/vscode',
'golang/go',
'kubernetes/kubernetes',
'rust-lang/rust',
'angular/angular',
'nodejs/node',
'python/cpython'
];
ngOnInit() {
this.checkGhAuth();
}
nextStep() {
if (this.step < 4) {
this.step++;
}
}
prevStep() {
if (this.step > 1) {
this.step--;
}
}
async checkGhAuth() {
try {
// Check if gh CLI is authenticated
// In a real implementation, this would call the backend
this.ghAuthenticated = true; // Assume authenticated for demo
} catch (err) {
this.ghAuthenticated = false;
}
}
addRepo() {
if (this.newRepo && !this.selectedRepos.includes(this.newRepo)) {
this.selectedRepos.push(this.newRepo);
this.newRepo = '';
}
}
removeRepo(index: number) {
this.selectedRepos.splice(index, 1);
}
addSuggested(repo: string) {
if (!this.selectedRepos.includes(repo)) {
this.selectedRepos.push(repo);
}
}
async complete() {
try {
// Save repos to config
if ((window as any).go?.main?.ConfigService?.SetConfig) {
const config = await (window as any).go.main.ConfigService.GetConfig() || {};
config.watchedRepos = this.selectedRepos;
await (window as any).go.main.ConfigService.SetConfig(config);
}
// Mark onboarding as complete
if ((window as any).go?.main?.TrayService?.CompleteOnboarding) {
await (window as any).go.main.TrayService.CompleteOnboarding();
}
// Close onboarding window and start fetching
if ((window as any).wails?.Window) {
(window as any).wails.Window.GetByName('onboarding').then((w: any) => w.Hide());
}
// Start fetching
if ((window as any).go?.main?.TrayService?.StartFetching) {
await (window as any).go.main.TrayService.StartFetching();
}
} catch (err) {
console.error('Failed to complete onboarding:', err);
}
}
}

View file

@ -1,407 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
interface Config {
watchedRepos: string[];
labels: string[];
fetchIntervalMinutes: number;
notificationsEnabled: boolean;
notificationSound: boolean;
workspaceDir: string;
marketplaceMcpRoot: string;
theme: string;
autoSeedContext: boolean;
workHours?: {
enabled: boolean;
startHour: number;
endHour: number;
days: number[];
timezone: string;
};
}
@Component({
selector: 'app-settings',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="settings">
<header class="settings-header">
<h1>Settings</h1>
<button class="btn btn--primary" (click)="saveSettings()">Save</button>
</header>
<div class="settings-content">
<section class="settings-section">
<h2>Repositories</h2>
<p class="section-description">Add GitHub repositories to watch for issues.</p>
<div class="repo-list">
<div class="repo-item" *ngFor="let repo of config.watchedRepos; let i = index">
<span>{{ repo }}</span>
<button class="btn btn--danger btn--sm" (click)="removeRepo(i)">Remove</button>
</div>
</div>
<div class="add-repo">
<input type="text" class="form-input" [(ngModel)]="newRepo"
placeholder="owner/repo (e.g., facebook/react)">
<button class="btn btn--secondary" (click)="addRepo()" [disabled]="!newRepo">Add</button>
</div>
</section>
<section class="settings-section">
<h2>Issue Labels</h2>
<p class="section-description">Filter issues by these labels.</p>
<div class="label-list">
<span class="label-chip" *ngFor="let label of config.labels; let i = index">
{{ label }}
<button class="label-remove" (click)="removeLabel(i)">x</button>
</span>
</div>
<div class="add-label">
<input type="text" class="form-input" [(ngModel)]="newLabel"
placeholder="Add label (e.g., good first issue)">
<button class="btn btn--secondary" (click)="addLabel()" [disabled]="!newLabel">Add</button>
</div>
</section>
<section class="settings-section">
<h2>Fetch Settings</h2>
<div class="form-group">
<label class="form-label">Fetch Interval (minutes)</label>
<input type="number" class="form-input" [(ngModel)]="config.fetchIntervalMinutes" min="5" max="120">
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" [(ngModel)]="config.autoSeedContext">
<span>Auto-prepare AI context for issues</span>
</label>
</div>
</section>
<section class="settings-section">
<h2>Work Hours</h2>
<p class="section-description">Only fetch issues during these hours.</p>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" [(ngModel)]="config.workHours!.enabled">
<span>Enable work hours</span>
</label>
</div>
<div class="work-hours-config" *ngIf="config.workHours?.enabled">
<div class="form-group">
<label class="form-label">Start Hour</label>
<select class="form-select" [(ngModel)]="config.workHours!.startHour">
<option *ngFor="let h of hours" [value]="h">{{ h }}:00</option>
</select>
</div>
<div class="form-group">
<label class="form-label">End Hour</label>
<select class="form-select" [(ngModel)]="config.workHours!.endHour">
<option *ngFor="let h of hours" [value]="h">{{ h }}:00</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Days</label>
<div class="day-checkboxes">
<label class="checkbox-label" *ngFor="let day of days; let i = index">
<input type="checkbox" [checked]="isDaySelected(i)" (change)="toggleDay(i)">
<span>{{ day }}</span>
</label>
</div>
</div>
</div>
</section>
<section class="settings-section">
<h2>Notifications</h2>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" [(ngModel)]="config.notificationsEnabled">
<span>Enable desktop notifications</span>
</label>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" [(ngModel)]="config.notificationSound">
<span>Play notification sounds</span>
</label>
</div>
</section>
<section class="settings-section">
<h2>Appearance</h2>
<div class="form-group">
<label class="form-label">Theme</label>
<select class="form-select" [(ngModel)]="config.theme">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="system">System</option>
</select>
</div>
</section>
<section class="settings-section">
<h2>Storage</h2>
<div class="form-group">
<label class="form-label">Workspace Directory</label>
<input type="text" class="form-input" [(ngModel)]="config.workspaceDir"
placeholder="Leave empty for default">
</div>
<div class="form-group">
<label class="form-label">Marketplace MCP Root</label>
<input type="text" class="form-input" [(ngModel)]="config.marketplaceMcpRoot"
placeholder="Path to core-agent (optional)">
<p class="section-description">Override the marketplace MCP root. Leave empty to auto-detect.</p>
</div>
</section>
</div>
</div>
`,
styles: [`
.settings {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--bg-secondary);
}
.settings-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) var(--spacing-lg);
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
.settings-header h1 {
font-size: 18px;
margin: 0;
}
.settings-content {
flex: 1;
overflow-y: auto;
padding: var(--spacing-lg);
}
.settings-section {
background-color: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.settings-section h2 {
font-size: 16px;
margin-bottom: var(--spacing-xs);
}
.section-description {
color: var(--text-muted);
font-size: 13px;
margin-bottom: var(--spacing-md);
}
.repo-list, .label-list {
margin-bottom: var(--spacing-md);
}
.repo-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-sm);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-xs);
}
.add-repo, .add-label {
display: flex;
gap: var(--spacing-sm);
}
.add-repo .form-input, .add-label .form-input {
flex: 1;
}
.label-list {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
}
.label-chip {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: var(--spacing-xs) var(--spacing-sm);
background-color: var(--bg-tertiary);
border-radius: 999px;
font-size: 13px;
}
.label-remove {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0;
font-size: 14px;
line-height: 1;
}
.label-remove:hover {
color: var(--accent-danger);
}
.checkbox-label {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.checkbox-label input[type="checkbox"] {
width: 16px;
height: 16px;
}
.work-hours-config {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.day-checkboxes {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.day-checkboxes .checkbox-label {
width: auto;
}
.btn--sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 12px;
}
`]
})
export class SettingsComponent implements OnInit {
config: Config = {
watchedRepos: [],
labels: ['good first issue', 'help wanted'],
fetchIntervalMinutes: 15,
notificationsEnabled: true,
notificationSound: true,
workspaceDir: '',
marketplaceMcpRoot: '',
theme: 'dark',
autoSeedContext: true,
workHours: {
enabled: false,
startHour: 9,
endHour: 17,
days: [1, 2, 3, 4, 5],
timezone: ''
}
};
newRepo = '';
newLabel = '';
hours = Array.from({ length: 24 }, (_, i) => i);
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
ngOnInit() {
this.loadConfig();
}
async loadConfig() {
try {
if ((window as any).go?.main?.ConfigService?.GetConfig) {
this.config = await (window as any).go.main.ConfigService.GetConfig();
if (!this.config.workHours) {
this.config.workHours = {
enabled: false,
startHour: 9,
endHour: 17,
days: [1, 2, 3, 4, 5],
timezone: ''
};
}
}
} catch (err) {
console.error('Failed to load config:', err);
}
}
async saveSettings() {
try {
if ((window as any).go?.main?.ConfigService?.SetConfig) {
await (window as any).go.main.ConfigService.SetConfig(this.config);
alert('Settings saved!');
}
} catch (err) {
console.error('Failed to save config:', err);
alert('Failed to save settings.');
}
}
addRepo() {
if (this.newRepo && !this.config.watchedRepos.includes(this.newRepo)) {
this.config.watchedRepos.push(this.newRepo);
this.newRepo = '';
}
}
removeRepo(index: number) {
this.config.watchedRepos.splice(index, 1);
}
addLabel() {
if (this.newLabel && !this.config.labels.includes(this.newLabel)) {
this.config.labels.push(this.newLabel);
this.newLabel = '';
}
}
removeLabel(index: number) {
this.config.labels.splice(index, 1);
}
isDaySelected(day: number): boolean {
return this.config.workHours?.days.includes(day) || false;
}
toggleDay(day: number) {
if (!this.config.workHours) return;
const index = this.config.workHours.days.indexOf(day);
if (index === -1) {
this.config.workHours.days.push(day);
} else {
this.config.workHours.days.splice(index, 1);
}
}
}

View file

@ -1,556 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
interface UpdateSettings {
channel: string;
autoUpdate: boolean;
checkInterval: number;
lastCheck: string;
}
interface VersionInfo {
version: string;
channel: string;
commit: string;
buildTime: string;
goVersion: string;
os: string;
arch: string;
}
interface ChannelInfo {
id: string;
name: string;
description: string;
}
interface UpdateCheckResult {
available: boolean;
currentVersion: string;
latestVersion: string;
release?: {
version: string;
channel: string;
tag: string;
name: string;
body: string;
publishedAt: string;
htmlUrl: string;
};
error?: string;
checkedAt: string;
}
@Component({
selector: 'app-updates-settings',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="updates-settings">
<div class="current-version">
<div class="version-badge">
<span class="version-number">{{ versionInfo?.version || 'Unknown' }}</span>
<span class="channel-badge" [class]="'channel-' + (versionInfo?.channel || 'dev')">
{{ versionInfo?.channel || 'dev' }}
</span>
</div>
<p class="build-info" *ngIf="versionInfo">
Built {{ versionInfo.buildTime | date:'medium' }} ({{ versionInfo.commit?.substring(0, 7) }})
</p>
</div>
<div class="update-check" *ngIf="checkResult">
<div class="update-available" *ngIf="checkResult.available">
<div class="update-icon">!</div>
<div class="update-info">
<h4>Update Available</h4>
<p>Version {{ checkResult.latestVersion }} is available</p>
<a *ngIf="checkResult.release?.htmlUrl"
[href]="checkResult.release.htmlUrl"
target="_blank"
class="release-link">
View Release Notes
</a>
</div>
<button class="btn btn--primary" (click)="installUpdate()" [disabled]="isInstalling">
{{ isInstalling ? 'Installing...' : 'Install Update' }}
</button>
</div>
<div class="up-to-date" *ngIf="!checkResult.available && !checkResult.error">
<div class="check-icon">OK</div>
<div class="check-info">
<h4>Up to Date</h4>
<p>You're running the latest version</p>
<span class="last-check" *ngIf="checkResult.checkedAt">
Last checked: {{ checkResult.checkedAt | date:'short' }}
</span>
</div>
</div>
<div class="check-error" *ngIf="checkResult.error">
<div class="error-icon">X</div>
<div class="error-info">
<h4>Check Failed</h4>
<p>{{ checkResult.error }}</p>
</div>
</div>
</div>
<div class="check-button-row">
<button class="btn btn--secondary" (click)="checkForUpdates()" [disabled]="isChecking">
{{ isChecking ? 'Checking...' : 'Check for Updates' }}
</button>
</div>
<div class="settings-section">
<h3>Update Channel</h3>
<p class="section-description">Choose which release channel to follow for updates.</p>
<div class="channel-options">
<label class="channel-option" *ngFor="let channel of channels"
[class.selected]="settings.channel === channel.id">
<input type="radio"
[name]="'channel'"
[value]="channel.id"
[(ngModel)]="settings.channel"
(change)="onSettingsChange()">
<div class="channel-content">
<span class="channel-name">{{ channel.name }}</span>
<span class="channel-desc">{{ channel.description }}</span>
</div>
</label>
</div>
</div>
<div class="settings-section">
<h3>Automatic Updates</h3>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox"
[(ngModel)]="settings.autoUpdate"
(change)="onSettingsChange()">
<span>Automatically install updates</span>
</label>
<p class="setting-hint">When enabled, updates will be installed automatically on app restart.</p>
</div>
<div class="form-group">
<label class="form-label">Check Interval</label>
<select class="form-select"
[(ngModel)]="settings.checkInterval"
(change)="onSettingsChange()">
<option [value]="0">Disabled</option>
<option [value]="1">Every hour</option>
<option [value]="6">Every 6 hours</option>
<option [value]="12">Every 12 hours</option>
<option [value]="24">Daily</option>
<option [value]="168">Weekly</option>
</select>
</div>
</div>
<div class="save-status" *ngIf="saveMessage">
<span [class.error]="saveError">{{ saveMessage }}</span>
</div>
</div>
`,
styles: [`
.updates-settings {
padding: var(--spacing-md);
}
.current-version {
background: var(--bg-tertiary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
text-align: center;
}
.version-badge {
display: flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-xs);
}
.version-number {
font-size: 24px;
font-weight: 600;
}
.channel-badge {
padding: 2px 8px;
border-radius: 999px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.channel-stable { background: var(--accent-success); color: white; }
.channel-beta { background: var(--accent-warning); color: black; }
.channel-nightly { background: var(--accent-purple, #8b5cf6); color: white; }
.channel-dev { background: var(--text-muted); color: var(--bg-primary); }
.build-info {
color: var(--text-muted);
font-size: 12px;
margin: 0;
}
.update-check {
margin-bottom: var(--spacing-lg);
}
.update-available, .up-to-date, .check-error {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
border-radius: var(--radius-md);
}
.update-available {
background: var(--accent-warning-bg, rgba(245, 158, 11, 0.1));
border: 1px solid var(--accent-warning);
}
.up-to-date {
background: var(--accent-success-bg, rgba(34, 197, 94, 0.1));
border: 1px solid var(--accent-success);
}
.check-error {
background: var(--accent-danger-bg, rgba(239, 68, 68, 0.1));
border: 1px solid var(--accent-danger);
}
.update-icon, .check-icon, .error-icon {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
}
.update-icon { background: var(--accent-warning); color: black; }
.check-icon { background: var(--accent-success); color: white; }
.error-icon { background: var(--accent-danger); color: white; }
.update-info, .check-info, .error-info {
flex: 1;
}
.update-info h4, .check-info h4, .error-info h4 {
margin: 0 0 var(--spacing-xs) 0;
font-size: 14px;
}
.update-info p, .check-info p, .error-info p {
margin: 0;
font-size: 13px;
color: var(--text-muted);
}
.release-link {
color: var(--accent-primary);
font-size: 12px;
}
.last-check {
font-size: 11px;
color: var(--text-muted);
}
.check-button-row {
margin-bottom: var(--spacing-lg);
}
.settings-section {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.settings-section h3 {
font-size: 14px;
margin: 0 0 var(--spacing-xs) 0;
}
.section-description {
color: var(--text-muted);
font-size: 12px;
margin-bottom: var(--spacing-md);
}
.channel-options {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.channel-option {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
padding: var(--spacing-md);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.15s ease;
}
.channel-option:hover {
border-color: var(--accent-primary);
}
.channel-option.selected {
border-color: var(--accent-primary);
background: var(--accent-primary-bg, rgba(59, 130, 246, 0.1));
}
.channel-option input[type="radio"] {
margin-top: 2px;
}
.channel-content {
display: flex;
flex-direction: column;
gap: 2px;
}
.channel-name {
font-weight: 500;
font-size: 14px;
}
.channel-desc {
font-size: 12px;
color: var(--text-muted);
}
.form-group {
margin-bottom: var(--spacing-md);
}
.form-group:last-child {
margin-bottom: 0;
}
.checkbox-label {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.setting-hint {
color: var(--text-muted);
font-size: 12px;
margin: var(--spacing-xs) 0 0 24px;
}
.form-label {
display: block;
font-size: 13px;
margin-bottom: var(--spacing-xs);
}
.form-select {
width: 100%;
padding: var(--spacing-sm);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: var(--bg-secondary);
color: var(--text-primary);
font-size: 14px;
}
.save-status {
text-align: center;
font-size: 13px;
color: var(--accent-success);
}
.save-status .error {
color: var(--accent-danger);
}
.btn {
padding: var(--spacing-sm) var(--spacing-md);
border: none;
border-radius: var(--radius-md);
font-size: 14px;
cursor: pointer;
transition: all 0.15s ease;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn--primary {
background: var(--accent-primary);
color: white;
}
.btn--primary:hover:not(:disabled) {
background: var(--accent-primary-hover, #2563eb);
}
.btn--secondary {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
.btn--secondary:hover:not(:disabled) {
background: var(--bg-secondary);
}
`]
})
export class UpdatesComponent implements OnInit, OnDestroy {
settings: UpdateSettings = {
channel: 'stable',
autoUpdate: false,
checkInterval: 6,
lastCheck: ''
};
versionInfo: VersionInfo | null = null;
checkResult: UpdateCheckResult | null = null;
channels: ChannelInfo[] = [
{ id: 'stable', name: 'Stable', description: 'Production releases - most stable, recommended for most users' },
{ id: 'beta', name: 'Beta', description: 'Pre-release builds - new features being tested before stable release' },
{ id: 'nightly', name: 'Nightly', description: 'Latest development builds - bleeding edge, may be unstable' }
];
isChecking = false;
isInstalling = false;
saveMessage = '';
saveError = false;
private saveTimeout: ReturnType<typeof setTimeout> | null = null;
ngOnInit() {
this.loadSettings();
this.loadVersionInfo();
}
ngOnDestroy() {
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
}
async loadSettings() {
try {
const wails = (window as any).go?.main;
if (wails?.UpdateService?.GetSettings) {
this.settings = await wails.UpdateService.GetSettings();
} else if (wails?.ConfigService?.GetUpdateSettings) {
this.settings = await wails.ConfigService.GetUpdateSettings();
}
} catch (err) {
console.error('Failed to load update settings:', err);
}
}
async loadVersionInfo() {
try {
const wails = (window as any).go?.main;
if (wails?.VersionService?.GetVersionInfo) {
this.versionInfo = await wails.VersionService.GetVersionInfo();
} else if (wails?.UpdateService?.GetVersionInfo) {
this.versionInfo = await wails.UpdateService.GetVersionInfo();
}
} catch (err) {
console.error('Failed to load version info:', err);
}
}
async checkForUpdates() {
this.isChecking = true;
this.checkResult = null;
try {
const wails = (window as any).go?.main;
if (wails?.UpdateService?.CheckForUpdate) {
this.checkResult = await wails.UpdateService.CheckForUpdate();
}
} catch (err) {
console.error('Failed to check for updates:', err);
this.checkResult = {
available: false,
currentVersion: this.versionInfo?.version || 'unknown',
latestVersion: '',
error: 'Failed to check for updates',
checkedAt: new Date().toISOString()
};
} finally {
this.isChecking = false;
}
}
async installUpdate() {
if (!this.checkResult?.available || !this.checkResult.release) {
return;
}
this.isInstalling = true;
try {
const wails = (window as any).go?.main;
if (wails?.UpdateService?.InstallUpdate) {
await wails.UpdateService.InstallUpdate();
}
} catch (err) {
console.error('Failed to install update:', err);
alert('Failed to install update. Please try again or download manually.');
} finally {
this.isInstalling = false;
}
}
async onSettingsChange() {
// Debounce save
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => this.saveSettings(), 500);
}
async saveSettings() {
try {
const wails = (window as any).go?.main;
if (wails?.UpdateService?.SetSettings) {
await wails.UpdateService.SetSettings(this.settings);
} else if (wails?.ConfigService?.SetUpdateSettings) {
await wails.ConfigService.SetUpdateSettings(this.settings);
}
this.saveMessage = 'Settings saved';
this.saveError = false;
} catch (err) {
console.error('Failed to save update settings:', err);
this.saveMessage = 'Failed to save settings';
this.saveError = true;
}
// Clear message after 2 seconds
setTimeout(() => {
this.saveMessage = '';
}, 2000);
}
}

View file

@ -1,303 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
interface TrayStatus {
running: boolean;
currentIssue: string;
queueSize: number;
issuesFixed: number;
prsMerged: number;
}
@Component({
selector: 'app-tray',
standalone: true,
imports: [CommonModule],
template: `
<div class="tray-panel">
<header class="tray-header">
<div class="logo">
<span class="logo-icon">B</span>
<span class="logo-text">BugSETI</span>
</div>
<span class="badge" [class.badge--success]="status.running" [class.badge--warning]="!status.running">
{{ status.running ? 'Running' : 'Paused' }}
</span>
</header>
<section class="stats-grid">
<div class="stat-card">
<span class="stat-value">{{ status.queueSize }}</span>
<span class="stat-label">In Queue</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ status.issuesFixed }}</span>
<span class="stat-label">Fixed</span>
</div>
<div class="stat-card">
<span class="stat-value">{{ status.prsMerged }}</span>
<span class="stat-label">Merged</span>
</div>
</section>
<section class="current-issue" *ngIf="status.currentIssue">
<h3>Current Issue</h3>
<div class="issue-card">
<p class="issue-title">{{ status.currentIssue }}</p>
<div class="issue-actions">
<button class="btn btn--primary btn--sm" (click)="openWorkbench()">
Open Workbench
</button>
<button class="btn btn--secondary btn--sm" (click)="skipIssue()">
Skip
</button>
</div>
</div>
</section>
<section class="current-issue" *ngIf="!status.currentIssue">
<div class="empty-state">
<span class="empty-icon">[ ]</span>
<p>No issue in progress</p>
<button class="btn btn--primary btn--sm" (click)="nextIssue()" [disabled]="status.queueSize === 0">
Get Next Issue
</button>
</div>
</section>
<footer class="tray-footer">
<button class="btn btn--secondary btn--sm" (click)="openJellyfin()">
Jellyfin
</button>
<button class="btn btn--secondary btn--sm" (click)="toggleRunning()">
{{ status.running ? 'Pause' : 'Start' }}
</button>
<button class="btn btn--secondary btn--sm" (click)="openSettings()">
Settings
</button>
</footer>
</div>
`,
styles: [`
.tray-panel {
display: flex;
flex-direction: column;
height: 100%;
padding: var(--spacing-md);
background-color: var(--bg-primary);
}
.tray-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-md);
}
.logo {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.logo-icon {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-success));
border-radius: var(--radius-md);
font-weight: bold;
color: white;
}
.logo-text {
font-weight: 600;
font-size: 16px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.stat-card {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-sm);
background-color: var(--bg-secondary);
border-radius: var(--radius-md);
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: var(--accent-primary);
}
.stat-label {
font-size: 11px;
color: var(--text-muted);
text-transform: uppercase;
}
.current-issue {
flex: 1;
margin-bottom: var(--spacing-md);
}
.current-issue h3 {
font-size: 12px;
color: var(--text-muted);
text-transform: uppercase;
margin-bottom: var(--spacing-sm);
}
.issue-card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--spacing-md);
}
.issue-title {
font-size: 13px;
line-height: 1.4;
margin-bottom: var(--spacing-sm);
}
.issue-actions {
display: flex;
gap: var(--spacing-sm);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-xl);
text-align: center;
}
.empty-icon {
font-size: 32px;
color: var(--text-muted);
margin-bottom: var(--spacing-sm);
}
.empty-state p {
color: var(--text-muted);
margin-bottom: var(--spacing-md);
}
.tray-footer {
display: flex;
gap: var(--spacing-sm);
justify-content: center;
}
.btn--sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 12px;
}
`]
})
export class TrayComponent implements OnInit, OnDestroy {
status: TrayStatus = {
running: false,
currentIssue: '',
queueSize: 0,
issuesFixed: 0,
prsMerged: 0
};
private refreshInterval?: ReturnType<typeof setInterval>;
ngOnInit() {
this.loadStatus();
this.refreshInterval = setInterval(() => this.loadStatus(), 5000);
}
ngOnDestroy() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
async loadStatus() {
try {
// Call Wails binding when available
if ((window as any).go?.main?.TrayService?.GetStatus) {
this.status = await (window as any).go.main.TrayService.GetStatus();
}
} catch (err) {
console.error('Failed to load status:', err);
}
}
async toggleRunning() {
try {
if (this.status.running) {
if ((window as any).go?.main?.TrayService?.PauseFetching) {
await (window as any).go.main.TrayService.PauseFetching();
}
} else {
if ((window as any).go?.main?.TrayService?.StartFetching) {
await (window as any).go.main.TrayService.StartFetching();
}
}
this.loadStatus();
} catch (err) {
console.error('Failed to toggle running:', err);
}
}
async nextIssue() {
try {
if ((window as any).go?.main?.TrayService?.NextIssue) {
await (window as any).go.main.TrayService.NextIssue();
}
this.loadStatus();
} catch (err) {
console.error('Failed to get next issue:', err);
}
}
async skipIssue() {
try {
if ((window as any).go?.main?.TrayService?.SkipIssue) {
await (window as any).go.main.TrayService.SkipIssue();
}
this.loadStatus();
} catch (err) {
console.error('Failed to skip issue:', err);
}
}
openWorkbench() {
if ((window as any).wails?.Window) {
(window as any).wails.Window.GetByName('workbench').then((w: any) => {
w.Show();
w.Focus();
});
}
}
openSettings() {
if ((window as any).wails?.Window) {
(window as any).wails.Window.GetByName('settings').then((w: any) => {
w.Show();
w.Focus();
});
}
}
openJellyfin() {
window.location.assign('/jellyfin');
}
}

View file

@ -1,356 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
interface Issue {
id: string;
number: number;
repo: string;
title: string;
body: string;
url: string;
labels: string[];
author: string;
context?: IssueContext;
}
interface IssueContext {
summary: string;
relevantFiles: string[];
suggestedFix: string;
complexity: string;
estimatedTime: string;
}
@Component({
selector: 'app-workbench',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div class="workbench">
<header class="workbench-header">
<h1>BugSETI Workbench</h1>
<div class="header-actions">
<button class="btn btn--secondary" (click)="skipIssue()">Skip</button>
<button class="btn btn--success" (click)="submitPR()" [disabled]="!canSubmit">Submit PR</button>
</div>
</header>
<div class="workbench-content" *ngIf="currentIssue">
<aside class="issue-panel">
<div class="card">
<div class="card__header">
<h2 class="card__title">Issue #{{ currentIssue.number }}</h2>
<a [href]="currentIssue.url" target="_blank" class="btn btn--secondary btn--sm">View on GitHub</a>
</div>
<h3>{{ currentIssue.title }}</h3>
<div class="labels">
<span class="badge badge--primary" *ngFor="let label of currentIssue.labels">
{{ label }}
</span>
</div>
<div class="issue-meta">
<span>{{ currentIssue.repo }}</span>
<span>by {{ currentIssue.author }}</span>
</div>
<div class="issue-body">
<pre>{{ currentIssue.body }}</pre>
</div>
</div>
<div class="card" *ngIf="currentIssue.context">
<div class="card__header">
<h2 class="card__title">AI Context</h2>
<span class="badge" [ngClass]="{
'badge--success': currentIssue.context.complexity === 'easy',
'badge--warning': currentIssue.context.complexity === 'medium',
'badge--danger': currentIssue.context.complexity === 'hard'
}">
{{ currentIssue.context.complexity }}
</span>
</div>
<p class="context-summary">{{ currentIssue.context.summary }}</p>
<div class="context-section" *ngIf="currentIssue.context.relevantFiles?.length">
<h4>Relevant Files</h4>
<ul class="file-list">
<li *ngFor="let file of currentIssue.context.relevantFiles">
<code>{{ file }}</code>
</li>
</ul>
</div>
<div class="context-section" *ngIf="currentIssue.context.suggestedFix">
<h4>Suggested Approach</h4>
<p>{{ currentIssue.context.suggestedFix }}</p>
</div>
<div class="context-meta">
<span>Est. time: {{ currentIssue.context.estimatedTime || 'Unknown' }}</span>
</div>
</div>
</aside>
<main class="editor-panel">
<div class="card">
<div class="card__header">
<h2 class="card__title">PR Details</h2>
</div>
<div class="form-group">
<label class="form-label">PR Title</label>
<input type="text" class="form-input" [(ngModel)]="prTitle"
[placeholder]="'Fix #' + currentIssue.number + ': ' + currentIssue.title">
</div>
<div class="form-group">
<label class="form-label">PR Description</label>
<textarea class="form-textarea" [(ngModel)]="prBody" rows="8"
placeholder="Describe your changes..."></textarea>
</div>
<div class="form-group">
<label class="form-label">Branch Name</label>
<input type="text" class="form-input" [(ngModel)]="branchName"
[placeholder]="'bugseti/issue-' + currentIssue.number">
</div>
<div class="form-group">
<label class="form-label">Commit Message</label>
<textarea class="form-textarea" [(ngModel)]="commitMessage" rows="3"
[placeholder]="'fix: resolve issue #' + currentIssue.number"></textarea>
</div>
</div>
</main>
</div>
<div class="empty-state" *ngIf="!currentIssue">
<h2>No Issue Selected</h2>
<p>Get an issue from the queue to start working.</p>
<button class="btn btn--primary" (click)="nextIssue()">Get Next Issue</button>
</div>
</div>
`,
styles: [`
.workbench {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--bg-secondary);
}
.workbench-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md) var(--spacing-lg);
background-color: var(--bg-primary);
border-bottom: 1px solid var(--border-color);
}
.workbench-header h1 {
font-size: 18px;
margin: 0;
}
.header-actions {
display: flex;
gap: var(--spacing-sm);
}
.workbench-content {
display: grid;
grid-template-columns: 400px 1fr;
flex: 1;
overflow: hidden;
}
.issue-panel {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-md);
overflow-y: auto;
border-right: 1px solid var(--border-color);
}
.editor-panel {
padding: var(--spacing-md);
overflow-y: auto;
}
.labels {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
margin: var(--spacing-sm) 0;
}
.issue-meta {
display: flex;
gap: var(--spacing-md);
font-size: 12px;
color: var(--text-muted);
margin-bottom: var(--spacing-md);
}
.issue-body {
padding: var(--spacing-md);
background-color: var(--bg-tertiary);
border-radius: var(--radius-md);
max-height: 200px;
overflow-y: auto;
}
.issue-body pre {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 13px;
line-height: 1.5;
margin: 0;
}
.context-summary {
color: var(--text-secondary);
margin-bottom: var(--spacing-md);
}
.context-section {
margin-bottom: var(--spacing-md);
}
.context-section h4 {
font-size: 12px;
text-transform: uppercase;
color: var(--text-muted);
margin-bottom: var(--spacing-xs);
}
.file-list {
list-style: none;
padding: 0;
margin: 0;
}
.file-list li {
padding: var(--spacing-xs) 0;
}
.context-meta {
font-size: 12px;
color: var(--text-muted);
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
text-align: center;
}
.empty-state h2 {
color: var(--text-secondary);
}
.empty-state p {
color: var(--text-muted);
}
`]
})
export class WorkbenchComponent implements OnInit {
currentIssue: Issue | null = null;
prTitle = '';
prBody = '';
branchName = '';
commitMessage = '';
get canSubmit(): boolean {
return !!this.currentIssue && !!this.prTitle;
}
ngOnInit() {
this.loadCurrentIssue();
}
async loadCurrentIssue() {
try {
if ((window as any).go?.main?.TrayService?.GetCurrentIssue) {
this.currentIssue = await (window as any).go.main.TrayService.GetCurrentIssue();
if (this.currentIssue) {
this.initDefaults();
}
}
} catch (err) {
console.error('Failed to load current issue:', err);
}
}
initDefaults() {
if (!this.currentIssue) return;
this.prTitle = `Fix #${this.currentIssue.number}: ${this.currentIssue.title}`;
this.branchName = `bugseti/issue-${this.currentIssue.number}`;
this.commitMessage = `fix: resolve issue #${this.currentIssue.number}\n\n${this.currentIssue.title}`;
}
async nextIssue() {
try {
if ((window as any).go?.main?.TrayService?.NextIssue) {
this.currentIssue = await (window as any).go.main.TrayService.NextIssue();
if (this.currentIssue) {
this.initDefaults();
}
}
} catch (err) {
console.error('Failed to get next issue:', err);
}
}
async skipIssue() {
try {
if ((window as any).go?.main?.TrayService?.SkipIssue) {
await (window as any).go.main.TrayService.SkipIssue();
this.currentIssue = null;
this.prTitle = '';
this.prBody = '';
this.branchName = '';
this.commitMessage = '';
}
} catch (err) {
console.error('Failed to skip issue:', err);
}
}
async submitPR() {
if (!this.currentIssue || !this.canSubmit) return;
try {
if ((window as any).go?.main?.SubmitService?.Submit) {
const result = await (window as any).go.main.SubmitService.Submit({
issue: this.currentIssue,
title: this.prTitle,
body: this.prBody,
branch: this.branchName,
commitMsg: this.commitMessage
});
if (result.success) {
alert(`PR submitted successfully!\n\n${result.prUrl}`);
this.currentIssue = null;
} else {
alert(`Failed to submit PR: ${result.error}`);
}
}
} catch (err) {
console.error('Failed to submit PR:', err);
alert('Failed to submit PR. Check console for details.');
}
}
}

View file

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>BugSETI</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View file

@ -1,6 +0,0 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));

View file

@ -1,268 +0,0 @@
// BugSETI Global Styles
// CSS Variables for theming
:root {
// Dark theme (default)
--bg-primary: #161b22;
--bg-secondary: #0d1117;
--bg-tertiary: #21262d;
--text-primary: #c9d1d9;
--text-secondary: #8b949e;
--text-muted: #6e7681;
--border-color: #30363d;
--accent-primary: #58a6ff;
--accent-success: #3fb950;
--accent-warning: #d29922;
--accent-danger: #f85149;
// Spacing
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
// Border radius
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 12px;
// Font
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
}
// Light theme
[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f6f8fa;
--bg-tertiary: #f0f3f6;
--text-primary: #24292f;
--text-secondary: #57606a;
--text-muted: #8b949e;
--border-color: #d0d7de;
--accent-primary: #0969da;
--accent-success: #1a7f37;
--accent-warning: #9a6700;
--accent-danger: #cf222e;
}
// Reset
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
width: 100%;
}
body {
font-family: var(--font-family);
font-size: 14px;
line-height: 1.5;
color: var(--text-primary);
background-color: var(--bg-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// Typography
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.25;
margin-bottom: var(--spacing-sm);
}
h1 { font-size: 24px; }
h2 { font-size: 20px; }
h3 { font-size: 16px; }
h4 { font-size: 14px; }
p {
margin-bottom: var(--spacing-md);
}
a {
color: var(--accent-primary);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
code {
font-family: var(--font-mono);
font-size: 12px;
padding: 2px 6px;
background-color: var(--bg-tertiary);
border-radius: var(--radius-sm);
}
// Buttons
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-xs);
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
font-weight: 500;
line-height: 1;
border: 1px solid transparent;
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
&--primary {
background-color: var(--accent-primary);
color: white;
&:hover:not(:disabled) {
opacity: 0.9;
}
}
&--secondary {
background-color: var(--bg-tertiary);
border-color: var(--border-color);
color: var(--text-primary);
&:hover:not(:disabled) {
background-color: var(--bg-secondary);
}
}
&--success {
background-color: var(--accent-success);
color: white;
}
&--danger {
background-color: var(--accent-danger);
color: white;
}
}
// Forms
.form-group {
margin-bottom: var(--spacing-md);
}
.form-label {
display: block;
margin-bottom: var(--spacing-xs);
font-weight: 500;
color: var(--text-primary);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
color: var(--text-primary);
&:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
}
&::placeholder {
color: var(--text-muted);
}
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
// Cards
.card {
background-color: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: var(--spacing-md);
&__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border-color);
}
&__title {
font-size: 16px;
font-weight: 600;
}
}
// Badges
.badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
border-radius: 999px;
&--primary {
background-color: rgba(88, 166, 255, 0.15);
color: var(--accent-primary);
}
&--success {
background-color: rgba(63, 185, 80, 0.15);
color: var(--accent-success);
}
&--warning {
background-color: rgba(210, 153, 34, 0.15);
color: var(--accent-warning);
}
&--danger {
background-color: rgba(248, 81, 73, 0.15);
color: var(--accent-danger);
}
}
// Utility classes
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--text-muted); }
.text-success { color: var(--accent-success); }
.text-danger { color: var(--accent-danger); }
.text-warning { color: var(--accent-warning); }
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.hidden { display: none; }

View file

@ -1,13 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
}

View file

@ -1,35 +0,0 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"lib": [
"ES2022",
"dom"
],
"paths": {
"@app/*": ["src/app/*"],
"@shared/*": ["src/app/shared/*"]
}
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View file

@ -1,13 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
]
},
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View file

@ -1,85 +0,0 @@
module forge.lthn.ai/core/cli/cmd/bugseti
go 1.25.5
require (
forge.lthn.ai/core/cli v0.0.0
forge.lthn.ai/core/cli/internal/bugseti v0.0.0
forge.lthn.ai/core/cli/internal/bugseti/updater v0.0.0
github.com/Snider/Borg v0.2.0
github.com/wailsapp/wails/v3 v3.0.0-alpha.64
)
replace forge.lthn.ai/core/cli => ../..
replace forge.lthn.ai/core/cli/internal/bugseti => ../../internal/bugseti
replace forge.lthn.ai/core/cli/internal/bugseti/updater => ../../internal/bugseti/updater
require (
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/42wim/httpsig v1.2.3 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/Snider/Enchantrix v0.0.2 // indirect
github.com/adrg/xdg v0.5.3 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/coder/websocket v1.8.14 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/go-git/go-git/v5 v5.16.4 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.1 // indirect
github.com/lmittmann/tint v1.1.2 // indirect
github.com/mailru/easyjson v0.9.1 // indirect
github.com/mark3labs/mcp-go v0.43.2 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/samber/lo v1.52.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/spf13/viper v1.21.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/mod v0.32.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View file

@ -1,181 +0,0 @@
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.2.0 h1:HTCWpzyWQOHDWt3LzI6/d2jvUDsw/vgGRWm/8BTvcqI=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
github.com/Snider/Borg v0.2.0 h1:iCyDhY4WTXi39+FexRwXbn2YpZ2U9FUXVXDZk9xRCXQ=
github.com/Snider/Borg v0.2.0/go.mod h1:TqlKnfRo9okioHbgrZPfWjQsztBV0Nfskz4Om1/vdMY=
github.com/Snider/Enchantrix v0.0.2 h1:ExZQiBhfS/p/AHFTKhY80TOd+BXZjK95EzByAEgwvjs=
github.com/Snider/Enchantrix v0.0.2/go.mod h1:CtFcLAvnDT1KcuF1JBb/DJj0KplY8jHryO06KzQ1hsQ=
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e h1:Lf/gRkoycfOBPa42vU2bbgPurFong6zXeFtPoxholzU=
github.com/go-json-experiment/json v0.0.0-20251027170946-4849db3c2f7e/go.mod h1:uNVvRXArCGbZ508SxYYTC5v1JWoz2voff5pm25jU1Ok=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
github.com/mark3labs/mcp-go v0.43.2 h1:21PUSlWWiSbUPQwXIJ5WKlETixpFpq+WBpbMGDSVy/I=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/wails/v3 v3.0.0-alpha.64 h1:xAhLFVfdbg7XdZQ5mMQmBv2BglWu8hMqe50Z+3UJvBs=
github.com/wailsapp/wails/v3 v3.0.0-alpha.64/go.mod h1:zvgNL/mlFcX8aRGu6KOz9AHrMmTBD+4hJRQIONqF/Yw=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 B

View file

@ -1,25 +0,0 @@
// Package icons provides embedded icon assets for the BugSETI application.
package icons
import _ "embed"
// TrayTemplate is the template icon for macOS systray (22x22 PNG, black on transparent).
// Template icons automatically adapt to light/dark mode on macOS.
//
//go:embed tray-template.png
var TrayTemplate []byte
// TrayLight is the light mode icon for Windows/Linux systray.
//
//go:embed tray-light.png
var TrayLight []byte
// TrayDark is the dark mode icon for Windows/Linux systray.
//
//go:embed tray-dark.png
var TrayDark []byte
// AppIcon is the main application icon.
//
//go:embed appicon.png
var AppIcon []byte

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

View file

@ -1,290 +0,0 @@
// Package main provides the BugSETI system tray application.
// BugSETI - "Distributed Bug Fixing like SETI@home but for code"
//
// The application runs as a system tray app that:
// - Pulls OSS issues from Forgejo
// - Uses AI to prepare context for each issue
// - Presents issues to users for fixing
// - Automates PR submission
package main
import (
"embed"
"io/fs"
"log"
"net/http"
"runtime"
"strings"
"forge.lthn.ai/core/cli/cmd/bugseti/icons"
"forge.lthn.ai/core/cli/internal/bugseti"
"forge.lthn.ai/core/cli/internal/bugseti/updater"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
//go:embed all:frontend/dist/bugseti/browser
var assets embed.FS
func main() {
// Strip the embed path prefix so files are served from root
staticAssets, err := fs.Sub(assets, "frontend/dist/bugseti/browser")
if err != nil {
log.Fatal(err)
}
// Initialize the config service
configService := bugseti.NewConfigService()
if err := configService.Load(); err != nil {
log.Printf("Warning: Could not load config: %v", err)
}
// Check Forgejo API availability
forgeClient, err := bugseti.CheckForge()
if err != nil {
log.Fatalf("Forgejo check failed: %v\n\nConfigure with: core forge config --url URL --token TOKEN", err)
}
// Initialize core services
notifyService := bugseti.NewNotifyService(configService)
statsService := bugseti.NewStatsService(configService)
fetcherService := bugseti.NewFetcherService(configService, notifyService, forgeClient)
queueService := bugseti.NewQueueService(configService)
seederService := bugseti.NewSeederService(configService, forgeClient.URL(), forgeClient.Token())
submitService := bugseti.NewSubmitService(configService, notifyService, statsService, forgeClient)
hubService := bugseti.NewHubService(configService)
versionService := bugseti.NewVersionService()
workspaceService := NewWorkspaceService(configService)
// Initialize update service
updateService, err := updater.NewService(configService)
if err != nil {
log.Printf("Warning: Could not initialize update service: %v", err)
}
// Create the tray service (we'll set the app reference later)
trayService := NewTrayService(nil)
// Build services list
services := []application.Service{
application.NewService(configService),
application.NewService(notifyService),
application.NewService(statsService),
application.NewService(fetcherService),
application.NewService(queueService),
application.NewService(seederService),
application.NewService(submitService),
application.NewService(versionService),
application.NewService(workspaceService),
application.NewService(hubService),
application.NewService(trayService),
}
// Add update service if available
if updateService != nil {
services = append(services, application.NewService(updateService))
}
// Create the application
app := application.New(application.Options{
Name: "BugSETI",
Description: "Distributed Bug Fixing - like SETI@home but for code",
Services: services,
Assets: application.AssetOptions{
Handler: spaHandler(staticAssets),
},
Mac: application.MacOptions{
ActivationPolicy: application.ActivationPolicyAccessory,
},
})
// Set the app reference and services in tray service
trayService.app = app
trayService.SetServices(fetcherService, queueService, configService, statsService)
// Set up system tray
setupSystemTray(app, fetcherService, queueService, configService)
// Start update service background checker
if updateService != nil {
updateService.Start()
}
log.Println("Starting BugSETI...")
log.Println(" - System tray active")
log.Println(" - Waiting for issues...")
log.Printf(" - Version: %s (%s)", bugseti.GetVersion(), bugseti.GetChannel())
// Attempt hub registration (non-blocking)
if hubURL := configService.GetHubURL(); hubURL != "" {
if err := hubService.AutoRegister(); err != nil {
log.Printf(" - Hub: auto-register skipped: %v", err)
} else if err := hubService.Register(); err != nil {
log.Printf(" - Hub: registration failed: %v", err)
} else {
log.Println(" - Hub: registered with portal")
}
} else {
log.Println(" - Hub: not configured (set hubUrl in config)")
}
if err := app.Run(); err != nil {
log.Fatal(err)
}
// Stop update service on exit
if updateService != nil {
updateService.Stop()
}
}
// setupSystemTray configures the system tray icon and menu
func setupSystemTray(app *application.App, fetcher *bugseti.FetcherService, queue *bugseti.QueueService, config *bugseti.ConfigService) {
systray := app.SystemTray.New()
systray.SetTooltip("BugSETI - Distributed Bug Fixing")
// Set tray icon based on OS
if runtime.GOOS == "darwin" {
systray.SetTemplateIcon(icons.TrayTemplate)
} else {
systray.SetDarkModeIcon(icons.TrayDark)
systray.SetIcon(icons.TrayLight)
}
// Create tray panel window (workbench preview)
trayWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "tray-panel",
Title: "BugSETI",
Width: 420,
Height: 520,
URL: "/tray",
Hidden: true,
Frameless: true,
BackgroundColour: application.NewRGB(22, 27, 34),
})
systray.AttachWindow(trayWindow).WindowOffset(5)
// Create main workbench window
workbenchWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "workbench",
Title: "BugSETI Workbench",
Width: 1200,
Height: 800,
URL: "/workbench",
Hidden: true,
BackgroundColour: application.NewRGB(22, 27, 34),
})
// Create settings window
settingsWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "settings",
Title: "BugSETI Settings",
Width: 600,
Height: 500,
URL: "/settings",
Hidden: true,
BackgroundColour: application.NewRGB(22, 27, 34),
})
// Create onboarding window
onboardingWindow := app.Window.NewWithOptions(application.WebviewWindowOptions{
Name: "onboarding",
Title: "Welcome to BugSETI",
Width: 700,
Height: 600,
URL: "/onboarding",
Hidden: true,
BackgroundColour: application.NewRGB(22, 27, 34),
})
// Build tray menu
trayMenu := app.Menu.New()
// Status item (dynamic)
statusItem := trayMenu.Add("Status: Idle")
statusItem.SetEnabled(false)
trayMenu.AddSeparator()
// Start/Pause toggle
startPauseItem := trayMenu.Add("Start Fetching")
startPauseItem.OnClick(func(ctx *application.Context) {
if fetcher.IsRunning() {
fetcher.Pause()
startPauseItem.SetLabel("Start Fetching")
statusItem.SetLabel("Status: Paused")
} else {
fetcher.Start()
startPauseItem.SetLabel("Pause")
statusItem.SetLabel("Status: Running")
}
})
trayMenu.AddSeparator()
// Current Issue
currentIssueItem := trayMenu.Add("Current Issue: None")
currentIssueItem.OnClick(func(ctx *application.Context) {
if issue := queue.CurrentIssue(); issue != nil {
workbenchWindow.Show()
workbenchWindow.Focus()
}
})
// Open Workbench
trayMenu.Add("Open Workbench").OnClick(func(ctx *application.Context) {
workbenchWindow.Show()
workbenchWindow.Focus()
})
trayMenu.AddSeparator()
// Settings
trayMenu.Add("Settings...").OnClick(func(ctx *application.Context) {
settingsWindow.Show()
settingsWindow.Focus()
})
// Stats submenu
statsMenu := trayMenu.AddSubmenu("Stats")
statsMenu.Add("Issues Fixed: 0").SetEnabled(false)
statsMenu.Add("PRs Merged: 0").SetEnabled(false)
statsMenu.Add("Repos Contributed: 0").SetEnabled(false)
trayMenu.AddSeparator()
// Quit
trayMenu.Add("Quit BugSETI").OnClick(func(ctx *application.Context) {
app.Quit()
})
systray.SetMenu(trayMenu)
// Check if onboarding needed (deferred until app is running)
app.Event.RegisterApplicationEventHook(events.Common.ApplicationStarted, func(event *application.ApplicationEvent) {
if !config.IsOnboarded() {
onboardingWindow.Show()
onboardingWindow.Focus()
}
})
}
// spaHandler wraps an fs.FS to serve static files with SPA fallback.
// If the requested path doesn't match a real file, it serves index.html
// so Angular's client-side router can handle the route.
func spaHandler(fsys fs.FS) http.Handler {
fileServer := http.FileServer(http.FS(fsys))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/")
if path == "" {
path = "index.html"
}
// Check if the file exists
if _, err := fs.Stat(fsys, path); err != nil {
// File doesn't exist — serve index.html for SPA routing
r.URL.Path = "/"
}
fileServer.ServeHTTP(w, r)
})
}

View file

@ -1,158 +0,0 @@
// Package main provides the BugSETI system tray application.
package main
import (
"context"
"log"
"forge.lthn.ai/core/cli/internal/bugseti"
"github.com/wailsapp/wails/v3/pkg/application"
)
// TrayService provides system tray bindings for the frontend.
type TrayService struct {
app *application.App
fetcher *bugseti.FetcherService
queue *bugseti.QueueService
config *bugseti.ConfigService
stats *bugseti.StatsService
}
// NewTrayService creates a new TrayService instance.
func NewTrayService(app *application.App) *TrayService {
return &TrayService{
app: app,
}
}
// SetServices sets the service references after initialization.
func (t *TrayService) SetServices(fetcher *bugseti.FetcherService, queue *bugseti.QueueService, config *bugseti.ConfigService, stats *bugseti.StatsService) {
t.fetcher = fetcher
t.queue = queue
t.config = config
t.stats = stats
}
// ServiceName returns the service name for Wails.
func (t *TrayService) ServiceName() string {
return "TrayService"
}
// ServiceStartup is called when the Wails application starts.
func (t *TrayService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
log.Println("TrayService started")
return nil
}
// ServiceShutdown is called when the Wails application shuts down.
func (t *TrayService) ServiceShutdown() error {
log.Println("TrayService shutdown")
return nil
}
// TrayStatus represents the current status of the tray.
type TrayStatus struct {
Running bool `json:"running"`
CurrentIssue string `json:"currentIssue"`
QueueSize int `json:"queueSize"`
IssuesFixed int `json:"issuesFixed"`
PRsMerged int `json:"prsMerged"`
}
// GetStatus returns the current tray status.
func (t *TrayService) GetStatus() TrayStatus {
var currentIssue string
if t.queue != nil {
if issue := t.queue.CurrentIssue(); issue != nil {
currentIssue = issue.Title
}
}
var queueSize int
if t.queue != nil {
queueSize = t.queue.Size()
}
var running bool
if t.fetcher != nil {
running = t.fetcher.IsRunning()
}
var issuesFixed, prsMerged int
if t.stats != nil {
stats := t.stats.GetStats()
issuesFixed = stats.IssuesAttempted
prsMerged = stats.PRsMerged
}
return TrayStatus{
Running: running,
CurrentIssue: currentIssue,
QueueSize: queueSize,
IssuesFixed: issuesFixed,
PRsMerged: prsMerged,
}
}
// StartFetching starts the issue fetcher.
func (t *TrayService) StartFetching() error {
if t.fetcher == nil {
return nil
}
return t.fetcher.Start()
}
// PauseFetching pauses the issue fetcher.
func (t *TrayService) PauseFetching() {
if t.fetcher != nil {
t.fetcher.Pause()
}
}
// GetCurrentIssue returns the current issue being worked on.
func (t *TrayService) GetCurrentIssue() *bugseti.Issue {
if t.queue == nil {
return nil
}
return t.queue.CurrentIssue()
}
// NextIssue moves to the next issue in the queue.
func (t *TrayService) NextIssue() *bugseti.Issue {
if t.queue == nil {
return nil
}
return t.queue.Next()
}
// SkipIssue skips the current issue.
func (t *TrayService) SkipIssue() {
if t.queue == nil {
return
}
t.queue.Skip()
}
// ShowWindow shows a specific window by name.
func (t *TrayService) ShowWindow(name string) {
if t.app == nil {
return
}
// Window will be shown by the frontend via Wails runtime
}
// IsOnboarded returns whether the user has completed onboarding.
func (t *TrayService) IsOnboarded() bool {
if t.config == nil {
return false
}
return t.config.IsOnboarded()
}
// CompleteOnboarding marks onboarding as complete.
func (t *TrayService) CompleteOnboarding() error {
if t.config == nil {
return nil
}
return t.config.CompleteOnboarding()
}

View file

@ -1,374 +0,0 @@
// Package main provides the BugSETI system tray application.
package main
import (
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"sort"
"sync"
"time"
"github.com/Snider/Borg/pkg/tim"
"forge.lthn.ai/core/cli/internal/bugseti"
"forge.lthn.ai/core/cli/pkg/io/datanode"
)
const (
// defaultMaxWorkspaces is the fallback upper bound when config is unavailable.
defaultMaxWorkspaces = 100
// defaultWorkspaceTTL is the fallback TTL when config is unavailable.
defaultWorkspaceTTL = 24 * time.Hour
// sweepInterval is how often the background sweeper runs.
sweepInterval = 5 * time.Minute
)
// WorkspaceService manages DataNode-backed workspaces for issues.
// Each issue gets a sandboxed in-memory filesystem that can be
// snapshotted, packaged as a TIM container, or shipped as a crash report.
type WorkspaceService struct {
config *bugseti.ConfigService
workspaces map[string]*Workspace // issue ID -> workspace
mu sync.RWMutex
done chan struct{} // signals the background sweeper to stop
stopped chan struct{} // closed when the sweeper goroutine exits
}
// Workspace tracks a DataNode-backed workspace for an issue.
type Workspace struct {
Issue *bugseti.Issue `json:"issue"`
Medium *datanode.Medium
DiskPath string `json:"diskPath"`
CreatedAt time.Time `json:"createdAt"`
Snapshots int `json:"snapshots"`
}
// CrashReport contains a packaged workspace state for debugging.
type CrashReport struct {
IssueID string `json:"issueId"`
Repo string `json:"repo"`
Number int `json:"number"`
Title string `json:"title"`
Error string `json:"error"`
Timestamp time.Time `json:"timestamp"`
Data []byte `json:"data"` // tar snapshot
Files int `json:"files"`
Size int64 `json:"size"`
}
// NewWorkspaceService creates a new WorkspaceService.
// Call Start() to begin the background TTL sweeper.
func NewWorkspaceService(config *bugseti.ConfigService) *WorkspaceService {
return &WorkspaceService{
config: config,
workspaces: make(map[string]*Workspace),
done: make(chan struct{}),
stopped: make(chan struct{}),
}
}
// ServiceName returns the service name for Wails.
func (w *WorkspaceService) ServiceName() string {
return "WorkspaceService"
}
// Start launches the background sweeper goroutine that periodically
// evicts expired workspaces. This prevents unbounded map growth even
// when no new Capture calls arrive.
func (w *WorkspaceService) Start() {
go func() {
defer close(w.stopped)
ticker := time.NewTicker(sweepInterval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
w.mu.Lock()
evicted := w.cleanup()
w.mu.Unlock()
if evicted > 0 {
log.Printf("Workspace sweeper: evicted %d stale entries, %d remaining", evicted, w.ActiveWorkspaces())
}
case <-w.done:
return
}
}
}()
log.Printf("Workspace sweeper started (interval=%s, ttl=%s, max=%d)",
sweepInterval, w.ttl(), w.maxCap())
}
// Stop signals the background sweeper to exit and waits for it to finish.
func (w *WorkspaceService) Stop() {
close(w.done)
<-w.stopped
log.Printf("Workspace sweeper stopped")
}
// ttl returns the configured workspace TTL, falling back to the default.
func (w *WorkspaceService) ttl() time.Duration {
if w.config != nil {
return w.config.GetWorkspaceTTL()
}
return defaultWorkspaceTTL
}
// maxCap returns the configured max workspace count, falling back to the default.
func (w *WorkspaceService) maxCap() int {
if w.config != nil {
return w.config.GetMaxWorkspaces()
}
return defaultMaxWorkspaces
}
// Capture loads a filesystem workspace into a DataNode Medium.
// Call this after git clone to create the in-memory snapshot.
func (w *WorkspaceService) Capture(issue *bugseti.Issue, diskPath string) error {
if issue == nil {
return fmt.Errorf("issue is nil")
}
m := datanode.New()
// Walk the filesystem and load all files into the DataNode
err := filepath.WalkDir(diskPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return nil // skip errors
}
// Get relative path
rel, err := filepath.Rel(diskPath, path)
if err != nil {
return nil
}
if rel == "." {
return nil
}
// Skip .git internals (keep .git marker but not the pack files)
if rel == ".git" {
return fs.SkipDir
}
if d.IsDir() {
return m.EnsureDir(rel)
}
// Skip large files (>1MB) to keep DataNode lightweight
info, err := d.Info()
if err != nil || info.Size() > 1<<20 {
return nil
}
content, err := os.ReadFile(path)
if err != nil {
return nil
}
return m.Write(rel, string(content))
})
if err != nil {
return fmt.Errorf("failed to capture workspace: %w", err)
}
w.mu.Lock()
w.cleanup()
w.workspaces[issue.ID] = &Workspace{
Issue: issue,
Medium: m,
DiskPath: diskPath,
CreatedAt: time.Now(),
}
w.mu.Unlock()
log.Printf("Captured workspace for issue #%d (%s)", issue.Number, issue.Repo)
return nil
}
// GetMedium returns the DataNode Medium for an issue's workspace.
func (w *WorkspaceService) GetMedium(issueID string) *datanode.Medium {
w.mu.RLock()
defer w.mu.RUnlock()
ws := w.workspaces[issueID]
if ws == nil {
return nil
}
return ws.Medium
}
// Snapshot takes a tar snapshot of the workspace.
func (w *WorkspaceService) Snapshot(issueID string) ([]byte, error) {
w.mu.Lock()
defer w.mu.Unlock()
ws := w.workspaces[issueID]
if ws == nil {
return nil, fmt.Errorf("workspace not found: %s", issueID)
}
data, err := ws.Medium.Snapshot()
if err != nil {
return nil, fmt.Errorf("snapshot failed: %w", err)
}
ws.Snapshots++
return data, nil
}
// PackageCrashReport captures the current workspace state as a crash report.
// Re-reads from disk to get the latest state (including git changes).
func (w *WorkspaceService) PackageCrashReport(issue *bugseti.Issue, errMsg string) (*CrashReport, error) {
if issue == nil {
return nil, fmt.Errorf("issue is nil")
}
w.mu.RLock()
ws := w.workspaces[issue.ID]
w.mu.RUnlock()
var diskPath string
if ws != nil {
diskPath = ws.DiskPath
} else {
// Try to find the workspace on disk
baseDir := w.config.GetWorkspaceDir()
if baseDir == "" {
baseDir = filepath.Join(os.TempDir(), "bugseti")
}
diskPath = filepath.Join(baseDir, sanitizeForPath(issue.Repo), fmt.Sprintf("issue-%d", issue.Number))
}
// Re-capture from disk to get latest state
if err := w.Capture(issue, diskPath); err != nil {
return nil, fmt.Errorf("capture failed: %w", err)
}
// Snapshot the captured workspace
data, err := w.Snapshot(issue.ID)
if err != nil {
return nil, fmt.Errorf("snapshot failed: %w", err)
}
return &CrashReport{
IssueID: issue.ID,
Repo: issue.Repo,
Number: issue.Number,
Title: issue.Title,
Error: errMsg,
Timestamp: time.Now(),
Data: data,
Size: int64(len(data)),
}, nil
}
// PackageTIM wraps the workspace as a TIM container (runc-compatible bundle).
// The resulting TIM can be executed via runc or encrypted to .stim for transit.
func (w *WorkspaceService) PackageTIM(issueID string) (*tim.TerminalIsolationMatrix, error) {
w.mu.RLock()
ws := w.workspaces[issueID]
w.mu.RUnlock()
if ws == nil {
return nil, fmt.Errorf("workspace not found: %s", issueID)
}
dn := ws.Medium.DataNode()
return tim.FromDataNode(dn)
}
// SaveCrashReport writes a crash report to the data directory.
func (w *WorkspaceService) SaveCrashReport(report *CrashReport) (string, error) {
dataDir := w.config.GetDataDir()
if dataDir == "" {
dataDir = filepath.Join(os.TempDir(), "bugseti")
}
crashDir := filepath.Join(dataDir, "crash-reports")
if err := os.MkdirAll(crashDir, 0755); err != nil {
return "", fmt.Errorf("failed to create crash dir: %w", err)
}
filename := fmt.Sprintf("crash-%s-issue-%d-%s.tar",
sanitizeForPath(report.Repo),
report.Number,
report.Timestamp.Format("20060102-150405"),
)
path := filepath.Join(crashDir, filename)
if err := os.WriteFile(path, report.Data, 0644); err != nil {
return "", fmt.Errorf("failed to write crash report: %w", err)
}
log.Printf("Crash report saved: %s (%d bytes)", path, report.Size)
return path, nil
}
// cleanup evicts expired workspaces and enforces the max size cap.
// Must be called with w.mu held for writing.
// Returns the number of evicted entries.
func (w *WorkspaceService) cleanup() int {
now := time.Now()
ttl := w.ttl()
cap := w.maxCap()
evicted := 0
// First pass: evict entries older than TTL.
for id, ws := range w.workspaces {
if now.Sub(ws.CreatedAt) > ttl {
delete(w.workspaces, id)
evicted++
}
}
// Second pass: if still over cap, evict oldest entries.
if len(w.workspaces) > cap {
type entry struct {
id string
createdAt time.Time
}
entries := make([]entry, 0, len(w.workspaces))
for id, ws := range w.workspaces {
entries = append(entries, entry{id, ws.CreatedAt})
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].createdAt.Before(entries[j].createdAt)
})
toEvict := len(w.workspaces) - cap
for i := 0; i < toEvict; i++ {
delete(w.workspaces, entries[i].id)
evicted++
}
}
return evicted
}
// Release removes a workspace from memory.
func (w *WorkspaceService) Release(issueID string) {
w.mu.Lock()
delete(w.workspaces, issueID)
w.mu.Unlock()
}
// ActiveWorkspaces returns the count of active workspaces.
func (w *WorkspaceService) ActiveWorkspaces() int {
w.mu.RLock()
defer w.mu.RUnlock()
return len(w.workspaces)
}
// sanitizeForPath converts owner/repo to a safe directory name.
func sanitizeForPath(s string) string {
result := make([]byte, 0, len(s))
for _, c := range s {
if c == '/' || c == '\\' || c == ':' {
result = append(result, '-')
} else {
result = append(result, byte(c))
}
}
return string(result)
}

View file

@ -1,151 +0,0 @@
package main
import (
"fmt"
"testing"
"time"
"forge.lthn.ai/core/cli/internal/bugseti"
)
func TestCleanup_TTL(t *testing.T) {
svc := NewWorkspaceService(bugseti.NewConfigService())
// Seed with entries that are older than TTL.
svc.mu.Lock()
for i := 0; i < 5; i++ {
svc.workspaces[fmt.Sprintf("old-%d", i)] = &Workspace{
CreatedAt: time.Now().Add(-25 * time.Hour),
}
}
// Add one fresh entry.
svc.workspaces["fresh"] = &Workspace{
CreatedAt: time.Now(),
}
svc.cleanup()
svc.mu.Unlock()
if got := svc.ActiveWorkspaces(); got != 1 {
t.Errorf("expected 1 workspace after TTL cleanup, got %d", got)
}
}
func TestCleanup_MaxSize(t *testing.T) {
svc := NewWorkspaceService(bugseti.NewConfigService())
maxCap := svc.maxCap()
// Fill beyond the cap with fresh entries.
svc.mu.Lock()
for i := 0; i < maxCap+20; i++ {
svc.workspaces[fmt.Sprintf("ws-%d", i)] = &Workspace{
CreatedAt: time.Now().Add(-time.Duration(i) * time.Minute),
}
}
svc.cleanup()
svc.mu.Unlock()
if got := svc.ActiveWorkspaces(); got != maxCap {
t.Errorf("expected %d workspaces after cap cleanup, got %d", maxCap, got)
}
}
func TestCleanup_EvictsOldestWhenOverCap(t *testing.T) {
svc := NewWorkspaceService(bugseti.NewConfigService())
maxCap := svc.maxCap()
// Create maxCap+1 entries; the newest should survive.
svc.mu.Lock()
for i := 0; i <= maxCap; i++ {
svc.workspaces[fmt.Sprintf("ws-%d", i)] = &Workspace{
CreatedAt: time.Now().Add(-time.Duration(maxCap-i) * time.Minute),
}
}
svc.cleanup()
svc.mu.Unlock()
// The newest entry (ws-<maxCap>) should still exist.
newest := fmt.Sprintf("ws-%d", maxCap)
svc.mu.RLock()
_, exists := svc.workspaces[newest]
svc.mu.RUnlock()
if !exists {
t.Error("expected newest workspace to survive eviction")
}
// The oldest entry (ws-0) should have been evicted.
svc.mu.RLock()
_, exists = svc.workspaces["ws-0"]
svc.mu.RUnlock()
if exists {
t.Error("expected oldest workspace to be evicted")
}
}
func TestCleanup_ReturnsEvictedCount(t *testing.T) {
svc := NewWorkspaceService(bugseti.NewConfigService())
svc.mu.Lock()
for i := 0; i < 3; i++ {
svc.workspaces[fmt.Sprintf("old-%d", i)] = &Workspace{
CreatedAt: time.Now().Add(-25 * time.Hour),
}
}
svc.workspaces["fresh"] = &Workspace{
CreatedAt: time.Now(),
}
evicted := svc.cleanup()
svc.mu.Unlock()
if evicted != 3 {
t.Errorf("expected 3 evicted entries, got %d", evicted)
}
}
func TestStartStop(t *testing.T) {
svc := NewWorkspaceService(bugseti.NewConfigService())
svc.Start()
// Add a stale entry while the sweeper is running.
svc.mu.Lock()
svc.workspaces["stale"] = &Workspace{
CreatedAt: time.Now().Add(-25 * time.Hour),
}
svc.mu.Unlock()
// Stop should return without hanging.
svc.Stop()
}
func TestConfigurableTTL(t *testing.T) {
cfg := bugseti.NewConfigService()
svc := NewWorkspaceService(cfg)
// Default TTL should be 24h (1440 minutes).
if got := svc.ttl(); got != 24*time.Hour {
t.Errorf("expected default TTL of 24h, got %s", got)
}
// Default max cap should be 100.
if got := svc.maxCap(); got != 100 {
t.Errorf("expected default max cap of 100, got %d", got)
}
}
func TestNilConfigFallback(t *testing.T) {
svc := &WorkspaceService{
config: nil,
workspaces: make(map[string]*Workspace),
done: make(chan struct{}),
stopped: make(chan struct{}),
}
if got := svc.ttl(); got != defaultWorkspaceTTL {
t.Errorf("expected fallback TTL %s, got %s", defaultWorkspaceTTL, got)
}
if got := svc.maxCap(); got != defaultMaxWorkspaces {
t.Errorf("expected fallback max cap %d, got %d", defaultMaxWorkspaces, got)
}
}

View file

@ -3,10 +3,10 @@ package collect
import (
"fmt"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/cli/pkg/io"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
"forge.lthn.ai/core/go/pkg/io"
)
func init() {

View file

@ -4,9 +4,9 @@ import (
"context"
"strings"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// BitcoinTalk command flags

View file

@ -4,9 +4,9 @@ import (
"fmt"
"time"
"forge.lthn.ai/core/cli/pkg/cli"
collectpkg "forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
collectpkg "forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// addDispatchCommand adds the 'dispatch' subcommand to the collect parent.

View file

@ -4,9 +4,9 @@ import (
"context"
"fmt"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// Excavate command flags

View file

@ -4,9 +4,9 @@ import (
"context"
"strings"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// GitHub command flags

View file

@ -3,9 +3,9 @@ package collect
import (
"context"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// Market command flags

View file

@ -3,9 +3,9 @@ package collect
import (
"context"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// Papers command flags

View file

@ -3,9 +3,9 @@ package collect
import (
"context"
"forge.lthn.ai/core/cli/pkg/cli"
"forge.lthn.ai/core/cli/pkg/collect"
"forge.lthn.ai/core/cli/pkg/i18n"
"forge.lthn.ai/core/go/pkg/cli"
"forge.lthn.ai/core/go/pkg/collect"
"forge.lthn.ai/core/go/pkg/i18n"
)
// addProcessCommand adds the 'process' subcommand to the collect parent.

View file

@ -186,7 +186,7 @@
<div class="flex items-center gap-6 text-sm">
<a href="#how-it-works" class="text-lethean-400 hover:text-lethean-200 transition-colors link-underline">How it works</a>
<a href="#ecosystem" class="text-lethean-400 hover:text-lethean-200 transition-colors link-underline">Ecosystem</a>
<a href="https://github.com/host-uk/core" target="_blank" rel="noopener" class="text-lethean-400 hover:text-lethean-200 transition-colors link-underline">GitHub</a>
<a href="https://forge.lthn.ai/core/cli" target="_blank" rel="noopener" class="text-lethean-400 hover:text-lethean-200 transition-colors link-underline">GitHub</a>
<a href="#join" class="inline-flex items-center gap-1.5 px-4 py-1.5 rounded-md bg-cyan-400/10 text-cyan-400 border border-cyan-400/20 hover:bg-cyan-400/20 hover:border-cyan-400/30 transition-all text-sm font-medium">
Get BugSETI
</a>
@ -249,7 +249,7 @@
Download BugSETI
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7l5 5m0 0l-5 5m5-5H6"/></svg>
</a>
<a href="https://github.com/host-uk/core" target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-6 py-3 rounded-lg border border-lethean-600/50 text-lethean-300 font-medium text-sm hover:bg-lethean-800/50 hover:border-lethean-500/50 transition-all">
<a href="https://forge.lthn.ai/core/cli" target="_blank" rel="noopener" class="inline-flex items-center gap-2 px-6 py-3 rounded-lg border border-lethean-600/50 text-lethean-300 font-medium text-sm hover:bg-lethean-800/50 hover:border-lethean-500/50 transition-all">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
View Source
</a>
@ -518,13 +518,13 @@
<!-- Download buttons -->
<div class="flex flex-col sm:flex-row items-center justify-center gap-3 mb-12">
<a href="https://github.com/host-uk/core/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<a href="https://forge.lthn.ai/core/cli/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<span class="text-lg">🐧</span> Linux
</a>
<a href="https://github.com/host-uk/core/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<a href="https://forge.lthn.ai/core/cli/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<span class="text-lg">🍎</span> macOS
</a>
<a href="https://github.com/host-uk/core/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<a href="https://forge.lthn.ai/core/cli/releases" target="_blank" rel="noopener" class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-6 py-3.5 rounded-lg bg-lethean-800 border border-lethean-600/40 text-lethean-200 font-medium text-sm hover:bg-lethean-700 hover:border-lethean-500/50 transition-all">
<span class="text-lg">🪟</span> Windows
</a>
</div>
@ -533,7 +533,7 @@
<div class="gradient-border rounded-lg overflow-hidden max-w-md mx-auto">
<div class="bg-lethean-900 rounded-lg px-5 py-3 font-mono text-sm text-left">
<span class="text-lethean-500"># or build from source</span><br>
<span class="text-cyan-400">$</span> <span class="text-lethean-300">git clone https://github.com/host-uk/core</span><br>
<span class="text-cyan-400">$</span> <span class="text-lethean-300">git clone https://forge.lthn.ai/core/cli</span><br>
<span class="text-cyan-400">$</span> <span class="text-lethean-300">cd core && go build ./cmd/bugseti</span>
</div>
</div>

View file

@ -1,6 +1,6 @@
package config
import "forge.lthn.ai/core/cli/pkg/cli"
import "forge.lthn.ai/core/go/pkg/cli"
func init() {
cli.RegisterCommands(AddConfigCommands)

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