Skip to content

feat: add apple-photos skill for macOS Photos library#91

Merged
TechNickAI merged 2 commits into
mainfrom
feat/apple-photos-skill
Apr 19, 2026
Merged

feat: add apple-photos skill for macOS Photos library#91
TechNickAI merged 2 commits into
mainfrom
feat/apple-photos-skill

Conversation

@TechNickAI
Copy link
Copy Markdown
Owner

Summary

  • Adds new apple-photos skill — single UV script with people, query, and export subcommands wrapping osxphotos
  • Consolidates raw prototype (3 separate scripts from live instance) into repo-convention single executable
  • 38 tests (unit with mocked PhotosDB + integration against real Photos library)

What changed from the prototype

  • Collision-safe exports — appends _1, _2 suffixes instead of silently overwriting
  • Safe default limit — export defaults to 50 (was unbounded 0)
  • Shared query builderbuild_query_options used by both query and export (was duplicated)
  • Per-file error handlingshutil.copy2 failures skip with report instead of killing the run
  • Actionable PhotosDB errors — wraps init with clear error message
  • Removed ambiguous path fieldphoto_to_dict now has original_path/edited_path only
  • Python >=3.13 — matches repo convention
  • No PII — all examples use <NAME> placeholders

Test plan

  • 38 pytest tests pass (unit + integration)
  • Pre-commit hooks pass (ruff, prettier, etc.)
  • Multi-agent code review (logic, robustness, style) — all findings addressed
  • Integration tests verified against real Photos library on macOS

🤖 Generated with Claude Code

Consolidates raw prototype (3 separate scripts) into a single UV script
with people/query/export subcommands following repo conventions.

Includes collision-safe exports, per-file error handling, shared query
builder, and 38 tests (unit + integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0dd2fd2bec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/apple-photos/apple-photos Outdated
Comment on lines +121 to +122
movies=getattr(args, "movies", True),
newest_first=getattr(args, "newest_first", True),
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #107. Export now uses BooleanOptionalAction with default=True for both --movies and --newest-first, making defaults consistent with the documented "default: on" behavior while allowing opt-out via --no-movies / --no-newest-first.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecff93d. Added --movies and --newest-first explicitly to the export parser with default=True, and dropped the opaque getattr(..., True) fallback. Both subcommands now use the same build_query_options path with consistent, visible defaults.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core bug (un-toggleable flags always sending movies=True) is fixed in PR #105 via BooleanOptionalAction. The remaining default difference (export defaults movies/newest_first to True, query to False) is intentional: export is a batch-copy-everything operation while query is a targeted preview. Users can use --no-movies on export to match query behavior.

# Pre-register a mock osxphotos so the import doesn't fail on non-macOS/CI
_mock_osxphotos = MagicMock()
_mock_osxphotos.QueryOptions = MagicMock
sys.modules.setdefault("osxphotos", _mock_osxphotos)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #107. Test now checks importlib.util.find_spec("osxphotos") before injecting the mock (line 21), using find_spec which does not import. The mock setup (line 28) uses setdefault to preserve the real package if already imported.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecff93d. Moved availability check before mock injection using importlib.util.find_spec('osxphotos') — which inspects the package finder without importing, so it's unaffected by the mock in sys.modules. Integration tests will now skip correctly on machines without the real package.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect — is computed on line 21 via (which doesn't import) before the mock is injected via on line 28. The comment in the code even says so explicitly: "Check real package availability BEFORE injecting the mock — find_spec doesn't import." The mock has no effect on an already-computed boolean.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect analysis. HAS_OSXPHOTOS is computed at line 21 using importlib.util.find_spec() before the mock is injected at line 28. find_spec() checks the import system without importing, so it correctly detects whether the real package is available. The comment on line 20 even notes: "Check real package availability BEFORE injecting the mock — find_spec doesn't import."

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👎 Incorrect analysis. Line 21 (HAS_OSXPHOTOS = importlib.util.find_spec(...)) runs before the mock is injected at lines 26–28. The comment above it even says "Check real package availability BEFORE injecting the mock — find_spec doesn't import." find_spec detects installed packages without triggering an import, so the check is correct and intentional.

…etection

- Add --movies and --newest-first explicitly to export parser (fixes hidden
  default divergence between query and export subcommands)
- Use find_spec() to detect real osxphotos before injecting mock, so
  HAS_OSXPHOTOS is correct on machines without the package installed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@TechNickAI TechNickAI merged commit afe68a0 into main Apr 19, 2026
10 checks passed
@TechNickAI TechNickAI deleted the feat/apple-photos-skill branch April 19, 2026 15:02
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ecff93d. Configure here.

from_date=parse_date(args.after),
to_date=parse_date(args.before),
favorite=True if getattr(args, "favorite", False) else None,
edited=True if getattr(args, "edited", False) else None,
Copy link
Copy Markdown

@cursor cursor Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #107. Added skip_edited_filter parameter to build_query_options(). Export uses skip_edited_filter=True to avoid DB-level filtering, then implements "prefer edited" semantics at file selection time (line 169). Non-edited photos are now included as documented.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch — confirmed bug. Fixed in PR #105 by adding edited_filter=False for the export path. build_query_options now accepts a keyword-only edited_filter param (defaults to True for query). Export passes False so QueryOptions doesn't pre-filter, letting cmd_export's source-selection logic handle the "prefer edited" intent correctly.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #105 (commit 2b47a03). The build_query_options function now accepts edited_filter=False for the export path, so QueryOptions doesn't pre-filter to edited-only — leaving the source-selection logic in cmd_export to do the "prefer edited" work correctly.

p_export.add_argument("--before", help="Photos before this date (YYYY-MM-DD)")
p_export.add_argument("--edited", action="store_true", help="Prefer edited versions")
p_export.add_argument("--movies", action="store_true", default=True, help="Include movies (default: on)")
p_export.add_argument("--newest-first", action="store_true", default=True, help="Sort newest first (default: on)")
Copy link
Copy Markdown

@cursor cursor Bot Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #107. Changed from action="store_true" with default=True to action=argparse.BooleanOptionalAction with default=True, enabling --no-movies and --no-newest-first opt-out flags.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real bug. Fixed in PR #105 by switching both flags to argparse.BooleanOptionalAction, which adds --no-movies / --no-newest-first and gives the "default on, opt-out possible" behavior the help text implied.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in PR #105 (commit 2b47a03). Switched --movies and --newest-first on export to argparse.BooleanOptionalAction, which adds --no-movies / --no-newest-first and gives users the opt-out the help text implied.

TechNickAI pushed a commit that referenced this pull request Apr 20, 2026
The --edited flag on export was documented as "prefer edited versions" but was
incorrectly filtering out all non-edited photos via QueryOptions. This fix adds a
filter_edited parameter to build_query_options() that allows export to disable the
filter while still using the flag as a preference in the source selection logic.

Now export includes all photos and just prefers edited versions when available,
matching the documented behavior.

Addresses cursor[bot] feedback on PR #91.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
TechNickAI added a commit that referenced this pull request Apr 28, 2026
…#107)

- --edited on export now queries all matching photos and prefers the
  edited file path at copy time (was incorrectly filtering to only-edited
  photos at the DB level, silently excluding non-edited photos)
- --movies and --newest-first on export now use BooleanOptionalAction so
  users can pass --no-movies / --no-newest-first to opt out (was store_true
  + default=True, making them permanent no-ops with no opt-out path)
- Bump apple-photos skill to 0.1.1

Co-authored-by: Hex Sullivan <hex@technick.ai>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant