{"id":47819288,"url":"https://github.com/romancha/salmon","last_synced_at":"2026-04-06T11:03:12.003Z","repository":{"id":342098210,"uuid":"1171966088","full_name":"Romancha/salmon","owner":"Romancha","description":"Sync engine for Bear notes — API server (hub) + macOS agent (bridge) + SalmonRun menu bar app","archived":false,"fork":false,"pushed_at":"2026-04-02T13:14:12.000Z","size":812,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-03T02:26:48.047Z","etag":null,"topics":["api-server","bear","bear-notes","golang","macos","menu-bar-app","note-taking","sqlite","swiftui","sync"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Romancha.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-03T20:02:56.000Z","updated_at":"2026-04-02T13:14:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Romancha/salmon","commit_stats":null,"previous_names":["romancha/bear-sync","romancha/salmon"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/Romancha/salmon","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romancha%2Fsalmon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romancha%2Fsalmon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romancha%2Fsalmon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romancha%2Fsalmon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Romancha","download_url":"https://codeload.github.com/Romancha/salmon/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Romancha%2Fsalmon/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31371650,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T17:53:18.093Z","status":"ssl_error","status_checked_at":"2026-04-03T17:53:17.617Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-server","bear","bear-notes","golang","macos","menu-bar-app","note-taking","sqlite","swiftui","sync"],"created_at":"2026-04-03T19:02:23.327Z","updated_at":"2026-04-03T19:02:30.535Z","avatar_url":"https://github.com/Romancha.png","language":"Go","readme":"# Salmon\n\nSyncs Bear notes with external consumers. Two components: **hub** (API server on VPS) and **salmon-run** (Mac agent that reads Bear SQLite).\n\n## Why Salmon?\n\nBear catches salmon — Bear.app is the source of truth for notes, and Salmon is the data that flows through the system. The salmon run (upstream migration) represents data flowing from Bear to the hub. The hub serves data downstream to consumers like a salmon stream. The bidirectional flow of data — notes flowing upstream from Bear to consumers, and write operations flowing back downstream to Bear — mirrors the salmon lifecycle.\n\n## Architecture\n\n### Components\n\n**Bear** — source of truth for all note content. Stores notes in a local SQLite database (Core Data schema). Salmon Run reads this database directly and applies writes via Bear's x-callback-url scheme.\n\n**Salmon Run** (`bin/salmon-run`) — Mac agent that runs on the same machine as Bear. Runs in daemon mode (`--daemon`) with a continuous sync loop, managed by SalmonRun.app. Reads Bear's SQLite, detects changes since the last run, pushes them to the hub, and pulls pending write operations from the hub to apply back to Bear via bear-xcall.\n\n**SalmonRun.app** — native macOS menu bar application that wraps the salmon-run binary. Provides a GUI for monitoring sync status, viewing logs, triggering manual syncs, and configuring settings. The salmon-run process runs as a managed child process in daemon mode.\n\n**Hub** (`bin/salmon-hub`) — API server that runs on a VPS. Acts as a read replica of Bear's notes and exposes a REST API for external consumers. Holds a write queue for consumer-initiated changes that need to propagate back to Bear.\n\n**Consumers** — external applications that read and write notes via the hub API. Each consumer is identified by name and authenticated with its own token. Multiple consumers can be configured simultaneously. Consumers communicate only with the hub; never touch Bear or Salmon Run directly.\n\n### System Overview\n\n```mermaid\ngraph TB\n    subgraph mac[\"Mac (user's machine)\"]\n        Bear[\"Bear.app\\n(SQLite source of truth)\"]\n        SalmonRunApp[\"SalmonRun.app\\n(menu bar UI)\"]\n        SalmonRun[\"salmon-run --daemon\\n(sync loop)\"]\n        bearxcall[\"bear-xcall CLI\\n(x-callback-url executor)\"]\n    end\n\n    subgraph vps[\"VPS\"]\n        Caddy[\"Caddy\\n(TLS reverse proxy)\"]\n        Hub[\"salmon-hub\\n(REST API + SQLite)\"]\n    end\n\n    Consumer[\"Consumer\\n(API client)\"]\n\n    SalmonRunApp -- \"manages process\\nstdout + IPC socket\" --\u003e SalmonRun\n    SalmonRun -- \"reads Bear SQLite\\n(read-only)\" --\u003e Bear\n    SalmonRun -- \"applies writes via\\nbear:// URL scheme\" --\u003e bearxcall\n    bearxcall -- \"x-callback-url\" --\u003e Bear\n    SalmonRun -- \"POST /api/sync/push\\n(bridge token)\" --\u003e Caddy\n    SalmonRun -- \"GET /api/sync/queue\\nPOST /api/sync/ack\" --\u003e Caddy\n    Caddy --\u003e Hub\n    Consumer -- \"GET/POST/PUT/DELETE /api/notes/, /api/tags/\\n(consumer token)\" --\u003e Caddy\n```\n\n### Note `sync_status` State Machine\n\nThe `sync_status` field on each hub note guards against write conflicts between consumers and Bear.\n\n```mermaid\nstateDiagram-v2\n    synced: synced\\n(normal state)\n    pending: pending_to_bear\\n(consumer write queued)\n    conflict: conflict\\n(Bear changed while write pending)\n\n    [*] --\u003e synced: Bear push (initial/delta)\n\n    synced --\u003e pending: consumer enqueues write\\n(POST/PUT/DELETE)\n    pending --\u003e synced: Salmon Run ACKs applied\n    pending --\u003e conflict: Bear push arrives\\nwith overlapping content change\n\n    conflict --\u003e synced: Salmon Run creates [Conflict] note\\nand ACKs conflict_resolved=true\n```\n\nWhile a note is `pending_to_bear`, Bear delta pushes do not overwrite `title`/`body` on the hub. Conflict detection is field-level: the hub snapshots Bear's title/body when transitioning to `pending_to_bear`, and a conflict is raised only if Bear changed a content field (title or body) that the consumer also changed. Metadata-only changes (e.g., opening the note in Bear) do not trigger a conflict. On conflict, Salmon Run creates a `[Conflict] Title` note in Bear instead of applying the queued write.\n\n### Write Actions\n\nConsumers can enqueue write operations via the hub API. Salmon Run picks them up and applies them to Bear via x-callback-url.\n\n| Action | Consumer API | Description |\n|---|---|---|\n| `create` | `POST /api/notes` | Create a new note |\n| `update` | `PUT /api/notes/{id}` | Update note title/body |\n| `add_tag` | `POST /api/notes/{id}/tags` | Add a tag to a note |\n| `trash` | `DELETE /api/notes/{id}` | Move note to trash |\n| `add_file` | `POST /api/notes/{id}/attachments` | Attach a file to a note (multipart, 5 MB limit) |\n| `archive` | `POST /api/notes/{id}/archive` | Archive a note |\n| `rename_tag` | `PUT /api/tags/{id}` | Rename a tag |\n| `delete_tag` | `DELETE /api/tags/{id}` | Delete a tag |\n\nAll mutating consumer endpoints require an `Idempotency-Key` header. Encrypted notes are read-only (403).\n\n## Prerequisites\n\n- Go 1.26+\n- Xcode Command Line Tools (for building bear-xcall and SalmonRun.app on macOS; provides `swiftc`)\n- Bear.app (for salmon-run)\n- bear-xcall CLI (built via `make build-xcall`, for salmon-run write operations; source in `tools/bear-xcall/`)\n\n## Build\n\n```\nmake build\n```\n\nBinaries are placed in `bin/salmon-hub`, `bin/salmon-run`, `bin/salmon-mcp`, `bin/bear-xcall.app`, and `bin/SalmonRun.app` (macOS only).\n\n## Hub Setup\n\n### Environment Variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `SALMON_HUB_HOST` | No | `127.0.0.1` | Listen host (`0.0.0.0` for Docker) |\n| `SALMON_HUB_PORT` | No | `7433` | Listen port |\n| `SALMON_HUB_DB_PATH` | Yes | — | Path to SQLite database file |\n| `SALMON_HUB_CONSUMER_TOKENS` | Yes | — | Consumer tokens in `name:token` format, comma-separated (e.g. `openclaw:secret1,myapp:secret2`) |\n| `SALMON_HUB_BRIDGE_TOKEN` | Yes | — | Bearer token for Salmon Run sync access |\n| `SALMON_HUB_ATTACHMENTS_DIR` | No | `attachments` | Directory for attachment file storage |\n\n### Running\n\n```\nexport SALMON_HUB_DB_PATH=/opt/salmon/data/hub.db\nexport SALMON_HUB_CONSUMER_TOKENS=\"openclaw:secret1,myapp:secret2\"\nexport SALMON_HUB_BRIDGE_TOKEN=\u003ctoken\u003e\n./bin/salmon-hub\n```\n\nThe hub listens on `127.0.0.1:PORT` (localhost only). Use a reverse proxy (e.g. Caddy) for TLS termination.\n\n### Systemd (production)\n\n```\nsudo cp deploy/salmon-hub.service /etc/systemd/system/\nsudo systemctl enable salmon-hub\nsudo systemctl start salmon-hub\n```\n\nCreate `/opt/salmon/.env` with the environment variables above.\n\n### Docker Compose (production)\n\n1. Create a `.env` file with your secrets:\n\n```\nSALMON_HUB_CONSUMER_TOKENS=\"openclaw:secret1,myapp:secret2\"\nSALMON_HUB_BRIDGE_TOKEN=\u003ctoken\u003e\nDOMAIN=salmon.example.com\n```\n\n2. Start the stack:\n\n```\ndocker compose up -d\n```\n\nThis starts the hub server and Caddy reverse proxy with automatic TLS. The hub is accessible only through Caddy (ports 80/443).\n\nTo check status:\n\n```\ndocker compose ps\ncurl https://your-domain.com/healthz\n```\n\nTo update to a new version:\n\n```\ndocker compose pull\ndocker compose up -d\n```\n\nData is persisted in Docker named volumes (`hub-data` for SQLite + attachments).\n\n#### Volume Permissions (Synology / bind mounts)\n\nThe hub container runs as non-root user `hub` (UID 1000). When using bind mounts, ensure the host directory is owned by UID 1000, otherwise SQLite will fail with `unable to open database file: out of memory (14)`:\n\n```\nmkdir -p /volume1/docker/salmon_hub/attachments\nchown -R 1000:1000 /volume1/docker/salmon_hub\n```\n\nThis is not needed for Docker named volumes — they inherit permissions from the image automatically.\n\n## Salmon Run Setup\n\n### Environment Variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `SALMON_HUB_URL` | Yes | — | Hub API URL (e.g. `https://salmon.example.com`) |\n| `SALMON_HUB_TOKEN` | Yes | — | Bearer token matching `SALMON_HUB_BRIDGE_TOKEN` |\n| `SALMON_BEAR_TOKEN` | Yes | — | Token for Bear x-callback-url API (any string, e.g. `openssl rand -base64 32`; Bear will prompt to allow access on first use) |\n| `SALMON_STATE_PATH` | No | `~/.salmon-state.json` | Path to salmon-run state file |\n| `SALMON_BEAR_DB_DIR` | No | `~/Library/Group Containers/9K33E3U3T4.net.shinyfrog.bear/Application Data` | Path to Bear Application Data directory |\n| `SALMON_SYNC_INTERVAL` | No | `300` | Sync interval in seconds (daemon mode only) |\n| `SALMON_IPC_SOCKET` | No | `~/.salmon.sock` | Unix socket path for IPC (daemon mode only) |\n\n### Running\n\n```\nexport SALMON_HUB_URL=https://salmon.example.com\nexport SALMON_HUB_TOKEN=\u003ctoken\u003e\nexport SALMON_BEAR_TOKEN=\u003ctoken\u003e\n./bin/salmon-run\n```\n\nCLI flags:\n- `--daemon` — run continuously with periodic sync (default interval: 5 minutes)\n- `--version` — print version and exit\n\nThe recommended way to run salmon-run is via the [Menu Bar App](#menu-bar-app-salmonrunapp), which manages it in daemon mode with a GUI.\n\n## Menu Bar App (SalmonRun.app)\n\nSalmonRun.app is a native macOS menu bar application (macOS 14+) that manages salmon-run as a child process in daemon mode and provides a GUI for monitoring and configuration.\n\n### Menu Bar UI\n\nThe app lives in the macOS menu bar with a sync icon that changes color based on status:\n\n```\n┌─────────────────────────┐\n│ ● Synced                │  ← green (idle), yellow (syncing), red (error)\n│ Last sync: 2 min ago    │\n│ ─────────────────────── │\n│ ▸ Sync Now              │  ← triggers immediate sync\n│ ─────────────────────── │\n│ Notes: 1,234            │\n│ Tags: 56                │\n│ Queue: 0 pending        │\n│ ─────────────────────── │\n│ ▸ View Logs...          │  ← opens log viewer window\n│ ▸ Settings...           │  ← opens settings window\n│ ─────────────────────── │\n│ ▸ Quit Salmon Run       │\n└─────────────────────────┘\n```\n\n### Features\n\n- Menu bar icon with color-coded sync status (green/yellow/red)\n- One-click \"Sync Now\" to trigger immediate sync\n- Live statistics: notes, tags, and write queue counts\n- Log viewer window with search, level filtering, and auto-scroll\n- Settings window with Hub URL, tokens (Keychain-secured), sync interval, and Launch at Login\n- macOS notifications on sync errors (rate-limited, configurable)\n- Auto-restart of salmon-run process on unexpected exit (up to 3 retries)\n\n### Settings\n\nSettings are accessible from the menu bar popup via \"Settings...\":\n\n| Tab | Setting | Storage | Description |\n|---|---|---|---|\n| Connection | Hub URL | UserDefaults | URL of your Salmon hub server |\n| Connection | Hub Token | Keychain | Salmon Run authentication token (matches `SALMON_HUB_BRIDGE_TOKEN`) |\n| Connection | Bear Token | Keychain | Token for Bear x-callback-url API |\n| Sync | Sync interval | UserDefaults | How often to sync (1-30 minutes, default 5) |\n| Sync | Sync on launch | Always on | Automatically syncs when the app starts |\n| General | Launch at Login | SMAppService | Auto-start SalmonRun.app on login |\n| General | Notifications | UserDefaults | Show macOS notifications on sync errors |\n\nTokens are stored securely in the macOS Keychain. All other settings use UserDefaults. The app generates environment variables for the salmon-run process from these settings.\n\n### Install\n\nDownload the .dmg for your architecture from the [Releases](../../releases) page:\n\n- `SalmonRun-vX.Y.Z-arm64.dmg` (Apple Silicon)\n- `SalmonRun-vX.Y.Z-amd64.dmg` (Intel)\n\nOpen the .dmg and drag SalmonRun.app to `/Applications`. Launch from Applications and configure your Hub URL and tokens in Settings.\n\nFrom source:\n\n```\nmake build-app\ncp -R bin/SalmonRun.app /Applications/\n```\n\n### Build\n\n```\nmake build-app       # Build SalmonRun.app (includes salmon-run + bear-xcall)\nmake test-app        # Run Swift tests\n```\n\n## Reverse Proxy\n\nA sample Caddyfile is provided in `deploy/Caddyfile` for systemd setup. For Docker Compose, `deploy/Caddyfile.docker` is used automatically.\n\nThe sample Caddyfile uses rate limiting, which requires the [caddy-ratelimit](https://github.com/mholt/caddy-ratelimit) plugin. Build Caddy with this plugin using `xcaddy`:\n\n```\nxcaddy build --with github.com/mholt/caddy-ratelimit\n```\n\n## API Documentation\n\nThe hub serves interactive API documentation via Swagger UI at `/api/docs/` (requires consumer auth).\n\nTo regenerate the OpenAPI spec after changing handler annotations:\n\n```\nmake swagger\n```\n\nFor a quick start guide with curl examples and integration details, see [docs/consumer-api.md](docs/consumer-api.md).\n\n## MCP Server\n\nThe MCP (Model Context Protocol) server allows AI assistants like Claude Code, Cursor, and OpenClaw to interact with Bear notes natively — without shell commands or manual API calls.\n\nThe server exposes all 13 consumer API operations as MCP tools over stdio transport.\n\n### Install\n\nDownload the latest release for your platform from [GitHub Releases](https://github.com/Romancha/salmon/releases):\n\n```bash\n# macOS (Apple Silicon)\ncurl -fsSL -o salmon-mcp https://github.com/Romancha/salmon/releases/latest/download/salmon-mcp-darwin-arm64\n\n# macOS (Intel)\ncurl -fsSL -o salmon-mcp https://github.com/Romancha/salmon/releases/latest/download/salmon-mcp-darwin-amd64\n\n# Linux (arm64)\ncurl -fsSL -o salmon-mcp https://github.com/Romancha/salmon/releases/latest/download/salmon-mcp-linux-arm64\n\n# Linux (amd64)\ncurl -fsSL -o salmon-mcp https://github.com/Romancha/salmon/releases/latest/download/salmon-mcp-linux-amd64\n```\n\nMake it executable and move to a directory in your PATH:\n\n```bash\nchmod +x salmon-mcp\nsudo mv salmon-mcp /usr/local/bin/\n```\n\nVerify the installation:\n\n```bash\nsalmon-mcp --version\n```\n\n\u003e Alternatively, build from source: `make build-mcp` (requires Go 1.26+). The binary will be in `bin/salmon-mcp`.\n\n### Setup\n\nYou need two things before configuring an MCP client:\n\n1. **Hub URL** — the address of your running Salmon Hub instance (e.g. `https://salmon.example.com`)\n2. **Consumer token** — a Bearer token configured in the hub's `SALMON_HUB_CONSUMER_TOKENS` env var\n\n| Variable | Required | Description |\n|---|---|---|\n| `SALMON_HUB_URL` | Yes | Hub API URL |\n| `SALMON_CONSUMER_TOKEN` | Yes | Consumer Bearer token |\n\n### Claude Code\n\nAdd to `.claude/mcp.json` (project-level) or `~/.claude/mcp.json` (global):\n\n```json\n{\n  \"mcpServers\": {\n    \"salmon\": {\n      \"command\": \"salmon-mcp\",\n      \"env\": {\n        \"SALMON_HUB_URL\": \"https://salmon.example.com\",\n        \"SALMON_CONSUMER_TOKEN\": \"your-token\"\n      }\n    }\n  }\n}\n```\n\nAfter saving, restart Claude Code. Tools will appear automatically — no approval prompts needed.\n\n### Cursor\n\nAdd to `.cursor/mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"salmon\": {\n      \"command\": \"salmon-mcp\",\n      \"env\": {\n        \"SALMON_HUB_URL\": \"https://salmon.example.com\",\n        \"SALMON_CONSUMER_TOKEN\": \"your-token\"\n      }\n    }\n  }\n}\n```\n\n### OpenClaw\n\nAdd to your `openclaw.json` (or use the [OpenClaw skill](tools/consumer/openclaw/bear-salmon-notes/) for a curl-based alternative):\n\n```json\n{\n  \"mcp\": {\n    \"servers\": {\n      \"salmon\": {\n        \"command\": \"salmon-mcp\",\n        \"env\": {\n          \"SALMON_HUB_URL\": \"https://salmon.example.com\",\n          \"SALMON_CONSUMER_TOKEN\": \"your-token\"\n        }\n      }\n    }\n  }\n}\n```\n\nOr via CLI:\n\n```bash\nopenclaw mcp set salmon '{\"command\":\"salmon-mcp\",\"env\":{\"SALMON_HUB_URL\":\"https://salmon.example.com\",\"SALMON_CONSUMER_TOKEN\":\"your-token\"}}'\n```\n\n### Available Tools\n\n| Tool | Description |\n|---|---|\n| `search_notes` | Full-text search across notes |\n| `get_note` | Get a note by ID (includes tags, attachments, backlinks) |\n| `list_notes` | List notes with optional filters |\n| `list_tags` | List all tags |\n| `get_attachment` | Get attachment content (base64) |\n| `sync_status` | Get sync status metadata |\n| `list_backlinks` | List backlinks for a note |\n| `create_note` | Create a new note |\n| `update_note` | Update note title/body |\n| `trash_note` | Move a note to trash |\n| `archive_note` | Archive a note |\n| `add_tag` | Add a tag to a note |\n| `rename_tag` | Rename a tag |\n| `delete_tag` | Delete a tag |\n\n## Development\n\n```\nmake test          # run all tests\nmake test-race     # run tests with race detector\nmake test-xcall    # run bear-xcall manual tests (macOS + Bear)\nmake test-app      # run SalmonRun Swift tests (macOS only)\nmake build-mcp     # build salmon-mcp binary\nmake build-xcall   # build bear-xcall .app bundle (macOS only)\nmake build-app     # build SalmonRun.app menu bar app (macOS only)\nmake lint          # run golangci-lint\nmake fmt           # format code\nmake tidy          # go mod tidy\nmake swagger       # generate Swagger docs (swag init)\n```\n\n## CI/CD\n\nGitHub Actions runs automatically:\n\n- **CI** (push/PR to main): lint, test, test with race detector\n- **Docker Publish** (push tag `v*`): builds multi-platform hub image (`linux/amd64`, `linux/arm64`) and pushes to `ghcr.io/romancha/salmon-hub`\n- **Release** (push tag `v*`): builds, signs, notarizes, and publishes SalmonRun.app as .dmg for macOS (`arm64`, `amd64`) as GitHub Release assets\n\n### Publishing a release\n\nTag and push to trigger both Docker and release workflows:\n\n```\ngit tag v0.1.0\ngit push origin v0.1.0\n```\n\nPre-release tags (e.g., `v0.1.0-rc.1`) are automatically marked as pre-releases on GitHub.\n\n### Required GitHub secrets for release\n\nThe release workflow requires Apple code signing credentials. Set these in the repository settings under Settings \u003e Secrets and variables \u003e Actions:\n\n| Secret | Description |\n|---|---|\n| `APPLE_CERTIFICATE` | Base64-encoded Developer ID Application .p12 certificate (`base64 -i cert.p12 \\| pbcopy`) |\n| `APPLE_CERTIFICATE_PASSWORD` | Password for the .p12 certificate |\n| `APPLE_TEAM_ID` | Apple Developer Team ID |\n| `APPLE_ID` | Apple ID email for notarytool authentication |\n| `APPLE_ID_PASSWORD` | App-specific password for notarytool (generate at [appleid.apple.com](https://appleid.apple.com/account/manage)) |\n\nSetup steps:\n1. Export your Developer ID Application certificate as .p12 from Keychain Access\n2. Base64-encode it: `base64 -i cert.p12 | pbcopy`\n3. Create an app-specific password at https://appleid.apple.com/account/manage\n4. Add all five secrets in the repository settings\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromancha%2Fsalmon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromancha%2Fsalmon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromancha%2Fsalmon/lists"}