Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,12 @@ $RECYCLE.BIN/
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/macos,windows,linux,git,node

# Swift / SwiftPM build artifacts (apps/tray-mac)
**/.build/
**/.swiftpm/
*.xcodeproj
DerivedData/

# Local agent worktrees
.claude/worktrees/
24 changes: 24 additions & 0 deletions apps/cli/scripts/copy-github-scan-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const HERE = dirname(fileURLToPath(import.meta.url));
const APPS_CLI_ROOT = resolve(HERE, "..");
const PACKAGE_ROOT = resolve(APPS_CLI_ROOT, "..", "..", "packages", "github-scan");
const SKILLS_ROOT = resolve(APPS_CLI_ROOT, "..", "..", "skills");
const TRAY_ROOT = resolve(APPS_CLI_ROOT, "..", "tray-mac");
const DIST = join(APPS_CLI_ROOT, "dist");

if (!existsSync(DIST)) {
Expand Down Expand Up @@ -49,4 +50,27 @@ mkdirSync(dirname(skillsDst), { recursive: true });
cpSync(SKILLS_ROOT, skillsDst, { recursive: true });
console.log(" copied skills/ -> dist/skills/");

// Copy the macOS tray payload (Sources, Package.swift, scripts) so the
// published tarball can build + install the menu-bar app on macOS during
// `first-tree github scan install --tray`. We don't ship a prebuilt .app
// here yet (tracked in #393); for now the install script rebuilds locally.
if (existsSync(TRAY_ROOT)) {
const trayDst = join(DIST, "tray-mac");
cpSync(TRAY_ROOT, trayDst, {
recursive: true,
filter: (src) => {
// Skip build artifacts and editor caches.
if (src.includes("/.build/")) return false;
if (src.endsWith("/.build")) return false;
if (src.includes("/.swiftpm/")) return false;
if (src.endsWith("/.swiftpm")) return false;
if (src.includes("/DerivedData/")) return false;
return true;
},
});
console.log(" copied tray-mac/ -> dist/tray-mac/ (excluding .build, .swiftpm)");
} else {
console.log(" skipped tray-mac/ (not present in this checkout)");
}

console.log("copy-github-scan-assets: ok");
26 changes: 26 additions & 0 deletions apps/tray-mac/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "FirstTreeTray",
platforms: [.macOS(.v13)],
products: [
.executable(name: "FirstTreeTray", targets: ["FirstTreeTray"])
],
targets: [
.executableTarget(
name: "FirstTreeTray",
path: "Sources/FirstTreeTray",
// PDF and SVG are source assets kept for editing; only the PNG variants
// are bundled into the .app at runtime.
exclude: [
"Resources/FirstTreeIcon.pdf",
"Resources/FirstTreeIcon.svg"
],
resources: [
.copy("Resources/FirstTreeIcon.png"),
.copy("Resources/FirstTreeIcon@2x.png")
]
)
]
)
59 changes: 59 additions & 0 deletions apps/tray-mac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# first-tree menu bar app

Native macOS menu bar tray for the `first-tree github scan` daemon. Lives in the
top-right corner of the screen, shows a badge with the count of items needing
human attention, and lets you Pause/Resume the daemon without dropping into a
terminal.

This is a thin client of the daemon — the same `127.0.0.1:7878/inbox` HTTP
endpoint that powers the dashboard at `http://127.0.0.1:7878/`. Daemon is
authoritative; the tray just renders state and proxies daemon-control actions.

## Requirements

- macOS 13+
- Swift toolchain (Xcode Command Line Tools is sufficient — no Xcode needed)
- The `first-tree` CLI must be on `PATH` (the tray shells out to it for daemon
control)

## Build

```bash
cd apps/tray-mac
swift build
```

This produces `.build/arm64-apple-macosx/debug/FirstTreeTray`. To run as a
proper menu-bar app (not a Dock app), you need a minimal `.app` bundle. See
`scripts/build-tray-app.sh` (when shipped) for the bundling flow used by
`first-tree github scan install`.

## What it does

- Shows the first-tree pixel-tree icon in the menu bar
- Badge: count of items where `github_scan_status === "human"`
- Click icon → dropdown with:
- Status row: ● Online / Paused / Offline + Pause/Resume button
- List of items needing you (PR / issue links — click to open in browser)
- Open dashboard
- Preferences…
- Quit
- Pause: stops the daemon, tray stays in menu bar
- Resume: restarts daemon with the same `--allow-repo` and `--tree-repo` it was
running with (read from the launchd plist)
- Quit: stops daemon AND closes the tray (with a confirmation dialog)

## Persistence

- `~/.first-tree/tray-state.json` — repo scope and tree-repo captured at Pause,
used by Resume to restart the daemon with the same configuration
- `~/.first-tree/tray-seen.json` — IDs of inbox items the user has clicked.
Persisted so clicked rows stay dimmed across tray restarts. Garbage-collected
whenever the daemon stops tracking an item.

## Distribution

The tray ships as part of the npm `first-tree` package and is installed by
`first-tree github scan install` — no separate download, no drag-to-Applications.
See [docs/onboarding.md](../../docs/onboarding.md) (when shipped) for the
end-to-end flow.
Loading
Loading