che-ical-mcp

Servidor MCP nativo para Calendário e Lembretes do macOS via Apple EventKit — eventos, lembretes, recorrência, gatilhos de localização, verificação de conflitos e operações em lote no iCloud/Google/Exchange.

Documentação

che-ical-mcp

License: MIT macOS Swift MCP

Dê ao Claude controle nativo do Calendário e Lembretes do macOS. Um servidor MCP em Swift construído diretamente sobre o EventKit — 29 ferramentas para eventos, lembretes, tags, operações em lote, detecção de conflitos e desfazer/refazer. Não apenas eventos de calendário: ele também controla Lembretes e tarefas.

English | 繁體中文


Instalação

Claude Code — registre este repositório como um marketplace e instale o plugin. O plugin inclui os comandos de barra /today, /week, /quick-event, /remind e um hook PreToolUse que verifica o dia da semana em cada gravação de evento:

claude plugin marketplace add PsychQuant/che-ical-mcp
claude plugin install che-ical-mcp@che-ical-mcp

Claude Desktop — baixe o .mcpb mais recente em Releases e clique duas vezes para instalar.

MCP autônomo — o servidor com 29 ferramentas por conta própria, sem extras do plugin:

mkdir -p ~/bin
curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

No primeiro uso, o macOS solicita acesso a Calendário e Lembretes — clique em Permitir. Compilando a partir do código-fonte, atualizando no local ou executando via SSH / launchd / VS Code? Consulte Instalação para o guia completo.


Por que che-ical-mcp?

RecursoOutros MCPs de Calendárioche-ical-mcp
Eventos de CalendárioSimSim
Lembretes/TarefasNãoSim
#Tags de LembreteNãoSim (nível MCP)
Busca por múltiplas palavras-chaveNãoSim
Detecção de duplicatasNãoSim
Detecção de conflitosNãoSim
Operações em loteNãoSim
Fuso horário localNãoSim
Desambiguação de origemNãoSim
Criar calendárioAlgunsSim
Excluir calendárioAlgunsSim
Lembretes de eventoAlgunsSim
Local e URLAlgunsSim
LinguagemPythonSwift (nativo)

Todas as 29 Ferramentas

Calendários (4)
FerramentaDescrição
list_calendarsLista todos os calendários e listas de lembretes (inclui source_type)
create_calendarCria um novo calendário
delete_calendarExclui um calendário
update_calendarRenomeia um calendário ou altera sua cor (v0.9.0)
Eventos (4)
FerramentaDescrição
list_eventsLista eventos com filtro/ordenação/limite (v1.0.0)
create_eventCria um evento (com lembretes, local, URL, fuso horário por evento, recorrência com datas excluídas)
update_eventAtualiza um evento (incluindo fuso horário, recorrência, abrangência para recorrentes)
delete_eventExclui um evento (com suporte a ocorrências para recorrentes)

Exclusões de recorrência (#182): create_event (e cada item de create_events_batch) aceita recurrence.excluded_occurrence_dates para pular datas específicas em uma série recorrente no momento da criação — aplicado com semântica de melhor esforço tudo-ou-nada (qualquer falha remove toda a nova série via exclusão compensatória; falha de reversão é relatada, nunca silenciosa; a primeira ocorrência não pode ser excluída), e um undo remove toda a série incluindo exclusões. Limitação conhecida: em nova tentativa idempotente, uma série recorrente duplicada com todas as datas solicitadas já ausentes é skipped, mas exclusões extras já presentes na série existente (além das solicitadas) não são detectadas.

Lembretes (8)
FerramentaDescrição
list_remindersLista lembretes com filtro/ordenação/limite, extração de tags (v1.0.0)
create_reminderCria um lembrete com data de vencimento, tags (v1.3.0)
update_reminderAtualiza um lembrete (incluindo tags, clear_due_date) (v1.3.0)
complete_reminderMarca como concluído/não concluído
delete_reminderExclui um lembrete
search_remindersBusca lembretes por palavra(s)-chave ou tag (v1.3.0)
list_reminder_tagsLista todas as tags exclusivas com contagens de uso (v1.3.0)
cleanup_completed_remindersExclui todos os lembretes concluídos em uma única chamada, pré-visualização dry_run por padrão (v1.7.2)

Lembretes recorrentes (#194): listar/buscar inclui has_recurrence, concluir recurrence_rules público, e due ciente de precisão. A conclusão adiciona operation (resultado de gravação) e next_occurrence (confirmado/desconhecido/não_aplicável). Use operation.status em vez do legado is_completed: uma conclusão bem-sucedida pode deixar a próxima ocorrência incompleta. Informações desconhecidas sobre o sucessor não devem acionar uma segunda gravação. Um sucessor com ID diferente ou não confirmável é relatado como unknown, nunca como a série tendo terminado. Desfazer uma conclusão recorrente é protegido por identidade (#204): quando o identificador não resolve mais para a ocorrência registrada, ele recusa explicitamente e descarta sua entrada de histórico em vez de travar a pilha. Quebra (#205): completed deve ser um booleano JSON em complete_reminder / list_reminders / search_reminders — strings e números são rejeitados; omitir ou null mantém o significado antigo. Quebra (#207, não lançado): o mesmo contrato agora se aplica a todo argumento booleano de ferramenta (all_day, clear_*, include_completed, dry_run, delete_original). Consulte contrato de resposta e limitações.

Recursos Avançados (10) ✨ Novo na v0.3.0+
FerramentaDescrição
search_eventsBusca eventos por palavra(s)-chave com correspondência E/OU
list_events_quickAtalhos rápidos: today, tomorrow, this_week, next_7_days, etc.
create_events_batchCria vários eventos de uma vez (com fuso horário por evento)
check_conflictsVerifica eventos sobrepostos em um intervalo de tempo
copy_eventCopia um evento para outro calendário (com movimentação opcional)
move_events_batchMove vários eventos para outro calendário
delete_events_batchExclui eventos por IDs ou intervalo de datas, com pré-visualização dry-run (v1.0.0)
find_duplicate_eventsEncontra eventos duplicados entre calendários (v0.5.0)
create_reminders_batchCria vários lembretes de uma vez (v0.9.0)
delete_reminders_batchExclui vários lembretes de uma vez (v0.9.0)
Desfazer/Refazer (3) ✨ Novo na v1.4.0
FerramentaDescrição
undoDesfaz a operação mais recente de calendário/lembrete
redoRefaz a última operação desfeita
undo_historyLista operações desfazíveis com carimbos de data/hora

Instalação

Os caminhos rápidos estão no topo deste README. Esta é a referência completa — configuração manual, compilações a partir do código-fonte, casos extremos de permissão, atualizações no local e modo CLI.

Requisitos

  • macOS 14.0+ (Sonoma ou posterior — necessário desde a v1.11.0 para a API completa de permissão TCC)
  • Ferramentas de linha de comando do Xcode (apenas se compilar a partir do código-fonte)

Claude Desktop

Um clique (recomendado): baixe o che-ical-mcp-<version>.mcpb mais recente em Releases, clique duas vezes e reinicie o Claude Desktop.

Configuração manual: baixe o binário e aponte claude_desktop_config.json para ele.

curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o /usr/local/bin/che-ical-mcp
chmod +x /usr/local/bin/che-ical-mcp

Edite ~/Library/Application Support/Claude/claude_desktop_config.json e reinicie o Claude Desktop:

{
  "mcpServers": {
    "che-ical-mcp": {
      "command": "/usr/local/bin/che-ical-mcp"
    }
  }
}

Claude Code — plugin (recomendado)

claude plugin marketplace add PsychQuant/che-ical-mcp
claude plugin install che-ical-mcp@che-ical-mcp
  • Dentro do Claude Code, os equivalentes de barra /plugin marketplace add PsychQuant/che-ical-mcp e /plugin install che-ical-mcp@che-ical-mcp funcionam da mesma forma.
  • Adicione o marketplace por meio do repositório Git (owner/repo), não por uma URL marketplace.json bruta — o source do plugin é um caminho relativo do mesmo repositório (./plugin) que só é resolvido quando adicionado via Git.
  • Também incluído no agregador psychquant-claude-plugins (claude plugin install che-ical-mcp@psychquant-claude-plugins); ambos servem o mesmo binário versionado.
  • O wrapper baixa automaticamente o binário para ~/bin/CheICalMCP no primeiro uso, se ele ainda não estiver lá.

Claude Code — MCP autônomo

mkdir -p ~/bin

# If upgrading, remove the old binary first. On macOS 26 the kernel can kill a
# fresh binary that inherits a stale code-signature cache from the old inode —
# one a running MCP process may still be holding open.
rm -f ~/bin/CheICalMCP

curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP
chmod +x ~/bin/CheICalMCP

# --scope user: available in all projects  ·  --transport stdio: local stdin/stdout
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

💡 Dica: Mantenha o binário em um diretório local como ~/bin/. Pastas sincronizadas em nuvem (Dropbox, iCloud, OneDrive) podem acionar tempos limite de conexão MCP quando a sincronização toca o arquivo.

Compilar a partir do código-fonte (opcional)

git clone https://github.com/PsychQuant/che-ical-mcp.git
cd che-ical-mcp
make release && make install
claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

⚠️ Usuários de Swift 6 / Xcode 18: Não execute swift build diretamente — o SDK MCP upstream tem um erro de concorrência (swift-sdk#214). O Makefile detecta isso automaticamente e usa o modo de linguagem Swift 5 como alternativa.

Conceder permissões

No primeiro uso, o macOS solicitará acesso a Calendário e Lembretes. Clique em Permitir para ambos.

⚠️ Nota sobre macOS Sequoia (15.x): O diálogo de permissão é atribuído ao aplicativo pai que iniciou o servidor MCP, não ao binário em si. Isso significa:

AmbientePermissão atribuída a
Claude DesktopClaude Desktop.app ✅ (funciona automaticamente)
Claude Code no Terminal.appTerminal.app ✅ (funciona automaticamente)
Claude Code no VS CodeVS Code ❌ (pode não mostrar o diálogo)
Claude Code no iTerm2iTerm2 ✅ (funciona automaticamente)

Se o diálogo de permissão não aparecer (comum com VS Code), você precisa adicionar NSCalendarsFullAccessUsageDescription ao Info.plist do VS Code:

# Adicionar descrição de uso de calendário ao VS Code
/usr/libexec/PlistBuddy -c "Add :NSCalendarsFullAccessUsageDescription string 'VS Code needs calendar access for MCP extensions.'" \
  "/Applications/Visual Studio Code.app/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Add :NSRemindersFullAccessUsageDescription string 'VS Code needs reminders access for MCP extensions.'" \
  "/Applications/Visual Studio Code.app/Contents/Info.plist"

# Reassinar o VS Code (necessário após modificar o Info.plist)
codesign -s - -f --deep "/Applications/Visual Studio Code.app"

# Reinicie o VS Code e o diálogo de permissão aparecerá

Nota: Esta modificação será sobrescrita quando o VS Code for atualizado. Você precisará reaplicá-la após cada atualização do VS Code.

Atualizando uma instalação existente

O wrapper do plugin baixa automaticamente em instalações novas, mas não substitui um binário existente. Para atualizar no local:

~/bin/CheICalMCP --self-update

Isso consulta o GitHub Releases pela tag mais recente, baixa o novo binário e substitui atomicamente o atual. Se estiver rodando como servidor MCP, reinicie o host MCP (Claude Desktop / Claude Code) depois para carregar a nova versão. Alternativa manual: rm -f ~/bin/CheICalMCP && curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP.

Modo CLI (sem servidor MCP)

Todas as 29 ferramentas podem ser invocadas diretamente pela linha de comando, sem necessidade de servidor MCP:

# Flag-based: --key value pairs
CheICalMCP --cli list_events --start_date 2026-03-29 --end_date 2026-03-30

# JSON via stdin
echo '{"tool":"list_calendars","arguments":{}}' | CheICalMCP --cli

# From Claude Code via shell
claude -p "Run: ~/bin/CheICalMCP --cli list_events_quick --range today"

Útil para jobs do launchd, scripts de shell, pipelines de CI e agentes que preferem um subprocesso ao protocolo MCP. As permissões TCC ainda se aplicam — execute CheICalMCP --setup primeiro, se necessário.


Recursos da v1.0.0

Análise flexível de datas

Todos os parâmetros de data agora aceitam 4 formatos:

FormatoExemploInterpretação
ISO8601 completo"2026-02-06T14:00:00+08:00"Data e hora exatas (deslocamento preservado)
Sem fuso horário"2026-02-06T14:00:00"Usa o timezone do evento, se fornecido; caso contrário, o fuso horário do sistema
Somente data"2026-02-06"Meia-noite no timezone do evento ou fuso horário do sistema
Somente hora"14:00"Hoje naquela hora

Fuso horário por evento (v1.5.0)

Defina o fuso horário de exibição para eventos individuais — essencial para itinerários de viagem com múltiplos fusos.

"Create a flight departure at 09:14 Berlin time"
→ create_event(title: "Flight LH123", start_time: "2026-04-08T09:14:00", timezone: "Europe/Berlin", ...)

"Update the hotel check-in to Dubai time"
→ update_event(event_id: "...", timezone: "Asia/Dubai")

"Remove the custom timezone from an event"
→ update_event(event_id: "...", clear_timezone: true)
  • O parâmetro timezone aceita identificadores IANA (ex.: Europe/Berlin, America/New_York, Asia/Taipei)
  • Quando timezone é fornecido, datas e horas ingênuas (sem offset) são interpretadas nesse fuso horário
  • A saída do evento inclui o fuso horário do próprio evento no campo timezone e formata start_date_local/end_date_local de acordo
  • Disponível em create_event, update_event e create_events_batch
  • Desfazer/refazer preserva o fuso horário de cada evento

Participantes e Organizador (Somente Leitura)

As respostas de eventos incluem informações dos participantes quando disponíveis. Esses campos são somente leitura devido a limitações do EventKit — eles não podem ser definidos ou modificados por meio do MCP.

Disponível em: list_events, search_events, list_events_quick, check_conflicts

attendees (array, opcional) — Presente quando o evento tem participantes. Cada objeto de participante contém:

CampoTipoDescrição
namestring ou nullNome de exibição, null se não estiver no Address Book
emailstringEndereço de e-mail extraído da URL do participante
rolestringUm de: unknown, required, optional, chair, non_participant
statusstringUm de: unknown, pending, accepted, declined, tentative, delegated, completed, in_process
typestringUm de: unknown, person, room, resource, group
is_current_userbooleanSe este participante é o usuário atual

organizer (objeto, opcional) — Presente quando o evento tem um organizador. Contém:

CampoTipoDescrição
namestring ou nullNome de exibição
emailstringEndereço de e-mail
is_current_userbooleanSe o organizador é o usuário atual

Nota: Ambos os campos são omitidos quando o evento não tem participantes ou organizador (ex.: eventos de calendário local criados sem convidados).

Correspondência Difusa de Calendários

Os nomes de calendários agora são correspondidos sem diferenciar maiúsculas de minúsculas. Se não forem encontrados, a mensagem de erro lista todos os calendários disponíveis.

Ferramentas Aprimoradas de Listagem/Exclusão

  • list_events: filter (all/past/future/all_day), sort (asc/desc), limit
  • list_reminders: filter (all/incomplete/completed/overdue), sort (due_date/creation_date/priority/title), limit
  • delete_events_batch: modo de intervalo de datas (before_date/after_date) + visualização dry_run

Mudança Importante: list_events e list_reminders agora retornam {events/reminders: [...], metadata: {...}} em vez de um array simples.


Exemplos de Uso

Gerenciamento de Calendários

"List all my calendars"
"What's on my schedule next week?"
"Create a meeting tomorrow at 2 PM titled 'Team Sync'"
"Add a dentist appointment on Friday at 10 AM with location '123 Main St'"
"Delete the meeting called 'Cancelled Meeting'"

Gerenciamento de Lembretes

"List my incomplete reminders"
"Show all reminders in my Shopping list"
"Add a reminder: Buy milk"
"Create a reminder to call mom tomorrow at 5 PM"
"Mark 'Buy milk' as completed"
"Delete the reminder about groceries"

Gerenciamento de Lembretes (v1.5.0)

"Remove the due date from 'Buy groceries'"
→ update_reminder(reminder_id: "...", clear_due_date: true)

Recursos Avançados (v0.3.0+)

"Search for events containing 'meeting'"
"Search for events with both 'project' AND 'review'"
"What do I have today?"
"Show me this week's schedule"
"Are there any conflicts if I schedule a meeting from 2-3 PM?"
"Create 3 weekly team meetings for the next 3 weeks"
"Copy the dentist appointment to my Work calendar"
"Move all events from 'Old Calendar' to 'New Calendar'"
"Delete all the cancelled events"
"Find duplicate events between 'IDOL' and 'Idol' calendars"

Melhorias de DX (v1.0.0)

"Show my next 5 upcoming events"
→ list_events(start_date: "2026-02-06", end_date: "2026-12-31", filter: "future", sort: "asc", limit: 5)

"Show my overdue reminders"
→ list_reminders(filter: "overdue")

"Preview which events would be deleted from 'Old Calendar' before 2025"
→ delete_events_batch(calendar_name: "Old Calendar", before_date: "2025-01-01", dry_run: true)

"Create an event at 2 PM" (no need for full ISO8601!)
→ create_event(start_time: "14:00", end_time: "15:00", ...)

Fontes de Calendário Suportadas

Funciona com qualquer calendário sincronizado com o app Calendários do macOS:

  • iCloud Calendar
  • Google Calendar
  • Microsoft Outlook/Exchange
  • Calendários CalDAV
  • Calendários locais

Desambiguação de Calendários com o Mesmo Nome (v0.6.0+)

Se você tiver calendários com o mesmo nome de fontes diferentes (ex.: "Trabalho" tanto no iCloud quanto no Google), use o parâmetro calendar_source:

"Create an event in my iCloud Work calendar"
→ create_event(calendar_name: "Work", calendar_source: "iCloud", ...)

"Show events from my Google Work calendar"
→ list_events(calendar_name: "Work", calendar_source: "Google", ...)

Se a ambiguidade for detectada, a mensagem de erro listará todas as fontes disponíveis.


Solução de Problemas

ProblemaSolução
Servidor desconectadoReconstrua com make release && make install
Permissão negadaConceda acesso a Calendários/Lembretes em Ajustes do Sistema > Privacidade e Segurança
A caixa de diálogo de permissão nunca apareceConsulte Conceder Permissões para a solução alternativa do macOS Sequoia
Permissão negada via SSHConsulte Acesso SSH abaixo
Permissão negada sob launchdConsulte launchd / Automação abaixo
Um serviço negado enquanto todos os diagnósticos relatam verdeConsulte Negação permanente silenciosa após atualização abaixo
Calendários/Lembretes quebram novamente após cada atualização do Claude CodeConsulte Atualizações do Claude Code rotacionam a concessão do lado do host abaixo
Calendário não encontradoGaranta que o calendário esteja visível no app Calendários do macOS
Lembretes não sincronizandoVerifique a sincronização do iCloud em Ajustes do Sistema

Negação permanente silenciosa após atualização (#154)

Se um serviço (tipicamente Calendários) retornar access denied enquanto o outro funciona, e --print-tcc-path e Ajustes do Sistema relatarem a permissão como concedida, você provavelmente está atingindo a assinatura #154: uma linha TCC criada por uma compilação pré-v1.7.1 (assinada ad-hoc) está fixada aos hashes de código daquela compilação antiga. O binário atualizado com Developer ID nunca pode corresponder a ela, e no macOS 26.5+ o sistema operacional só permite o novo prompt de correção quando o binário carrega a entitlement com.apple.security.personal-information.* correspondente.

A partir de v1.14.0+, o banner de inicialização expõe isso diretamente — uma linha [drift] TCC.db <service> entry pins a code requirement this binary no longer satisfies (#155) — quando a verificação do framework Security pode confirmar a incompatibilidade csreq. Antes disso, todos os diagnósticos da API de status (incluindo o banner) relatavam verde, que é exatamente o que tornava essa classe silenciosa. Se você atingir a negação por meio da instalação .mcpb do Claude Desktop, a própria mensagem de negação agora nomeia o bloqueador real e os caminhos que funcionam em vez do beco sem saída --setup (#158).

Correção: atualize para v1.11.0 ou posterior (o binário agora inclui ambas as entitlements), reinicie o app host (Cmd+Q completo para Claude Desktop) e aprove a caixa de diálogo de permissão que aparece no primeiro acesso a Calendários/Lembretes. Aprovar reescreve a linha TCC vinculada ao requisito do Developer ID, então ela sobrevive a todas as atualizações futuras. Se você negar acidentalmente a caixa de diálogo, reative o interruptor correspondente em Ajustes do Sistema → Privacidade e Segurança → Calendários ou Lembretes.

⚠️ Errata para a solução alternativa da era #108: tccutil reset Calendar com.checheng.CheICalMCP não funciona para um binário simples (sem bundle) — falha com OSStatus error -10814 porque o binário não tem registro no LaunchServices. E não execute um tccutil reset Calendar simples (sem um bundle ID): isso apaga as concessões de Calendários para todos os apps na máquina e, em um binário pré-entitlements, deixa o CheICalMCP permanentemente incapaz de solicitar novamente.

Atualizações do Claude Code rotacionam a concessão do lado do host (#170)

Em uma instalação nativa do Claude Code, o executável real reside em um caminho versionado (~/.local/share/claude/versions/<version>; ~/.local/bin/claude é apenas um symlink), e o TCC do macOS vincula a concessão de Calendários/Lembretes do lado do host a esse caminho. Cada atualização automática do Claude Code rotaciona o caminho e invalida silenciosamente a concessão — o sintoma clássico é "funcionou ontem, quebrou logo após uma atualização", com Ajustes do Sistema acumulando entradas antigas com números de versão simples (2.1.202, 2.1.203, …).

Correção: acione qualquer chamada de ferramenta de calendário pelo Claude Code para que o macOS solicite novamente (ou recrie a entrada), então ative a entrada mais recente com número de versão em Ajustes do Sistema → Privacidade e Segurança → Calendários / Lembretes. Lista de verificação completa: a habilidade troubleshoot-tcc (/che-ical-mcp:check-tcc). A causa raiz é upstream (rastreada em #170 — o Claude Code precisaria de uma identidade TCC estável); este repositório só pode detectar e documentar isso.

Acesso SSH

O TCC do macOS (Transparência, Consentimento e Controle) concede permissões de privacidade por aplicativo. Sessões SSH são executadas sob sshd, que é um contexto de segurança diferente — portanto, permissões concedidas ao Terminal ou ao Claude Code localmente não são transferidas para SSH.

Solução alternativa A — Execute localmente primeiro (recomendado):

  1. Execute CheICalMCP uma vez no Mac de destino localmente (não via SSH)
  2. Conceda acesso a Calendários e Lembretes quando a caixa de diálogo TCC aparecer
  3. Sessões SSH devem então herdar a concessão para o binário CheICalMCP

Solução alternativa B — Conceda Acesso Total ao Disco para sshd:

  1. Abra Ajustes do Sistema → Privacidade e Segurança → Acesso Total ao Disco
  2. Clique em +, pressione G, digite /usr/sbin/sshd e adicione-o
  3. Reinicie a sessão SSH

⚠️ A solução alternativa B concede ao sshd acesso amplo a arquivos — use apenas em máquinas que você controla totalmente.

launchd / Automação

Ao executar o CheICalMCP a partir de launchd, cron ou outra automação não interativa, o TCC do macOS não pode exibir caixas de diálogo de permissão. Use --setup para pré-conceder permissões:

# Step 1: Run once from Terminal (triggers TCC permission dialog)
CheICalMCP --setup

# Step 2: Grant Calendar & Reminders access in the dialog that appears
# Step 3: The binary now has permission — launchd jobs can use it

Detecção: o CheICalMCP detecta automaticamente sessões não interativas (variável de ambiente TERM ausente ou filho direto do launchd) e fornece mensagens de erro direcionadas com instruções de --setup. Isso funciona mesmo para cadeias de inicialização indiretas (launchd → Claude Code → CheICalMCP).

--setup em sessões não interativas (#143): se você executar o próprio --setup a partir de uma sessão não interativa (sem TERM / filho direto do launchd) e a permissão ainda estiver indeterminada, o --setup agora ignora a solicitação e sai com código não zero em vez de travar — uma caixa de diálogo TCC não pode aparecer ali, então ele imprime instruções de concessão manual em vez de bloquear. Execute --setup a partir de um Terminal real para acionar a caixa de diálogo. (Um binário já autorizado ainda relata sucesso mesmo quando executado novamente de forma não interativa.)

Nota: Se --setup conceder permissão, mas o MCP ainda falhar sob launchd, o TCC pode ter associado a permissão ao processo pai. Nesse caso, adicione manualmente o CheICalMCP em Ajustes do Sistema → Privacidade e Segurança → Calendários/Lembretes.


Detalhes Técnicos

  • Versão Atual: v1.15.0
  • Framework: MCP Swift SDK v0.12.0
  • API de Calendário: EventKit (framework nativo do macOS)
  • Transporte: stdio
  • Plataforma: macOS 14.0+ (Sonoma e posterior — elevado de 13.0 no cluster pós-1.10 conforme #119)
  • Ferramentas: 29 ferramentas para calendários, eventos, lembretes, tags, desfazer/refazer, limpeza e operações avançadas

Histórico de Versões

VersãoAlterações
v1.18.0Completude de desfazer/refazer para lembretes, discard_id explícito, booleanos estritos em todos os lugares (#196/#197/#198/#199/#202/#203/#206/#207/#208/#209/#211/#212/#214/#215/#216): desfazer grava de volta o completion_date registrado e refazer restaura o instante salvo em vez de re-inferir o estado; conclusão repetida preserva o instante original; undo(discard_id) remove explicitamente um registro de cabeçalho bloqueado pelo seu ID undo_history estável (alvos não encontrados não travam mais a pilha para lembretes excluídos ou eventos mortos); movimentos de eventos registram a ocorrência de origem para que desfazer restaure no calendário original; event_recurrence_rules / reminder_recurrence_rules distinguem os dois formatos (aliases legados mantidos); toda anotação de ferramenta tem uma política testada explícita, com complete_reminder, ferramentas de atualização e movimento opcional agora destructiveHint: true. QUEBRA: todo argumento booleano de ferramenta é um booleano JSON estrito (strings/números rejeitados com <key> must be a boolean). Internos: filtro/ordenação/limite de lista e busca de lembretes dentro do ator pré-snapshot, list_reminder_tags na costura do snapshot, varredura linear de tags (sem retrocesso exponencial), resultados de gravação de lembretes Sendable imutáveis, métodos de conclusão/histórico recorrente em uma extensão de ator dedicada.
v1.17.0Recorrência de lembretes na leitura, resultados de conclusão explícitos, desfazer com guarda de identidade, completed booleano estrito (#194/#204/#205): list_reminders / search_reminders expõem has_recurrence, recurrence_rules completo (incl. frequency_raw_value) e um objeto due; complete_reminder separa o resultado da gravação (operation) do objeto salvo (observed) e relata o sucessor como next_occurrence — observado uma vez, sincronamente, após salvar (iCloud no dispositivo: o mesmo ID avança no lugar, a ocorrência concluída é arquivada sob um novo ID), mensagem no relógio de parede do próprio lembrete. Desfazer/refazer de uma conclusão recorrente é protegido por identidade: uma vez que o ID não resolve mais para a ocorrência registrada, recusa explicitamente e descarta a entrada para que operações mais antigas permaneçam desfazíveis; não encontrado permanece transitório (#191). QUEBRA: completed deve ser um booleano JSON nas três ferramentas de lembrete (strings/números rejeitados antes de qualquer leitura ou gravação; omitir/null mantêm seu significado; --cli JSON null → omitido). Três rodadas de verificação 6-AI no PR #195 mais rodadas em #200 / #201; duas sondas stdio no dispositivo. 582 testes.
v1.16.1Correções de segurança de tipo + verificação no dispositivo (#184/#190/#191): recurrence não-objeto agora rejeitado (era silenciosamente descartado); pareamento all_day + timezone rejeitado (estava silenciosamente removendo o sinalizador de dia inteiro e anulando exclusões através da linha de data); desfazer de exclusão de série recorrente reconstrói regras a partir de snapshots de valor (corrige EKCADErrorDomain 1010) e um desfazer/refazer falho não consome mais a entrada. 529 testes.
v1.16.0Exclusões de recorrência + completude de desfazer + habilidade de arquivamento de eventos (#182/#185/#180): excluded_occurrence_dates em create_event/batch (criação em duas passadas com remoção compensatória, primeira ocorrência não excluível); exclusões em lote/série agora registram entradas de desfazer (uma unidade .batch); desfazer de uma criação recorrente agora remove a série inteira; nova habilidade archive-event — arquivamento de fonte para evento com rastreamento de correção e configuração de projeto .claude/.ical/. 514 testes.
v1.15.0Contexto de execução --print-tcc-path + sinal de banner de host versionado. O diagnóstico TCC agora imprime sua cadeia de processos pai (self → … → launchd) com um aviso de dependência de contexto — o status de autorização segue o contexto do processo responsável (#168), então saber qual host executou a consulta é essencial (#169). Polimento de diagnóstico de cadeia pai: marcadores visíveis de truncamento/ciclo, ligação comm vazia, ps -ww, relatório de falha de decodificação/saída, precisão de NOTA (#173). Novo sinal de detector de deriva: "host Claude Code versionado + EventKit não concedido" — explica a quebra de atualização-rotação #170 proativamente na inicialização, suprimindo a dica --setup contraditória nesse cenário (#175). Todos os três verificados por ensembles 6-AI entre modelos; uma lacuna de escape de terminal CWE-150 encontrada pela verificação foi corrigida antes do merge. Deriva de versão do lançamento do plugin v1.14.2 alinhada em todos os cinco locais de versão (#172). 490 testes.
v1.14.2Lançamento de camada de docs/habilidades — modelo de autorização TCC de duas camadas (#168): habilidade troubleshoot-tcc, /check-tcc, mcpb/README.md e plugin/CLAUDE.md agora documentam a camada TCC do aplicativo host (processo responsável), as entradas de versão de número simples em Configurações do Sistema (2.1.202 = binário versionado do Claude Code) e o procedimento de verificação alternar-e-observar. Binário byte-idêntico ao v1.14.1 no momento do lançamento (somente shell de plugin; locais de versão de origem alinhados depois em #172).
v1.14.1Correção de metadados — consistência de contagem de ferramentas. server.json description dizia "24 ferramentas" e PROMOTION.md dizia "20 ferramentas"; o servidor realmente expõe 29 ferramentas (correspondendo a mcpb/manifest.json long_description e à guarda de paridade de ferramentas ManifestParityTests). Corrigido o server.json voltado ao registro, docs/COMPETITIVE_ANALYSIS.md e PROMOTION.md para 29. Sem mudanças de código ou superfície de ferramentas — funcionalmente idêntico ao v1.14.0; este lançamento existe apenas para publicar metadados de registro corrigidos (versões de registro são imutáveis).
v1.14.0Correção de queda de injeção de ferramentas do Claude Desktop (#166): um & literal em mcpb/manifest.json display_name fez o Desktop 1.18286.0 descartar silenciosamente o servidor inteiro de 29 ferramentas de toda conversa (Claude Code não afetado); alterado &and, confirmado por intervenção de variável única na instalação com falha + uma guarda de regressão ManifestParityTests. Também alinhado serverInfo.name ao id de manifesto kebab (higiene; empiricamente refutado como causa). Lote irmão #154: sinal de deriva TCC de incompatibilidade csreq (#155, auto-verificação SecCodeCheckValidity para a classe de negação silenciosa), mensagem de negação .mcpb não termina mais em beco sem saída em --setup para a assinatura já .denied (#158), badge macOS 13.0 → 14.0 (#157), swift-nio 2.96 → 2.101 (#159). 454 testes.
v1.13.0SwiftUI SetupWindow (#164): --setup interativo apresenta uma janela de status ao vivo (botões Grant por entidade + caminho binário resolvido) dentro do NSApplication em primeiro plano #163. Correção de negação de Calendário no Desktop (#165): isNonInteractive disparou erroneamente em TERM == nil para servidores iniciados por aplicativos GUI → falha rápida antes de requestFullAccess, então o diálogo de primeira concessão nunca apareceu através do Claude Desktop; agora usa um sinal de sessão GUI CGSession. 429 testes.
v1.12.0--setup em primeiro plano (#163): --setup interativo agora roda dentro de um NSApplication em primeiro plano para que o modal TCC de Calendário do EventKit realmente apareça (anteriormente negado silenciosamente de um contexto CLI assíncrono simples). Mensagens de negação + banner de inicialização exibem o caminho binário resolvido + um comando "<path>" --setup copiável para o binário .mcpb enterrado.
v1.11.1Validação de intervalo de tempo create_event (#160): simétrico a update_event — rejeita eventos temporizados invertidos / duração zero via uma guarda compartilhada validateTimeRange. 405 testes.
v1.11.0Re-prompt de cura TCC desbloqueado (#154): Entitlements.plist inclui personal-information.calendars + .reminders — instalações antigas pré-v1.7.1 podiam atingir negação permanente silenciosa de Calendário no macOS 26.5 (linha TCC fixada em cdhashes antigos, re-prompt de cura bloqueado por política porque o binário não tinha entitlements, todos os diagnósticos relatando verde); gate de lançamento de binário assinado verifica ambas as chaves. Endurecimento de EventKit não interativo (#131 / #143 / #144 + #146–#150). QUEBRA: piso de implantação elevado para macOS 14.0 (#119). 401 testes.
v1.10.0Detector de deriva TCC + banner de inicialização (#122): banner stderr de disparo único na inicialização do servidor MCP com versão/caminho/PID + sinais de deriva (incompatibilidade de caminho TCC.db por serviço, processos obsoletos); opt-out via CHE_ICAL_MCP_NO_BANNER=1. Correção de deadlock de pipe em helpers de subprocesso; defesa de injeção de stderr CWE-117 em todos os valores de banner interpolados.
v1.9.0Refatoração do gate de acesso TCC (#108 Fase 2, fecha #109): removido o anti-padrão de cache has*Access de vida do processo; EKEventStore.authorizationStatus(for:) por chamada via nova costura AuthorizationGate + AuthorizationStatusSource (padrão Apple TN3153) — mudanças de estado aparecem imediatamente em vez de falha silenciosa de concessão obsoleta. Adiciona sinalizador de diagnóstico --print-tcc-path.
v1.8.1Docs: guia de configuração de permissão TCC pós-instalação / atualização mcpb/README.md (#108 Fase 1).
v1.8.0Onda de consistência de formato de fio + parâmetros de forma de resposta (cluster #101 — 5 issues fechadas em 3 dias, todos Refs #N IDD + verificação de ensemble 6-AI). Parâmetros de forma de resposta de listagem de eventos (#47 / #101): detail_level (summary/standard), lista de permissão fields, display_timezone (IANA estrito), limit (limite 10000) — ajuste de verbosidade LLM. Unificação de envelope (#102 / #107, quebra de formato de fio): list_events.metadata.returned + list_reminders.metadata.returned removidos; todos os 5 envelopes de lista/busca usam <entity>_count de nível superior com semântica pré-limite; search_reminders.result_countreminder_count; search_reminders ganha parâmetro limit (espelho search_events). Clientes MCP lendo metadata.returned ou result_count devem atualizar. Endurecimento de validador (#101 F1–F3): requireOptionalInt usa Int(exactly:) fechando a armadilha DoS Int.max; validadores detail_level / display_timezone distinguem ausente vs. não-string (sem coerção silenciosa). Detecção de deriva ancorada em runtime (#103, fortalecendo #101 M3): teste de divergência formatEventDictvalidEventFields agora via costura EventFormattingSource + FakeFormattableEvent. Reclassificação de CHANGELOG (#106): renomeações de formato de fio movidas de Fixed para Changed (Keep a Changelog 1.1.0). Correção de pipeline de lançamento: verificação de defesa pré-empacotamento agora deriva Team ID do certificado DEVELOPER_ID (estava comparando hash SHA contra string Authority= legível).
v1.7.2Onda de endurecimento + recursos (30+ commits sobre v1.7.1, todos Refs #N IDD com verificação 6-AI). --self-update (#49) + verificação binária SHA-256 (#98): caminho de atualização de instalação existente com garantia criptográfica contra lançamentos corrompidos. make install-signed (#50): fluxo TCC de desenvolvedor mantenedor no macOS 26 — falha rápida em Developer ID ausente + verificação forçada de codesign. Workflow de teste CI (#51): swift build + swift test em tempo de PR no macos-latest. Cluster de endurecimento de sanitizador: escapeForStderr cobertura completa C0+DEL (#73), sanitizeForInterpolation para interpolação de título executeUndo/executeRedo (#74), stderr de CLIRunner delegado a writeFailureLog para carve-out de ramo confiável (#80), limite DoS de 1024 caracteres writeFailureLog (#86), doc de contrato controlado apenas por autor CLIError.invalidJSON (#85), segurança de thread FileHandle.standardError.write + PIPE_BUF=512 do macOS documentado (#70 / #94). Polimento de distribuição: snippets de instalação com cache de codesign obsoleto recebem preâmbulo rm -f (#90 paridade zh-TW para #62). Polimento pós-v1.7.1 (#46 #57 #58 #60): paridade de interpolação de erro de refazer, renumeração de etapa build-mcpb.sh, documentação Entitlements.plist, nota de cwd Makefile release-signed:. Ferramenta cleanup_completed_reminders (#21): limpeza de chamada única de todos os lembretes concluídos, padrão dry_run=true.
v1.7.1Endurecimento de segurança (#20 #26): validação de entrada (limites de comprimento + allowlist de esquema de URL) em todos os pontos de entrada de evento/lembrete, wrapper de injeção de prompt em respostas de leitura MCP, validação de limite de análise para days_of_week / days_of_month / alarms_minutes_offsets (lança em vez de descartar silenciosamente valores inválidos), atualização Info.plist, 42 novos testes de regressão.
v1.7.0Informações de participante e organizador (#17): array attendees somente leitura e objeto organizer nas respostas de eventos. Método formatEventDict compartilhado refatorado.
v1.6.0Flag --setup (#13): pré-autoriza permissões TCC para launchd/automação. Detecção de sessão não interativa (TERM + ppid). Mensagens de erro combinadas SSH+launchd. Modo --cli (#14): invoca todas as 28 ferramentas diretamente da linha de comando sem servidor MCP. Modos baseados em flag (--key value) e stdin JSON. Inferência inteligente de tipos para parâmetros bool/int/double/array. MCP Swift SDK 0.12.0 (compatibilidade Swift 6.3).
v1.5.0Fuso horário por evento (#12): parâmetro timezone em create_event/update_event/create_events_batch, a saída do evento usa o fuso horário do próprio evento, datetimes ingênuos analisados no fuso horário do evento. Limpar data de vencimento (#9): clear_due_date em update_reminder. Validação de dia da semana (#5): create_event/update_event validam o dia da semana start_time contra days_of_week. Desfazer/refazer (#8): 3 novas ferramentas (undo, redo, undo_history). Correções de eventos recorrentes (#7): exclusão/atualização no nível de ocorrência com occurrence_date. Build Swift 6 (#11): README atualizado para o fluxo de trabalho make release
v1.4.0Confiabilidade LLM: corrige o intervalo de pesquisa padrão (±2 anos em vez de distantPast/Future), metadados searched_range na resposta search_events, dicas similar_events em create_events_batch, dicas LLM nas descrições das ferramentas
v1.3.1Correção de documentação: esclareceu que as tags são de nível MCP (não tags nativas do Reminders.app); a Apple não fornece API pública para tags nativas
v1.3.0Tags de lembrete (nível MCP): texto #hashtag armazenado nas notas para create_reminder/update_reminder/create_reminders_batch, filtragem baseada em tags em search_reminders, nova ferramenta list_reminder_tags; MCP SDK 0.11.0. Nota: as tags são pesquisáveis via MCP, mas não aparecem como tags nativas do Reminders.app (a Apple não fornece API pública para isso)
v1.2.0Escritas idempotentes: create_event, create_events_batch, create_reminder, create_reminders_batch, create_calendar agora verificam antes de escrever para evitar duplicatas em nova tentativa; as respostas incluem contagem de skipped
v1.1.0Recorrência + Localização: eventos/lembretes recorrentes (diário/semanal/mensal/anual), locais estruturados com coordenadas, gatilhos de lembrete baseados em localização (entrada/saída de geofence), saída rica de recorrência
v1.0.0Melhorias de DX: análise flexível de datas (4 formatos), correspondência difusa de calendários, list_events/list_reminders filtro/ordenação/limite, delete_events_batch modo dry-run + intervalo de datas
v0.9.04 novas ferramentas (20→24): update_calendar, search_reminders, create_reminders_batch, delete_reminders_batch
v0.8.2Suporte a semanas i18n: parâmetro week_starts_on para list_events_quick (monday/sunday/saturday/system)
v0.8.1Correção: bug de validação de tempo update_event, preservação de duração ao mover eventos
v0.8.0MUDANÇA IMPORTANTE: calendar_name agora é obrigatório para operações de criação (sem mais padrões implícitos)
v0.7.0Anotações de ferramentas para o Diretório de Conectores da Anthropic, mecanismo de atualização automática, descrições de ferramentas em lote melhoradas
v0.6.0Desambiguação de fonte: parâmetro calendar_source para calendários com o mesmo nome
v0.5.0Exclusão em lote, detecção de duplicatas, pesquisa por múltiplas palavras-chave, erros de permissão melhorados, PRIVACY.md
v0.4.0Copiar/mover eventos: copy_event, move_events_batch
v0.3.0Recursos avançados: pesquisa, intervalo rápido, criação em lote, verificação de conflitos, exibição de fuso horário
v0.2.0Reescrita em Swift com suporte completo a Reminders
v0.1.xVersão Python (obsoleta)

Contribuindo

Contribuições são bem-vindas! Sinta-se à vontade para enviar um Pull Request.

Processo de Release (para mantenedores)

Os números de versão estão em três lugares com semânticas diferentes:

ArquivoPapelQuando atualizar
Sources/CheICalMCP/Version.swiftAppVersion.currentFonte da verdade; aparece em --version, help e MCP serverInfo.versionA cada release
Sources/CheICalMCP/Info.plistCFBundleVersionVersão do bundle macOSA cada release; deve corresponder a AppVersion.current
mcpb/manifest.jsonversionManifesto do bundle do Claude Desktop enviado dentro de .mcpbA cada release; deve corresponder a AppVersion.current
server.jsonversion + packages[].identifier + fileSha256Snapshot de submissão ao MCP RegistrySomente ao reenviar um novo .mcpb ao MCP Registry (cadência independente)

scripts/build-mcpb.sh garante que os três primeiros correspondam; ele falhará o build se houver qualquer divergência. server.json é intencionalmente desacoplado porque atualizá-lo exige um .mcpb reconstruído, um SHA256 novo e uma nova submissão — etapas que não acontecem a cada release de código-fonte.

Assinatura e Notarização (obrigatório para macOS 26+)

A partir da v1.7.1, os binários de release são assinados com um certificado Developer ID Application e notarizados via notarytool da Apple. Isso é obrigatório no macOS 26 — binários assinados ad-hoc não podem acionar os diálogos de permissão TCC de Calendário / Lembretes lá.

Pré-requisitos (configuração única):

  1. Inscrição no Apple Developer Program.
  2. Certificado Developer ID Application instalado no keychain de login.
    • Verifique com: security find-identity -p codesigning -v (deve mostrar Developer ID Application: <Your Name> (<TeamID>)).
    • Seu Team ID é seu — encontre-o em https://developer.apple.com/account → Membership Details. (O 6W377FS7BS do mantenedor mostrado em qualquer lugar deste repositório é apenas para referência.)
  3. Perfil de keychain notarytool (qualquer nome; che-ical-mcp é o padrão que o script de build procura).
    • Crie interativamente (recomendado — mantém a senha fora do histórico do shell):
      xcrun notarytool store-credentials che-ical-mcp --apple-id <your-apple-id> --team-id <your-team-id>
      # notarytool will prompt for the app-specific password
      
    • Senha específica do app: gere em https://account.apple.com → Sign-In and Security → App-Specific Passwords. Use uma senha de propósito único (ex.: nomeada che-ical-mcp); revogue e regenere se vazar. Nunca a passe via --password na linha de comando — ela vai parar em ~/.zsh_history.
  4. Exporte sua identidade para o script de build:
    export DEVELOPER_ID='Developer ID Application: <Your Name> (<TeamID>)'
    export NOTARY_PROFILE='che-ical-mcp'   # match what you set up in step 3
    
    Persista-as em ~/.zshrc ou em um .envrc local ao projeto (gitignored). O script intencionalmente não tem padrões para essas variáveis, para que um fork novo não falhe com erros referentes à identidade do mantenedor.

Fluxo por release:

make release-signed     # builds universal binary → signs + notarizes → packages .mcpb
gh release create vX.Y.Z mcpb/server/CheICalMCP mcpb/server/CheICalMCP.sha256 mcpb/che-ical-mcp-X.Y.Z.mcpb mcpb/che-ical-mcp-X.Y.Z.mcpb.sha256 --notes "..."

make release-signed executa scripts/build-mcpb.sh, que após criar o binário universal chama scripts/sign-and-notarize.sh. O script de assinatura faz verificações prévias (certificado + perfil notarytool) e falha rapidamente com mensagens amigáveis se algo estiver faltando. A notarização normalmente leva de 1 a 15 minutos (notarytool submit --wait bloqueia até a Apple terminar).

Verificação após o build (execute todos os três para confirmar de ponta a ponta):

# 1. Signature properties (cert + hardened runtime + team ID)
codesign -dv --verbose=2 mcpb/server/CheICalMCP
# Expected:
#   Authority=Developer ID Application: <Your Name> (<TeamID>)
#   TeamIdentifier=<TeamID>
#   flags=0x10000(runtime)
#   Signature size in the few thousand bytes range (varies by cert chain)

# 2. Signature integrity
codesign --verify --deep --strict --verbose=2 mcpb/server/CheICalMCP
# Expected: exit 0, no warnings

# 3. Notarization end-to-end (this is the real "Gatekeeper would accept" gate)
spctl -a -vvv -t install mcpb/server/CheICalMCP
# Expected: <binary>: accepted; source=Notarized Developer ID
#
# Note on flag choice (verified empirically on macOS 26.4.1, 2026-05-04):
#   -t execute → rejected "code is valid but does not seem to be an app"
#                (Apple's "execute" type expects a .app bundle structure,
#                 not raw Mach-O CLI binaries)
#   -t install → accepted; source=Notarized Developer ID  ← use this
#   -t open    → rejected "Insufficient Context"
#
# Apple's Code Signing Guide describes -t execute as the assessment type for
# "applications and tools", but on macOS 26 raw Mach-O binaries fall through
# the .app bundle check. -t install is the documented assessment type for
# software being installed (which describes how a CLI binary lands in ~/bin),
# and is the type that returns the actual notarization verdict in practice.
# Re-test if Apple changes this behavior in a future macOS update.

Iteração de desenvolvimento local sem latência de assinatura:

SKIP_CODESIGN=1 ./scripts/build-mcpb.sh   # ad-hoc signed; do NOT ship the result
make install                              # installs ad-hoc to ~/bin (dev only)

O script build-mcpb.sh também pula a assinatura automaticamente quando DEVELOPER_ID não está definido OU o certificado não está no seu keychain — assim, contribuidores / CI / forks podem compilar um .mcpb não assinado funcional para testes sem definir SKIP_CODESIGN manualmente. (Você verá um aviso claro de "Skipping codesign" quando isso acontecer.)

Ambiente de identidade de assinatura:

Variável de ambientePadrãoObrigatório para
DEVELOPER_ID(não definido — pula assinatura automaticamente)Release assinado
NOTARY_PROFILE(não definido — falha rápida em sign-and-notarize.sh)Release assinado
ENTITLEMENTSSources/CheICalMCP/Entitlements.plistArquivo de entitlements personalizado
SKIP_CODESIGN(não definido)Forçar a pular assinatura mesmo com certificado presente (defina como 1 ou true)
REQUIRE_CODESIGN(não definido)Falha rápida se os pré-requisitos de assinatura estiverem ausentes (defina como 1 por make release-signed — o caminho canônico de release não deve produzir silenciosamente artefatos não assinados; não defina ao executar ./scripts/build-mcpb.sh diretamente para builds de desenvolvimento amigáveis a forks)

Limitação conhecida — sem stapling: stapler staple não suporta binários Mach-O brutos (apenas bundles .app / .pkg / .dmg). Após a notarização, o Gatekeeper verificará o binário online no primeiro lançamento em vez de ler um ticket com staple. Usuários finais em redes isoladas podem ver avisos de "cannot verify developer"; um lançamento com rede resolve isso (a Apple armazena em cache o veredito). Mitigação: xcrun stapler staple em um futuro wrapper .pkg se necessário.

Solução de problemas:

  • Notarização rejeitada? xcrun notarytool log <submission-id> --keychain-profile $NOTARY_PROFILE mostra o motivo da Apple. O script de assinatura imprime o ID de submissão a cada execução.
  • codesign reclama de identidade ausente? security find-identity -p codesigning -v para confirmar que o certificado está presente e válido; xcrun notarytool history --keychain-profile $NOTARY_PROFILE para confirmar que o perfil funciona.
  • Certificado expirado? Reemita em https://developer.apple.com/account/resources/certificates, instale, reexporte DEVELOPER_ID.
  • Aviso de segurança: não desbloqueie o keychain de assinatura em máquinas compartilhadas / não confiáveis. O artefato de assinatura (certificado + chave privada) é crítico para a cadeia de suprimentos.

Licença

Licença MIT — veja LICENSE para detalhes.


Autor

Criado por Che Cheng (@kiki830621)

Se você achar isso útil, considere dar uma estrela!

Nomes de leitura de recorrência (#198, não lançado): use event_recurrence_rules para eventos e reminder_recurrence_rules para lembretes. Ambos são arrays de regras, mas seletores ausentes e renderização de data final diferem. O alias legado recurrence_rules é mantido. Clientes que rejeitam campos desconhecidos precisam de atualizações de decoder. Veja a comparação de formatos.