Skip to content

Commit 30834bb

Browse files
MossPickachu Creation (#337)
## Pull Request Checklist Please ensure that your PR meets the following requirements: - [x] I have read the [CONTRIBUTING](CONTRIBUTING.md) guide. - [x] I have updated the documentation (if applicable). - [x] My code follows the style guidelines of this project. - [x] I have performed a self-review of my own code. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] New and existing unit tests pass locally with my changes. ## Description Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. Fixes # (issue number) ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent f5323a1 commit 30834bb

134 files changed

Lines changed: 14656 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
name: moss-pikachu
3+
description: >-
4+
Build and maintain Moss Pikachu, a macOS menu bar semantic file search app
5+
using Moss Python SDK (PyPI moss>=1.6.0), FSEvents, SwiftUI, and Pikachu pet
6+
animations. Use when working on MossPikachu, moss_worker.py, FileMonitor,
7+
SearchService, menu bar overlay, or Moss integration in this repository.
8+
---
9+
10+
# Moss Pikachu Agent Skill
11+
12+
## Project docs (start here)
13+
14+
| Doc | Purpose |
15+
|-----|---------|
16+
| [project-summary.md](../../project-summary.md) | Full technical reference |
17+
| [how-to.md](../../how-to.md) | End-user guide |
18+
| [contribution.md](../../contribution.md) | Contributor guide |
19+
20+
## Specialized skills
21+
22+
| Skill | Use when |
23+
|-------|----------|
24+
| [moss-pikachu-semantic-search](../moss-pikachu-semantic-search/SKILL.md) | Search quality, query tuning |
25+
| [moss-pikachu-indexing](../moss-pikachu-indexing/SKILL.md) | Indexing, manual/automatic modes |
26+
| [moss-pikachu-contributing](../moss-pikachu-contributing/SKILL.md) | PRs and contribution workflow |
27+
28+
## Read order
29+
30+
1. [architecture.md](architecture.md)
31+
2. [moss-integration.md](moss-integration.md)
32+
3. [macos-patterns.md](macos-patterns.md)
33+
4. [ui-animations.md](ui-animations.md)
34+
5. [pitfalls.md](pitfalls.md)
35+
36+
## Non-negotiables
37+
38+
- **Xcode `.app` bundle** — not a root-level SPM executable for the menu bar app
39+
- **macOS-only target**`SUPPORTED_PLATFORMS = macosx`
40+
- **Python worker** for Moss on macOS — Moss Swift SPM is iOS-only
41+
- **`pip install moss>=1.6.0`** — GitHub `main` `sdks/python/sdk` lacks `SessionIndex` API
42+
- **No hardcoded credentials**`MOSS_PROJECT_ID` / `MOSS_PROJECT_KEY` via Keychain or env
43+
- **NSPanel** for search overlay — not `WindowGroup` (avoids Dock icon)
44+
45+
## Phase gates
46+
47+
Run before advancing phases:
48+
49+
```bash
50+
./.cursor/skills/moss-pikachu/scripts/validate-phase.sh 1
51+
./.cursor/skills/moss-pikachu/scripts/validate-phase.sh 2
52+
./.cursor/skills/moss-pikachu/scripts/validate-phase.sh 3
53+
```
54+
55+
## Task sequencing
56+
57+
| Phase | Scope |
58+
|-------|-------|
59+
| 1 | Menu bar, hotkey, search overlay shell, settings window |
60+
| 2 | FileMonitor, moss_worker.py, MossBridge, SearchService |
61+
| 3 | Pikachu animations, live search UI, settings, polish |
62+
63+
Do not wire live search before MossBridge + SearchService compile and pass Phase 2 validation.
64+
65+
## Code conventions
66+
67+
- `@MainActor` for all UI updates
68+
- `async/await` for MossBridge calls (not callbacks)
69+
- Line-delimited JSON between Swift and Python (`\n` terminated)
70+
- Debounce FSEvents (100ms) and search input (200ms)
71+
- `MARK:` sections in Swift service files
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Architecture
2+
3+
## Component map
4+
5+
```
6+
AppDelegate
7+
├── NSStatusBar (menu: Search, Settings, Quit)
8+
├── HotKeyManager (⌘⇧M)
9+
├── SearchOverlayController (NSPanel)
10+
└── SettingsWindowController
11+
12+
SearchOverlayView
13+
├── PikachuPetView
14+
├── ResultsListView
15+
└── SearchService (injected)
16+
17+
SearchService
18+
├── FileMonitor (FSEvents)
19+
├── IndexManager (file manifest in Application Support)
20+
└── MossBridge → moss_worker.py subprocess
21+
```
22+
23+
## Data flow
24+
25+
1. **Launch:** SearchService.initialize() → start MossBridge → init_session → scan folders → add_docs
26+
2. **File change:** FileMonitor → debounce → SearchService.indexFiles → MossBridge.addDocs
27+
3. **Search:** SearchOverlayView → debounced query → SearchService.search → MossBridge.query → results
28+
4. **Quit:** FileMonitor.stop → MossBridge.saveSession (push_index if cloud sync) → terminate worker
29+
30+
## File ownership
31+
32+
| Path | Owner |
33+
|------|-------|
34+
| `MossPikachu/AppDelegate.swift` | Menu bar lifecycle |
35+
| `MossPikachu/Views/SearchOverlayController.swift` | NSPanel window chrome |
36+
| `MossPikachu/Services/MossBridge.swift` | Subprocess JSON protocol |
37+
| `MossPikachu/Resources/moss_worker.py` | Moss SessionIndex loop |
38+
| `~/Library/Application Support/MossPikachu/` | Index manifest, logs |
39+
40+
## Persistence (no Python disk API)
41+
42+
Python `SessionIndex` has no `save_to_disk` / `load_from_disk`. Persistence strategy:
43+
44+
- **In-session:** worker holds SessionIndex in memory while app runs
45+
- **Across launches:** Swift `IndexManager` stores `{path, mtime}` manifest; rescan only changed files
46+
- **Optional cloud:** `push_index()` when user enables Moss cloud sync in Settings
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# macOS Patterns
2+
3+
## Menu bar agent (no Dock icon)
4+
5+
`Info.plist`:
6+
```xml
7+
<key>LSUIElement</key>
8+
<true/>
9+
```
10+
11+
Or build setting: `INFOPLIST_KEY_LSUIElement = YES`
12+
13+
## Search overlay (NSPanel)
14+
15+
- Subclass `NSPanel`, style `.nonactivatingPanel` + `.fullSizeContentView`
16+
- `level = .floating`, `isOpaque = false`, `backgroundColor = .clear`
17+
- `collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]`
18+
- `isMovableByWindowBackground = false`
19+
- Click-outside: global `NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown)`
20+
21+
## Global hotkey (Carbon, no deps)
22+
23+
- Key code `46` = M (US keyboard)
24+
- Modifiers: `cmdKey | shiftKey`
25+
- `RegisterEventHotKey` + `InstallEventHandler` for `kEventHotKeyPressed`
26+
27+
## App Sandbox
28+
29+
Disabled for MVP (`ENABLE_APP_SANDBOX = NO`) — required for:
30+
- FSEvents on ~/Documents, ~/Desktop, ~/Downloads
31+
- Spawning Python subprocess with venv outside bundle
32+
33+
Re-enable with entitlements before App Store distribution.
34+
35+
## Credentials
36+
37+
Read from Keychain service `dev.moss.pikachu` or fall back to environment variables for dev.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Moss Integration
2+
3+
## Install
4+
5+
```bash
6+
./scripts/setup-moss-venv.sh
7+
export MOSS_PROJECT_ID=... MOSS_PROJECT_KEY=...
8+
```
9+
10+
Uses **PyPI `moss>=1.6.0`**, not editable install from `vendor/moss/sdks/python/sdk` (main branch lacks sessions).
11+
12+
`vendor/moss` submodule is for examples/reference only.
13+
14+
## Worker JSON protocol (line-delimited)
15+
16+
### Request → Response
17+
18+
| action | input | output |
19+
|--------|-------|--------|
20+
| `ping` | `{}` | `{"status":"ok"}` |
21+
| `init_session` | `{"index_name":"documents"}` | `{"status":"ok","doc_count":N}` |
22+
| `add_docs` | `{"files":["/path/a.md"]}` | `{"status":"ok","chunks_indexed":N,"file_chunk_counts":{"/path/a.md":3}}` |
23+
| `query` | `{"query":"text","top_k":5}` | `{"results":[...],"timing_ms":4.2}` |
24+
| `save_session` | `{"cache_path":"..."}` | `{"status":"ok","doc_count":N}` |
25+
| `clear_index` | `{}` | `{"status":"ok"}` |
26+
27+
### Error shape
28+
29+
```json
30+
{"error": "message"}
31+
```
32+
33+
## Moss SDK calls (verified PyPI 1.6.0)
34+
35+
```python
36+
client = MossClient(os.environ["MOSS_PROJECT_ID"], os.environ["MOSS_PROJECT_KEY"])
37+
session = await client.session(index_name="documents")
38+
await session.add_docs([DocumentInfo(id=path, text=content, metadata={"path": path, "filename": name})])
39+
results = await session.query("query", QueryOptions(top_k=5, alpha=0.6))
40+
session._inner.save_to_disk(cache_path) # local persistence only
41+
```
42+
43+
## Text extraction
44+
45+
- Supported: `.md`, `.txt`, `.rtf`, `.html` (BeautifulSoup), `.pdf` (pypdf), `.docx` (python-docx)
46+
- Skipped: `.notes`
47+
- Chunks: ~1800 chars with 300 char overlap; IDs like `path#chunk-0001`
48+
49+
## Dev credentials
50+
51+
Priority: `MOSS_PROJECT_ID`/`MOSS_PROJECT_KEY` env → Keychain → repo `.env` via `DotEnvLoader`
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Pitfalls
2+
3+
1. **Do not use Moss Swift SPM on macOS**`Package.swift` targets iOS only
4+
2. **Do not install Moss from GitHub main SDK** — no `SessionIndex`; use `pip install moss>=1.6.0`
5+
3. **No Python disk save API** — use in-memory session + Swift file manifest for relaunch
6+
4. **FSEvents permissions** — may need Full Disk Access; handle `start()` returning false gracefully
7+
5. **App Sandbox blocks subprocess** — disable sandbox for MVP or add `com.apple.security.cs.allow-unsigned-executable-memory`
8+
6. **Bundle resources**`moss_worker.py` must be in Copy Bundle Resources; resolve via `Bundle.main.url(forResource:withExtension:)`
9+
7. **@MainActor default** — Xcode 26 sets `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`; mark background work `nonisolated` or use `Task.detached`
10+
8. **Carbon hotkey in sandbox** — works without sandbox; test on real hardware not just simulator
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
4+
cd "$ROOT"
5+
python3 -m venv .venv
6+
source .venv/bin/activate
7+
pip install --upgrade pip
8+
pip install "moss>=1.6.0"
9+
echo "Moss venv ready at $ROOT/.venv"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
PHASE="${1:-1}"
4+
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
5+
cd "$ROOT"
6+
7+
case "$PHASE" in
8+
1)
9+
xcodebuild -project MossPikachu.xcodeproj -scheme MossPikachu -destination 'platform=macOS' build
10+
echo "Phase 1: build OK — run in Xcode and verify menu bar + ⌘⇧M overlay"
11+
;;
12+
2)
13+
test -f .venv/bin/python3 || { echo "Run ./scripts/setup-moss-venv.sh first"; exit 1; }
14+
.venv/bin/python3 -c "from moss import MossClient; print('moss import OK')"
15+
xcodebuild -project MossPikachu.xcodeproj -scheme MossPikachu -destination 'platform=macOS' build
16+
echo "Phase 2: Moss SDK + build OK"
17+
;;
18+
3)
19+
xcodebuild -project MossPikachu.xcodeproj -scheme MossPikachu -destination 'platform=macOS' build
20+
echo "Phase 3: build OK — run end-to-end search test manually"
21+
;;
22+
*)
23+
echo "Usage: validate-phase.sh [1|2|3]"
24+
exit 1
25+
;;
26+
esac
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# UI & Animations
2+
3+
## Search overlay sizing
4+
5+
- Width: 300pt (thought bubble)
6+
- Max height: 600pt (results scroll inside)
7+
- Corner radius: 12pt
8+
- Shadow: `radius: 20, y: 8, opacity: 0.25`
9+
10+
## Pikachu sizes
11+
12+
| Context | Size |
13+
|---------|------|
14+
| Menu bar | 32×32 pt |
15+
| Search overlay | 64×64 pt |
16+
| Hover scale | 1.05 |
17+
18+
## PetState animations
19+
20+
| State | Animation |
21+
|-------|-----------|
22+
| `idle` | breathe 1.0→1.02 (2s), blink every 5–8s random, tail ±2° every 3–5s |
23+
| `searching` | tail wag ±15° (0.6s loop), thinking dots |
24+
| `found(n)` | bounce 1.0→1.15 (0.4s ×2), ✨ sparkles |
25+
| `notFound` | head tilt ±8° (1s), sad expression |
26+
27+
Use SwiftUI `.animation(.easeInOut, value:)` and `Timer` for random idle intervals.
28+
29+
## Search input debounce
30+
31+
160ms after last keystroke before calling `SearchService.search`.

examples/moss-pikachu/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MOSS_PROJECT_ID=your_project_id
2+
MOSS_PROJECT_KEY=your_project_key

examples/moss-pikachu/.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Xcode
2+
build/
3+
DerivedData/
4+
*.xcuserstate
5+
xcuserdata/
6+
7+
# Python
8+
.venv/
9+
__pycache__/
10+
*.pyc
11+
12+
# Moss / secrets
13+
.env
14+
.env.local
15+
16+
# macOS
17+
.DS_Store
18+
19+
# Picklight promo (Remotion)
20+
promo/node_modules/
21+
promo/out/

0 commit comments

Comments
 (0)