Skip to content
Merged
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
26 changes: 0 additions & 26 deletions .claude/skills/auto-tune/SKILL.md

This file was deleted.

28 changes: 0 additions & 28 deletions .claude/skills/create-social-video/SKILL.md

This file was deleted.

62 changes: 0 additions & 62 deletions .claude/skills/critique-video/SKILL.md

This file was deleted.

56 changes: 56 additions & 0 deletions .claude/skills/cull-shoot/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: cull-shoot
description: Import a folder of photos/videos into the Keeper library (or work on what's already there) and cull it — confirm the pipeline's verdicts, visually review the borderline items, pick the best frame of each burst, and leave a clean picks/rejects split. Use when the user wants a shoot, trip, or SD-card dump culled.
---

# Cull a shoot

You are culling for a real person: the goal is that their **picks are postable/editable keepers** and their **rejects are safely discardable junk**. You can look at images directly — use that superpower where the local heuristics can't decide.

## Ground rules

- The library home is `~/Pictures/Keeper` unless `KEEPER_LIBRARY_DIR` is set. Pass `--library <home>` to every script if the user gave a custom location.
- **Never delete or move media files.** Verdicts are flags; the user empties rejects themselves in the app.
- Every CLI below prints JSON. Scripts live in `app/scripts/`.

## Workflow

1. **Import** (skip if the media is already in the library):

```bash
node app/scripts/import.mjs --source /path/to/dump
```

This copies (checksummed, deduped), extracts metadata, generates thumbnails, measures quality, suggests verdicts, groups bursts, and builds the search index. Resume an interrupted run with `node app/scripts/reprocess.mjs`.

2. **Read the queues**:

```bash
node app/scripts/query.mjs --review --limit 100
```

Three buckets: `sure-reject` (high-confidence junk), `sure-keep`, `needs-eye` (borderline). Each item includes `thumbPath` — an absolute path to its thumbnail.

3. **Spot-check the sure queues.** View a handful of `sure-reject` thumbnails with the Read tool. If they are genuinely junk (blur, black frames, misfires), confirm the whole queue:

```bash
node app/scripts/verdict.mjs --ids <comma-separated-ids> --flag reject
```

Same for `sure-keep` with `--flag pick`. If a spot-check reveals a wrong call, DO NOT bulk-confirm — review that queue item by item.

4. **Review `needs-eye` visually.** Read each thumbnail. Judge like a photo editor: moment > technical perfection. A soft photo of a real moment (laughter, a kiss, the peak of action) is a **pick**; a tack-sharp photo of nothing is not automatically one. Set verdicts in batches.

5. **Resolve bursts.** Items sharing a `groupId` are one moment. View all frames of a group, pick the best (eyes open, expression, framing), then:

```bash
node app/scripts/verdict.mjs --group <groupId> --best <assetId>
node app/scripts/verdict.mjs --ids <bestId> --flag pick
node app/scripts/verdict.mjs --ids <otherIds> --flag reject
```

6. **Report.** Summarize honestly: counts per verdict, anything you were unsure about (leave those unrated rather than guessing), and any pattern worth adding as a taste rule.

## Taste

Your verdicts feed `taste.json` exactly like the user's own (contradictions of AI suggestions become learning exemplars). Read the profile first if it exists — `<home>/taste.json` — and respect its `rules`.
26 changes: 26 additions & 0 deletions .claude/skills/find-media/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: find-media
description: Find photos/videos in the Keeper library from a vague natural-language memory ("the ocean at sunset", "the kids at that dinner in June") using semantic search, then visually verify the hits. Use when the user is looking for specific media they half-remember.
---

# Find media

The library ships a local CLIP index — semantic search runs offline and returns ranked candidates. Your job is to turn a fuzzy memory into verified results.

## Workflow

1. **Search** (scripts in `app/scripts/`, `--library <home>` if non-default):

```bash
node app/scripts/query.mjs --search "ocean at sunset" --limit 24
```

Results are ranked by similarity and include `thumbPath` + `originalPath`. If it errors with "no embeddings yet", run `node app/scripts/reprocess.mjs --stage embed` first (downloads the model on first run).

2. **Vary the phrasing.** CLIP responds to concrete visual language. Try 2–3 reformulations: "waves at golden hour", "beach silhouette dusk". Merge candidates.

3. **Verify visually.** Read the top thumbnails and keep only genuine matches — semantic scores are suggestive, not proof. The user asked for a memory, not a similarity list.

4. **Narrow by metadata when the user gave constraints.** Capture dates are in each record (`capturedAt`); "second week of the trip" means filtering the date range yourself. `query.mjs --asset <id>` returns the full record.

5. **Present**: absolute original paths + one-line why-it-matches each. If the user wants them exported, set `--flag pick` via `verdict.mjs` and point them at Export in the app (or run `node app/scripts/export.mjs --dest <folder> --ids <ids> --xmp`).
31 changes: 0 additions & 31 deletions .claude/skills/learn-aesthetic/SKILL.md

This file was deleted.

33 changes: 33 additions & 0 deletions .claude/skills/organize-library/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: organize-library
description: Organize and enrich the Keeper library — caption and tag media for better search, name events, surface duplicates across imports, and produce a library health report. Use when the user wants their library "organized", "tagged", or "cleaned up".
---

# Organize the library

Keeper's catalog stores tags and captions per asset; search and export both use them. You can look at thumbnails directly and write enrichment back through the CLIs (scripts in `app/scripts/`, add `--library <home>` for non-default locations).

## Workflow

1. **Assess**:

```bash
node app/scripts/query.mjs --counts
node app/scripts/query.mjs --imports
```

Report totals, unrated backlog, and how much of the search index is built (run `reprocess.mjs` for anything missing).

2. **Enrich where it pays.** Captions/tags come from the LLM judge when configured:

```bash
node app/scripts/judge-llm.mjs --budget 200
```

It only spends budget on borderline/unjudged items and writes captions + lowercase tags. If the user wants deeper coverage, raise the budget explicitly — never silently.

3. **Name events.** List days (`query.mjs --counts` shows the day count; `--list unrated --limit …` etc. include `capturedAt`). Cluster consecutive days with media into events, read a few thumbnails per day, and propose names ("Kyoto — days 3–5"). Write them as tags on the day's assets via the catalog CLI if the user approves.

4. **Surface cross-import duplicates.** Exact duplicates are already blocked at import (content hash). Near-duplicates across sessions show up as high-similarity search hits — flag suspicious pairs for the user rather than auto-rejecting.

5. **Report.** What was tagged, what events were named, what the user should review. Nothing in this skill deletes or moves media.
12 changes: 7 additions & 5 deletions .cursor/rules/design-system.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ alwaysApply: false
- Colors come from `styles/tokens.css` custom properties (`--background-primary`, `--foreground-secondary`, `--border-primary`, ...). Both themes must work — check `:root[data-theme="dark"]` derivations before adding a raw hex.
- Radii/spacing/typography: `--radius-*`, `--space-*`, `--font-ui`, `--font-brand`.
- Needing a "slightly different" color? Prefer `color-mix(in srgb, var(--token) N%, var(--other-token))` over new hex values.
- Verdict semantics are the one hardcoded exception: keep-green `#2f9e63`, reject-red `#c4384f`, star-gold `#f5c451` (used identically in both themes).

## Components
- Reuse the UI kit first: `Button`, `IconButton`, `Badge`, `Modal`, `Field`/`Input`/`TextArea`/`Select`, `Icon` from `components/ui`.
Expand All @@ -18,10 +19,11 @@ alwaysApply: false
- Dialogs/dropdowns must close on Escape (`useEscapeKey`) and outside click.

## CSS placement
- Editor chrome styles: `styles/editor.css`. UI kit: `styles/ui.css`. Home + legacy: `styles.css`. Delete dead CSS when retiring a component.
- File inputs are hidden `<input type="file">` + a styled `upload-area` div; snapshot `FileList` synchronously (it's live and emptied by `input.value = ""`).
- Library UI styles: `styles/library.css`. UI kit: `styles/ui.css`. Delete dead CSS when retiring a component.
- Media loads through the `keeper-asset://home/<rel>` protocol via the helpers in `lib/format.ts` (`thumbUrl`/`previewUrl`/`originalUrl`) — never build protocol URLs inline.
- Grid thumbnails use `loading="lazy"` and day sections lazy-load via IntersectionObserver — keep it that way; a 50k library must open instantly.

## Store & interaction
- Every EDL mutation goes through `updateEdl(mutate)` — it's one undo step and schedules the debounced save. Batch multi-asset operations into ONE `updateEdl` call.
- Editor-wide shortcuts live in `App.tsx` (Cmd+Z, T, Cmd+\, Space) — never inside a component that unmounts (e.g. Timeline in focus mode). Always guard: skip when typing in INPUT/TEXTAREA/SELECT/contentEditable.
- Long text/media lists: cap visible rows and scroll within (`clip-list-capped` pattern).
- Every verdict mutation goes through `useKeeper.setVerdict(ids, patch)` — it's optimistic, one undo step, and records the taste signal server-side. Batch multi-asset operations into ONE call.
- App-wide shortcuts live in `App.tsx` only (P/K keep, X/R reject, U unrated, 0–5 stars, Space loupe, arrows navigate, Cmd+Z undo, T theme, Cmd+F search) — never inside a component that unmounts. Always guard: skip when typing in INPUT/TEXTAREA/SELECT/contentEditable.
- Records are normalized in one `Map` (`records`); section/search/queue views hold ids only. Merge fetched assets with `mergeRecords`, never store copies.
Loading
Loading