Files
TZGyn-skill/SKILL.md
T
2026-09-28 12:23:22 +08:00

7.2 KiB

name, description
name description
TZGyn-skill Use for any TZGyn development workflow — starting a new project on Gitea, publishing to the TZGyn Homebrew tap (casks for GUI apps, formulae for CLIs), signing and notarizing a desktop/mobile build, cutting a release, or extending/updating this skill itself.

TZGyn dev skill

Personal conventions for TZGyn projects. Source of truth is the Git repo https://gitea.tzgyn.dev/TZGyn/TZGyn-skill; it is installed as a global Crush skill at ~/.agents/skills/TZGyn-skill (a symlink to the checkout, so editing the repo updates the live skill).

The reference implementations to copy from are ~/Git/kapture and ~/Git/konference (macOS menu-bar apps).

Ground rules

  • Host: Gitea at https://gitea.tzgyn.dev, owner TZGyn, tea login name gitea.tzgyn.dev. SSH: git@gitea.tzgyn.dev:TZGyn/<repo>.git.
  • Always print the upgrade script (the brew commands below) in the final answer after any release or version bump. The user wants it every time.
  • Never commit build artifacts. .gitignore covers .build/, *.app/, dist/, .swiftpm/, xcuserdata/, DerivedData/, .DS_Store.
  • Never commit secrets (.p8, API keys) or push to a remote unless asked.
  • Commit messages: concise, why-focused, first line < 72 chars; add a body only when it helps a future reader.
  • Prefer SwiftPM (Swift/SwiftUI) for macOS apps; no external dependencies.

1. New project on Gitea

# 1. Create the repo (as the authenticated user).
tea repos create --name <repo> --desc "<one-line description>" --login gitea.tzgyn.dev
#    Do NOT pass --owner TZGyn: that hits /orgs/TZGyn/repos and 404s for a user.

# 2. Initialise and push.
cd ~/Git/<repo>
git init -b main
git add -A && git commit -m "<why>"
git remote add origin https://gitea.tzgyn.dev/TZGyn/<repo>.git
git push -u origin main

Scaffold every repo with README.md, AGENTS.md (architecture + commands + gotchas), and .gitignore. See ~/Git/simpletopbar for the minimal shape: Package.swift, Sources/<Name>/*.swift, build-app.sh, release.sh, scripts/.

2. Homebrew tap

  • The tap clone lives at ~/.cache/homebrew-tap (origin https://gitea.tzgyn.dev/TZGyn/homebrew-tap.git).

  • Layout: GUI apps → Casks/<name>.rb; CLI tools → Formula/<name>.rb.

  • After pushing, sync Homebrew's own clone:

    git -C ~/.cache/homebrew-tap add -A
    git -C ~/.cache/homebrew-tap commit -m "Add <name> cask"
    git -C ~/.cache/homebrew-tap push
    git -C /opt/homebrew/Library/Taps/tzgyn/homebrew-tap pull --ff-only
    brew audit --cask tzgyn/tap/<name>     # or: brew audit --formula tzgyn/tap/<name>
    
  • Users tap once (non-GitHub taps need the full URL):

    brew tap TZGyn/tap https://gitea.tzgyn.dev/TZGyn/homebrew-tap.git
    brew trust TZGyn/tap
    

Cask (GUI app)

cask "<name>" do
  version "X.Y.Z"
  sha256 "<sha256 of the release zip>"

  url "https://gitea.tzgyn.dev/TZGyn/<repo>/releases/download/v#{version}/<Asset>.zip"
  name "<Display Name>"
  desc "<one line>"
  homepage "https://gitea.tzgyn.dev/TZGyn/<repo>"

  app "<Name>.app"
end

Formula (CLI)

class <Name> < Formula
  desc "<one line>"
  homepage "https://gitea.tzgyn.dev/TZGyn/<repo>"
  url "https://gitea.tzgyn.dev/TZGyn/<repo>/releases/download/v0.1.0/<asset>-0.1.0-darwin-arm64.tar.gz"
  sha256 "<sha256 of the tarball>"
  version "0.1.0"

  def install
    bin.install "<binary>"
  end
end

3. Releasing a macOS GUI app

Two scripts per repo: build-app.sh <version> and release.sh <version>.

  1. Build + sign (build-app.sh): swift build -c release → handwritten Info.plist (NOT generated from Package.swift) → make-icon.sh → sign with the Developer ID identity resolved from the login keychain (security find-identity -v -p codesigning | awk '/Developer ID Application:/{print $2;exit}'), falling back to ad-hoc (-). GUI apps set LSUIElement = true and CFBundleIdentifier = dev.tzgyn.<name>.

  2. Notarize + staple + zip (release.sh): build → ditto -c -k --keepParent <App>.app dist/<App>.zip → submit to Apple → staple → re-zip → print sha256. See Notarization for the credentials.

  3. Publish the release and attach the zip:

    tea releases create --tag v<version> --title "<App> <version>" \
      --note "<what changed>" --asset dist/<App>.zip \
      --repo TZGyn/<repo> --login gitea.tzgyn.dev
    
  4. Bump the cask (version + sha256), push the tap, pull Homebrew's clone, brew audit. Verify by downloading the asset back and comparing the sha256, and spctl -a -vv <App>.app → accepted / source=Notarized Developer ID.

  5. Print the upgrade script:

    brew update
    brew upgrade --cask <name>
    

4. Notarization

Signing identity: Developer ID Application: Teh Zi Gyn (RX6767S6BW). App Store Connect API key (shared with Kapture):

Value Where
Key file ~/.config/kapture/AuthKey_K5KXMMSB28.p8
Key ID K5KXMMSB28
Issuer ID 3e25e5dc-8b75-453f-a71c-c7f3feaac628 (not secret)
Team ID RX6767S6BW

Signing must use a hardened runtime and a secure timestamp:

codesign --force --options runtime --timestamp --sign "$IDENT" "<App>.app"

Submit and verify:

xcrun notarytool submit dist/<App>.zip \
  --key "$HOME/.config/kapture/AuthKey_K5KXMMSB28.p8" \
  --key-id K5KXMMSB28 --issuer 3e25e5dc-8b75-453f-a71c-c7f3feaac628 \
  --wait --output-format json
xcrun stapler staple <App>.app && xcrun stapler validate <App>.app
spctl -a -vv <App>.app      # want: accepted, source=Notarized Developer ID

Wire release.sh so it notarizes when NOTARY_* env vars are set and otherwise skips gracefully (build + zip + sha only). An unnotarized app is still signed but Gatekeeper warns on other machines; the user can bypass with xattr -dr com.apple.quarantine <App>.app or install with brew install --cask --no-quarantine <name>.

5. Mobile

For mobile (iOS/Android), notarization does not apply; the equivalent is store signing (App Store Connect / Play Console) or EAS. Ask for the specific stack before assuming; this skill currently only codifies the macOS path.

6. Extending this skill (self-improvement)

This skill is meant to grow. When the user asks for a new behaviour, preference, or convention:

  1. Edit the relevant section of SKILL.md (add a new section if it is a new topic). Keep it concrete: exact commands, paths, and templates.

  2. Keep it truthful — only document what has actually worked; delete steps that became stale (e.g. a changed path or flag).

  3. Commit and push:

    cd ~/Git/TZGyn-skill
    git add -A && git commit -m "<what behaviour was added>"
    git push
    
  4. The installed copy is a symlink, so it updates immediately; a new Crush session picks it up. Confirm with crush_info (the skill should appear under [skills]).

Prefer updating this skill over repeating ad-hoc instructions in a project: if a convention applies to more than one repo, it belongs here.