Skip to content

Commit 0e924b9

Browse files
committed
Initial commit for CodexSkillManager app
Add SwiftPM-based macOS SwiftUI app to list Codex skills. Includes app source files, skill model and views, SkillStore logic, packaging scripts, development and release helpers, and project documentation. Sets up dependencies, build scripts, and versioning.
0 parents  commit 0e924b9

19 files changed

+1262
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.build
2+
/CodexSkillManager.app

.vscode/launch.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"configurations": [
3+
{
4+
"type": "swift",
5+
"request": "launch",
6+
"args": [],
7+
"cwd": "${workspaceFolder:CodexSkillManager}",
8+
"name": "Debug CodexSkillManager",
9+
"target": "CodexSkillManager",
10+
"configuration": "debug",
11+
"preLaunchTask": "swift: Build Debug CodexSkillManager"
12+
},
13+
{
14+
"type": "swift",
15+
"request": "launch",
16+
"args": [],
17+
"cwd": "${workspaceFolder:CodexSkillManager}",
18+
"name": "Release CodexSkillManager",
19+
"target": "CodexSkillManager",
20+
"configuration": "release",
21+
"preLaunchTask": "swift: Build Release CodexSkillManager"
22+
}
23+
]
24+
}

AGENTS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# CodexSkillManager
2+
3+
## What this app is
4+
CodexSkillManager is a small macOS SwiftUI app built with SwiftPM (no Xcode project) that lists the Codex skills installed in the user's public skills folder.
5+
6+
## How it works
7+
- The app scans `~/.codex/skills/public` for subdirectories.
8+
- Each directory name becomes a skill entry.
9+
- The UI is a SwiftUI `NavigationSplitView` with a list on the left and a detail view on the right.
10+
- The detail view shows the skill name and its full path.
11+
12+
## Build and run
13+
- Build: `swift build`
14+
- Run: `swift run CodexSkillManager`
15+
When editing this app, build after each change and fix any compile errors before continuing.
16+
17+
## Packaging and release
18+
Use the `macos-spm-app-packaging` skill for packaging, notarization, appcast, and GitHub release steps.
19+
Local packaging helpers live in `Scripts/`:
20+
- `Scripts/compile_and_run.sh`: package (adhoc sign) + launch the `.app`.
21+
- `Scripts/package_app.sh`: build and create `CodexSkillManager.app`.
22+
- `Scripts/sign-and-notarize.sh`: sign + notarize for releases.
23+
- `Scripts/make_appcast.sh`: generate Sparkle appcast from a zip.
24+
25+
## Project layout
26+
- `Package.swift`: SwiftPM manifest for the executable target.
27+
- `Sources/CodexSkillManager/App/CodexSkillManagerApp.swift`: App entry point + dependency injection.
28+
- `Sources/CodexSkillManager/Skills/SkillStore.swift`: Loads skills + selected SKILL.md content.
29+
- `Sources/CodexSkillManager/Skills/SkillSplitView.swift`: Split view shell with list + detail.
30+
- `Sources/CodexSkillManager/Skills/SkillDetailView.swift`: Markdown rendering for SKILL.md content.
31+
- `version.env`: Template version file (used by the packaging scripts if added later).

Package.resolved

Lines changed: 33 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// swift-tools-version: 6.2
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "CodexSkillManager",
6+
platforms: [
7+
.macOS(.v14),
8+
],
9+
dependencies: [
10+
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui.git", from: "2.4.1"),
11+
],
12+
targets: [
13+
.executableTarget(
14+
name: "CodexSkillManager",
15+
dependencies: [
16+
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
17+
],
18+
path: "Sources/CodexSkillManager")
19+
]
20+
)

Scripts/build_icon.sh

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ICON_FILE=${1:-Icon.icon}
5+
BASENAME=${2:-Icon}
6+
OUT_ROOT=${3:-build/icon}
7+
XCODE_APP=${XCODE_APP:-/Applications/Xcode.app}
8+
9+
ICTOOL="$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/ictool"
10+
if [[ ! -x "$ICTOOL" ]]; then
11+
ICTOOL="$XCODE_APP/Contents/Applications/Icon Composer.app/Contents/Executables/icontool"
12+
fi
13+
if [[ ! -x "$ICTOOL" ]]; then
14+
echo "ictool/icontool not found. Set XCODE_APP if Xcode is elsewhere." >&2
15+
exit 1
16+
fi
17+
18+
ICONSET_DIR="$OUT_ROOT/${BASENAME}.iconset"
19+
TMP_DIR="$OUT_ROOT/tmp"
20+
mkdir -p "$ICONSET_DIR" "$TMP_DIR"
21+
22+
MASTER_ART="$TMP_DIR/icon_art_824.png"
23+
MASTER_1024="$TMP_DIR/icon_1024.png"
24+
25+
# Render inner art (no margin) with macOS Default appearance.
26+
"$ICTOOL" "$ICON_FILE" \
27+
--export-preview macOS Default 824 824 1 -45 "$MASTER_ART"
28+
29+
# Pad to 1024x1024 with transparent border.
30+
sips --padToHeightWidth 1024 1024 "$MASTER_ART" --out "$MASTER_1024" >/dev/null
31+
32+
# Generate required sizes.
33+
sizes=(16 32 64 128 256 512 1024)
34+
for sz in "${sizes[@]}"; do
35+
out="$ICONSET_DIR/icon_${sz}x${sz}.png"
36+
sips -z "$sz" "$sz" "$MASTER_1024" --out "$out" >/dev/null
37+
if [[ "$sz" -ne 1024 ]]; then
38+
dbl=$((sz*2))
39+
out2="$ICONSET_DIR/icon_${sz}x${sz}@2x.png"
40+
sips -z "$dbl" "$dbl" "$MASTER_1024" --out "$out2" >/dev/null
41+
fi
42+
done
43+
44+
# 512x512@2x already covered by 1024; ensure it exists.
45+
cp "$MASTER_1024" "$ICONSET_DIR/icon_512x512@2x.png"
46+
47+
iconutil -c icns "$ICONSET_DIR" -o Icon.icns
48+
49+
echo "Icon.icns generated at $(pwd)/Icon.icns"

Scripts/compile_and_run.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# Kill running instances, package, relaunch, verify.
3+
set -euo pipefail
4+
5+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6+
APP_NAME=${APP_NAME:-CodexSkillManager}
7+
APP_BUNDLE="${ROOT_DIR}/${APP_NAME}.app"
8+
APP_PROCESS_PATTERN="${APP_NAME}.app/Contents/MacOS/${APP_NAME}"
9+
DEBUG_PROCESS_PATTERN="${ROOT_DIR}/.build/debug/${APP_NAME}"
10+
RELEASE_PROCESS_PATTERN="${ROOT_DIR}/.build/release/${APP_NAME}"
11+
RUN_TESTS=0
12+
RELEASE_ARCHES=""
13+
14+
log() { printf '%s\n' "$*"; }
15+
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
16+
17+
for arg in "$@"; do
18+
case "${arg}" in
19+
--test|-t) RUN_TESTS=1 ;;
20+
--release-universal) RELEASE_ARCHES="arm64 x86_64" ;;
21+
--release-arches=*) RELEASE_ARCHES="${arg#*=}" ;;
22+
--help|-h)
23+
log "Usage: $(basename "$0") [--test] [--release-universal] [--release-arches=\"arm64 x86_64\"]"
24+
exit 0
25+
;;
26+
esac
27+
done
28+
29+
log "==> Killing existing ${APP_NAME} instances"
30+
pkill -f "${APP_PROCESS_PATTERN}" 2>/dev/null || true
31+
pkill -f "${DEBUG_PROCESS_PATTERN}" 2>/dev/null || true
32+
pkill -f "${RELEASE_PROCESS_PATTERN}" 2>/dev/null || true
33+
pkill -x "${APP_NAME}" 2>/dev/null || true
34+
35+
if [[ "${RUN_TESTS}" == "1" ]]; then
36+
log "==> swift test"
37+
swift test -q
38+
fi
39+
40+
HOST_ARCH="$(uname -m)"
41+
ARCHES_VALUE="${HOST_ARCH}"
42+
if [[ -n "${RELEASE_ARCHES}" ]]; then
43+
ARCHES_VALUE="${RELEASE_ARCHES}"
44+
fi
45+
46+
log "==> package app"
47+
SIGNING_MODE=adhoc ARCHES="${ARCHES_VALUE}" "${ROOT_DIR}/Scripts/package_app.sh" release
48+
49+
log "==> launch app"
50+
if ! open "${APP_BUNDLE}"; then
51+
log "WARN: open failed; launching binary directly."
52+
"${APP_BUNDLE}/Contents/MacOS/${APP_NAME}" >/dev/null 2>&1 &
53+
disown
54+
fi
55+
56+
for _ in {1..10}; do
57+
if pgrep -f "${APP_PROCESS_PATTERN}" >/dev/null 2>&1; then
58+
log "OK: ${APP_NAME} is running."
59+
exit 0
60+
fi
61+
sleep 0.4
62+
done
63+
fail "App exited immediately. Check crash logs in Console.app (User Reports)."

Scripts/launch.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6+
APP_NAME=${APP_NAME:-CodexSkillManager}
7+
APP_PATH="$PROJECT_ROOT/${APP_NAME}.app"
8+
9+
echo "==> Killing existing ${APP_NAME} instances"
10+
pkill -x "$APP_NAME" || pkill -f "${APP_NAME}.app" || true
11+
sleep 0.5
12+
13+
if [[ ! -d "$APP_PATH" ]]; then
14+
echo "ERROR: ${APP_NAME}.app not found at $APP_PATH"
15+
echo "Run ./Scripts/package_app.sh first to build the app"
16+
exit 1
17+
fi
18+
19+
echo "==> Launching ${APP_NAME} from $APP_PATH"
20+
open -n "$APP_PATH"
21+
22+
sleep 1
23+
if pgrep -x "$APP_NAME" > /dev/null; then
24+
echo "OK: ${APP_NAME} is running."
25+
else
26+
echo "ERROR: App exited immediately. Check crash logs in Console.app (User Reports)."
27+
exit 1
28+
fi

Scripts/make_appcast.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT=$(cd "$(dirname "$0")/.." && pwd)
5+
ZIP=${1:?
6+
"Usage: $0 MyApp-<ver>.zip"}
7+
FEED_URL=${2:-"https://example.com/appcast.xml"}
8+
PRIVATE_KEY_FILE=${SPARKLE_PRIVATE_KEY_FILE:-}
9+
if [[ -z "$PRIVATE_KEY_FILE" ]]; then
10+
echo "Set SPARKLE_PRIVATE_KEY_FILE to your ed25519 private key (Sparkle)." >&2
11+
exit 1
12+
fi
13+
if [[ ! -f "$ZIP" ]]; then
14+
echo "Zip not found: $ZIP" >&2
15+
exit 1
16+
fi
17+
18+
ZIP_DIR=$(cd "$(dirname "$ZIP")" && pwd)
19+
ZIP_NAME=$(basename "$ZIP")
20+
ZIP_BASE="${ZIP_NAME%.zip}"
21+
VERSION=${SPARKLE_RELEASE_VERSION:-}
22+
if [[ -z "$VERSION" ]]; then
23+
if [[ "$ZIP_NAME" =~ ^[^-]+-([0-9]+(\.[0-9]+){1,2}([-.][^.]*)?)\.zip$ ]]; then
24+
VERSION="${BASH_REMATCH[1]}"
25+
else
26+
echo "Could not infer version from $ZIP_NAME; set SPARKLE_RELEASE_VERSION." >&2
27+
exit 1
28+
fi
29+
fi
30+
31+
NOTES_HTML="${ZIP_DIR}/${ZIP_BASE}.html"
32+
KEEP_NOTES=${KEEP_SPARKLE_NOTES:-0}
33+
if [[ -x "$ROOT/Scripts/changelog-to-html.sh" ]]; then
34+
"$ROOT/Scripts/changelog-to-html.sh" "$VERSION" >"$NOTES_HTML"
35+
else
36+
cat >"$NOTES_HTML" <<HTML
37+
<!doctype html>
38+
<html lang="en">
39+
<meta charset="utf-8">
40+
<title>${ZIP_BASE}</title>
41+
<body>
42+
<h2>${ZIP_BASE}</h2>
43+
<p>Release notes not provided.</p>
44+
</body>
45+
</html>
46+
HTML
47+
fi
48+
cleanup() {
49+
if [[ -n "${WORK_DIR:-}" ]]; then
50+
rm -rf "$WORK_DIR"
51+
fi
52+
if [[ "$KEEP_NOTES" != "1" ]]; then
53+
rm -f "$NOTES_HTML"
54+
fi
55+
}
56+
trap cleanup EXIT
57+
58+
DOWNLOAD_URL_PREFIX=${SPARKLE_DOWNLOAD_URL_PREFIX:-"https://example.com/downloads/v${VERSION}/"}
59+
60+
if ! command -v generate_appcast >/dev/null; then
61+
echo "generate_appcast not found in PATH. Install Sparkle tools." >&2
62+
exit 1
63+
fi
64+
65+
WORK_DIR=$(mktemp -d /tmp/appcast.XXXXXX)
66+
67+
cp "$ROOT/appcast.xml" "$WORK_DIR/appcast.xml"
68+
cp "$ZIP" "$WORK_DIR/$ZIP_NAME"
69+
cp "$NOTES_HTML" "$WORK_DIR/$ZIP_BASE.html"
70+
71+
pushd "$WORK_DIR" >/dev/null
72+
generate_appcast \
73+
--ed-key-file "$PRIVATE_KEY_FILE" \
74+
--download-url-prefix "$DOWNLOAD_URL_PREFIX" \
75+
--embed-release-notes \
76+
--link "$FEED_URL" \
77+
"$WORK_DIR"
78+
popd >/dev/null
79+
80+
cp "$WORK_DIR/appcast.xml" "$ROOT/appcast.xml"
81+
82+
echo "Appcast generated (appcast.xml). Upload alongside $ZIP at $FEED_URL"

0 commit comments

Comments
 (0)