@xiaotianxt/skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/EXCLUDED.md +42 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -0
  4. package/SECURITY.md +23 -0
  5. package/SOURCES.md +45 -0
  6. package/bin/skills.mjs +241 -0
  7. package/package.json +38 -0
  8. package/skills/1password/SKILL.md +94 -0
  9. package/skills/1password/agents/openai.yaml +4 -0
  10. package/skills/1password/references/item-management.md +80 -0
  11. package/skills/1password/references/op-cli.md +107 -0
  12. package/skills/apple-calendar-event/SKILL.md +81 -0
  13. package/skills/apple-calendar-event/agents/openai.yaml +4 -0
  14. package/skills/apple-calendar-event/scripts/calendar_audit.py +201 -0
  15. package/skills/apple-calendar-event/scripts/calendar_event.py +164 -0
  16. package/skills/bro-browser/SKILL.md +118 -0
  17. package/skills/bro-browser/agents/openai.yaml +4 -0
  18. package/skills/bro-browser/references/tool-map.md +102 -0
  19. package/skills/bro-browser/references/workflows.md +146 -0
  20. package/skills/bro-browser/scripts/bro-call.mjs +189 -0
  21. package/skills/calendar/SKILL.md +182 -0
  22. package/skills/calendar/agents/openai.yaml +4 -0
  23. package/skills/calendar/references/operations.md +255 -0
  24. package/skills/calendar/scripts/calendar_list_review.py +157 -0
  25. package/skills/calendar/scripts/event_dedupe_preview.py +155 -0
  26. package/skills/canvas/SKILL.md +70 -0
  27. package/skills/canvas/agents/openai.yaml +4 -0
  28. package/skills/canvas/references/canvas-api.md +76 -0
  29. package/skills/course-exam-review-planner/SKILL.md +127 -0
  30. package/skills/cx/SKILL.md +25 -0
  31. package/skills/gh-fix-ci/LICENSE.txt +201 -0
  32. package/skills/gh-fix-ci/SKILL.md +81 -0
  33. package/skills/gh-fix-ci/agents/openai.yaml +6 -0
  34. package/skills/gh-fix-ci/assets/github-small.svg +3 -0
  35. package/skills/gh-fix-ci/assets/github.png +0 -0
  36. package/skills/gh-fix-ci/scripts/inspect_pr_checks.py +509 -0
  37. package/skills/gh-review-workflow/SKILL.md +61 -0
  38. package/skills/gh-review-workflow/agents/openai.yaml +4 -0
  39. package/skills/gh-review-workflow/references/workflow.md +48 -0
  40. package/skills/gh-review-workflow/scripts/fetch_review_state.py +222 -0
  41. package/skills/gh-review-workflow/scripts/resolve_review_threads.py +83 -0
  42. package/skills/github/SKILL.md +74 -0
  43. package/skills/github/agents/openai.yaml +6 -0
  44. package/skills/github/assets/github-small.svg +3 -0
  45. package/skills/github/assets/github.png +0 -0
  46. package/skills/gws-calendar/SKILL.md +126 -0
  47. package/skills/gws-calendar-agenda/SKILL.md +52 -0
  48. package/skills/gws-calendar-insert/SKILL.md +66 -0
  49. package/skills/gws-docs/SKILL.md +48 -0
  50. package/skills/gws-docs-write/SKILL.md +49 -0
  51. package/skills/gws-drive/SKILL.md +137 -0
  52. package/skills/gws-drive-upload/SKILL.md +52 -0
  53. package/skills/gws-gmail/SKILL.md +62 -0
  54. package/skills/gws-gmail-forward/SKILL.md +55 -0
  55. package/skills/gws-gmail-reply/SKILL.md +58 -0
  56. package/skills/gws-gmail-reply-all/SKILL.md +62 -0
  57. package/skills/gws-gmail-send/SKILL.md +57 -0
  58. package/skills/gws-gmail-triage/SKILL.md +50 -0
  59. package/skills/gws-gmail-watch/SKILL.md +58 -0
  60. package/skills/gws-shared/SKILL.md +27 -0
  61. package/skills/helium-browser-mcp/SKILL.md +137 -0
  62. package/skills/helium-browser-mcp/agents/openai.yaml +4 -0
  63. package/skills/helium-browser-mcp/scripts/obmcp.mjs +92 -0
  64. package/skills/helium-browser-mcp/scripts/openbrowsermcp-stdio-proxy.mjs +170 -0
  65. package/skills/learn/SKILL.md +122 -0
  66. package/skills/learn/agents/openai.yaml +7 -0
  67. package/skills/learn/assets/AGENTS.template.md +33 -0
  68. package/skills/learn/assets/errorlog.template.typ +61 -0
  69. package/skills/learn/assets/reading-sequence.template.md +23 -0
  70. package/skills/learn/assets/source-index.template.md +17 -0
  71. package/skills/learn/assets/tasklog.template.typ +57 -0
  72. package/skills/learn/assets/workbook.template.typ +60 -0
  73. package/skills/learn/references/learning-science.md +103 -0
  74. package/skills/learn/scripts/init_learning_workspace.py +70 -0
  75. package/skills/macos-messages/SKILL.md +258 -0
  76. package/skills/memory/SKILL.md +33 -0
  77. package/skills/memory/codex.md +186 -0
  78. package/skills/memory/opencode.md +164 -0
  79. package/skills/mimestreamctl/SKILL.md +170 -0
  80. package/skills/mimestreamctl/agents/openai.yaml +4 -0
  81. package/skills/mimestreamctl/scripts/mimestreamctl +33 -0
  82. package/skills/mon/SKILL.md +51 -0
  83. package/skills/mon/scripts/mon_spend_review.py +458 -0
  84. package/skills/ocr/SKILL.md +136 -0
  85. package/skills/ocr/agents/openai.yaml +4 -0
  86. package/skills/ocr/references/local-ocr-best-practices.md +297 -0
  87. package/skills/ocr/references/mineru-api.md +159 -0
  88. package/skills/ocr/scripts/ocr-router +22 -0
  89. package/skills/ocr/scripts/ocr_router.py +741 -0
  90. package/skills/panopto-mp4-bulk-download/SKILL.md +57 -0
  91. package/skills/panopto-mp4-bulk-download/agents/openai.yaml +4 -0
  92. package/skills/panopto-mp4-bulk-download/references/url-patterns.md +26 -0
  93. package/skills/panopto-mp4-bulk-download/scripts/panopto_bulk_mp4.sh +213 -0
  94. package/skills/rust-systems-style/SKILL.md +109 -0
  95. package/skills/rust-systems-style/agents/openai.yaml +4 -0
  96. package/skills/rust-systems-style/references/rust-review-checklist.md +77 -0
  97. package/skills/rust-systems-style/references/style-sources.md +68 -0
  98. package/skills/ship-ai-native-cli/SKILL.md +76 -0
  99. package/skills/ship-ai-native-cli/agents/openai.yaml +4 -0
  100. package/skills/ship-ai-native-cli/references/case-notes.md +83 -0
  101. package/skills/ship-ai-native-cli/references/product-method.md +82 -0
  102. package/skills/ship-ai-native-cli/references/release-checklist.md +147 -0
  103. package/skills/ship-ai-native-cli/references/rust-cli-shape.md +111 -0
  104. package/skills/telegram-mtproto-session/SKILL.md +125 -0
  105. package/skills/telegram-mtproto-session/agents/openai.yaml +4 -0
  106. package/skills/telegram-mtproto-session/scripts/telegram_session.py +687 -0
  107. package/skills/tg/SKILL.md +173 -0
  108. package/skills/things3-manager/SKILL.md +116 -0
  109. package/skills/things3-manager/scripts/things +42 -0
  110. package/skills/things3-manager/scripts/things_cli.py +514 -0
  111. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  112. package/skills/web-artifacts-builder/SKILL.md +74 -0
  113. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  114. package/skills/web-artifacts-builder/scripts/init-artifact.sh +379 -0
  115. package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  116. package/skills/yeet/LICENSE.txt +201 -0
  117. package/skills/yeet/SKILL.md +71 -0
  118. package/skills/yeet/agents/openai.yaml +6 -0
  119. package/skills/yeet/assets/yeet-small.svg +3 -0
  120. package/skills/yeet/assets/yeet.png +0 -0
@@ -0,0 +1,80 @@
1
+ # 1Password Item Management
2
+
3
+ Use this reference before creating, editing, copying, exporting, or deleting 1Password items.
4
+
5
+ ## Creation Principles
6
+
7
+ - Prefer the 1Password app when the user needs to store a new plaintext secret.
8
+ - Do not ask the user to paste a plaintext secret into chat.
9
+ - Do not put sensitive values in command-line assignment statements. The `op item create --help` text warns that command arguments may be logged in shell history or visible to local processes.
10
+ - For CLI creation with secrets, use a short-lived JSON template or stdin, set restrictive permissions, and delete plaintext temporary files immediately.
11
+ - Use `--dry-run` for shape validation when possible.
12
+ - Use clear item titles and field names that match the service's docs.
13
+
14
+ ## Field Types
15
+
16
+ Use explicit field types:
17
+
18
+ ```text
19
+ Name[text]=value
20
+ Secret[concealed]=value
21
+ URL[url]=https://example.com
22
+ Old Field[delete]
23
+ ```
24
+
25
+ Only conceal actual secrets: passwords, API keys, bearer tokens, refresh tokens, private keys, recovery codes, and cookies.
26
+
27
+ Use text or URL fields for usernames, account emails, client IDs, hostnames, ports, redirect URLs, docs links, and other identifiers.
28
+
29
+ ## API Credential Items
30
+
31
+ For simple API keys in this user's setup, prefer an item category of `API Credential` with the secret in a field named `credential`.
32
+
33
+ Preferred read reference:
34
+
35
+ ```text
36
+ op://Private/<title-or-id>/credential
37
+ ```
38
+
39
+ Useful non-secret fields:
40
+
41
+ ```text
42
+ Account[text]
43
+ Service[text]
44
+ Documentation[url]
45
+ Developer Portal[url]
46
+ Notes
47
+ ```
48
+
49
+ OAuth-style credentials should distinguish non-secret and secret fields:
50
+
51
+ ```text
52
+ Client ID[text]
53
+ Client Secret[concealed]
54
+ Authorization URL[url]
55
+ Token Request URL[url]
56
+ Redirect URL[text]
57
+ Scopes[text]
58
+ ```
59
+
60
+ ## Listing And Searching
61
+
62
+ Do not list vaults or items for convenience. If discovery is necessary, keep it narrow:
63
+
64
+ ```bash
65
+ op item list --vault Private --categories "API Credential" --format json
66
+ ```
67
+
68
+ When summarizing discovery results, report only item titles, IDs, vault names, categories, and timestamps needed for selection. Do not reveal field values.
69
+
70
+ ## Service Accounts And Automation
71
+
72
+ Service accounts are production-impacting credentials. Before creating or modifying one, state:
73
+
74
+ - intended name
75
+ - target vault
76
+ - permissions
77
+ - where the token will be stored
78
+ - how it will be rotated or revoked
79
+
80
+ Do not print service account tokens in final responses. Store them directly in the intended secret manager when possible.
@@ -0,0 +1,107 @@
1
+ # 1Password CLI Patterns
2
+
3
+ Use this reference for local `op` CLI tasks. Prefer official command help for exact flags:
4
+
5
+ ```bash
6
+ op read --help
7
+ op run --help
8
+ op inject --help
9
+ op item --help
10
+ ```
11
+
12
+ ## Auth And Accounts
13
+
14
+ Check the CLI without touching secrets:
15
+
16
+ ```bash
17
+ op --version
18
+ op whoami
19
+ ```
20
+
21
+ If `op whoami` fails, run:
22
+
23
+ ```bash
24
+ op signin
25
+ ```
26
+
27
+ If the user has multiple accounts, avoid guessing. Use the account explicitly once known:
28
+
29
+ ```bash
30
+ op whoami --account <account>
31
+ OP_ACCOUNT=<account> op whoami
32
+ ```
33
+
34
+ ## Secret References
35
+
36
+ Standard reference:
37
+
38
+ ```text
39
+ op://<vault>/<item>/<field>
40
+ op://<vault>/<item>/<section>/<field>
41
+ ```
42
+
43
+ Common local convention for API Credential items:
44
+
45
+ ```text
46
+ op://Private/<title-or-id>/credential
47
+ ```
48
+
49
+ Use item IDs when the item title is unstable or ambiguous.
50
+
51
+ ## Prefer `op run`
52
+
53
+ Use `op run` when the target command accepts environment variables. It resolves secret references for the child process and masks secrets in stdout/stderr by default.
54
+
55
+ ```bash
56
+ API_KEY='op://Private/Service API/credential' op run -- ./script-that-reads-env
57
+ ```
58
+
59
+ With an env template:
60
+
61
+ ```bash
62
+ # .env.op contains references, not plaintext values.
63
+ API_KEY=op://Private/Service API/credential
64
+ DATABASE_URL=op://Private/App Database/url
65
+ ```
66
+
67
+ ```bash
68
+ op run --env-file .env.op -- npm run dev
69
+ ```
70
+
71
+ Do not use `--no-masking` unless the user explicitly asks to display the secret value.
72
+
73
+ ## Use `op inject` For Config Files
74
+
75
+ Use `op inject` when a config format needs inline substitution.
76
+
77
+ ```yaml
78
+ # config.yml.tpl
79
+ api_key: "{{ op://Private/Service API/credential }}"
80
+ ```
81
+
82
+ ```bash
83
+ op inject -i config.yml.tpl -o config.yml
84
+ ```
85
+
86
+ Delete resolved files once they are no longer needed. Keep template files because they contain references, not secret values.
87
+
88
+ ## Direct `op read`
89
+
90
+ Use direct reads only for commands that cannot use `op run` or templates. Keep the value inside the pipeline or command invocation, and do not show it in final responses.
91
+
92
+ ```bash
93
+ some-cli login --token "$(op read 'op://Private/Service API/credential')"
94
+ ```
95
+
96
+ Avoid command shapes that expose secrets through process arguments when the command supports stdin or environment variables.
97
+
98
+ ## Sensitive Outputs
99
+
100
+ Treat these as secret-bearing output:
101
+
102
+ - `op read`
103
+ - `op item get --reveal`
104
+ - `op run --no-masking`
105
+ - resolved files from `op inject`
106
+ - service account creation output
107
+ - one-time passwords, SSH private keys, cookies, and tokens
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: apple-calendar-event
3
+ description: Inspect, create, and verify events in macOS Calendar.app with osascript-backed local automation. Use when Codex needs to audit local Apple Calendar sources/defaults, understand Calendar.sqlitedb cache state, or add an event directly to a specific Apple Calendar calendar on the current Mac. Prefer Google Calendar skills for durable calendar writes unless the user explicitly asks for Apple Calendar.
4
+ ---
5
+
6
+ # Apple Calendar Event
7
+
8
+ Use this skill for local `Calendar.app` inspection and direct Apple Calendar writes.
9
+
10
+ ## Guardrails
11
+
12
+ - Prefer Google Calendar (`gws-calendar` / `gws-calendar-insert`) for durable user calendar writes unless the user explicitly asks for Apple Calendar.
13
+ - Treat `Calendar.sqlitedb` as a read-only cache for inspection. Never edit it directly.
14
+ - Do not trust Calendar.app's default calendar for automation. It may be `UseLastSelectedAsDefaultCalendar` or point at an unsuitable source.
15
+ - Do not identify a write target by display name alone when names are duplicated. Audit first, then use the exact calendar name only after confirming it is unambiguous enough for the requested write.
16
+ - Expect Apple Calendar to contain mixed stores: Google, iCloud, school CalDAV, subscribed calendars, Reminders, Siri suggestions, birthdays, and local/system stores.
17
+
18
+ ## Workflow
19
+
20
+ ### Audit local Calendar.app state
21
+
22
+ Use this before cleanup, migration planning, or any Apple write where the target source is unclear:
23
+
24
+ ```bash
25
+ python3 scripts/calendar_audit.py
26
+ python3 scripts/calendar_audit.py --json
27
+ ```
28
+
29
+ The audit reads `~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb` in read-only mode, maps calendars to their stores/accounts, reports duplicate display names, and maps the Calendar.app default calendar UUID when possible.
30
+
31
+ ### Create an Apple Calendar event
32
+
33
+ 1. Confirm the event title, target calendar name, local date, start time, and end time.
34
+ 2. If the target account/source is uncertain, run `scripts/calendar_audit.py` first.
35
+ 3. If only the calendar name is uncertain, run `scripts/calendar_event.py list-calendars` and pick the exact calendar name from the output.
36
+ 4. Put video links in `--location`. Put meeting numbers, interview notes, and buffer details in `--notes`.
37
+ 5. If the user asks to reserve buffer time, reflect that in the final blocked time range before writing the event.
38
+ 6. Create the event with `scripts/calendar_event.py create-event ...`.
39
+ 7. Verify the write with `scripts/calendar_event.py verify-event ...`.
40
+
41
+ ## Commands
42
+
43
+ List calendars:
44
+
45
+ ```bash
46
+ python3 scripts/calendar_event.py list-calendars
47
+ ```
48
+
49
+ Audit calendar sources and defaults:
50
+
51
+ ```bash
52
+ python3 scripts/calendar_audit.py
53
+ ```
54
+
55
+ Create an event:
56
+
57
+ ```bash
58
+ python3 scripts/calendar_event.py create-event \
59
+ --calendar "重要事件" \
60
+ --title "视频面试" \
61
+ --start "2026-04-15 11:30" \
62
+ --end "2026-04-15 12:45" \
63
+ --location "https://vc.feishu.cn/j/268399244" \
64
+ --notes $'面试时长:1小时,已额外预留缓冲时间至 12:45\n会议号:268399244'
65
+ ```
66
+
67
+ Verify the event:
68
+
69
+ ```bash
70
+ python3 scripts/calendar_event.py verify-event \
71
+ --calendar "重要事件" \
72
+ --title "视频面试" \
73
+ --start "2026-04-15 11:30"
74
+ ```
75
+
76
+ ## Notes
77
+
78
+ - Expect macOS to prompt for `Calendar` and `Automation` access the first time `osascript` runs.
79
+ - Keep times explicit in local time. Do not rely on phrases like “tomorrow morning” without converting them to exact timestamps first.
80
+ - Prefer editing the title only when the title itself is user-visible. Put operational detail, meeting IDs, and links in `location` and `notes`.
81
+ - Verify after every write. If verification fails, inspect the target calendar name again before retrying.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Apple Calendar Event"
3
+ short_description: "Audit, create, and verify macOS Calendar.app events."
4
+ default_prompt: "Use this skill to audit local Calendar.app sources/defaults or create and verify an Apple Calendar event in a specific calendar. Prefer Google Calendar for durable writes unless Apple Calendar is explicitly requested."
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import plistlib
7
+ import sqlite3
8
+ import subprocess
9
+ import sys
10
+ from collections import Counter
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+
15
+ DEFAULT_DB = (
16
+ Path.home()
17
+ / "Library"
18
+ / "Group Containers"
19
+ / "group.com.apple.calendar"
20
+ / "Calendar.sqlitedb"
21
+ )
22
+
23
+ STORE_TYPES = {
24
+ 0: "local/default",
25
+ 2: "caldav/account",
26
+ 4: "subscribed",
27
+ 5: "other/system",
28
+ 6: "reminders",
29
+ }
30
+
31
+
32
+ def run_command(command: list[str]) -> str | None:
33
+ result = subprocess.run(command, capture_output=True, text=True)
34
+ if result.returncode != 0:
35
+ return None
36
+ return result.stdout
37
+
38
+
39
+ def load_ical_defaults() -> dict[str, Any]:
40
+ output = run_command(["defaults", "export", "com.apple.iCal", "-"])
41
+ if not output:
42
+ return {}
43
+ try:
44
+ return plistlib.loads(output.encode())
45
+ except Exception:
46
+ return {}
47
+
48
+
49
+ def load_writable_names() -> dict[str, list[bool]]:
50
+ script = [
51
+ 'tell application "Calendar"',
52
+ "set rows to {}",
53
+ "repeat with c in calendars",
54
+ "set end of rows to ((name of c as text) & tab & (writable of c as text))",
55
+ "end repeat",
56
+ "set AppleScript's text item delimiters to linefeed",
57
+ "return rows as text",
58
+ "end tell",
59
+ ]
60
+ command: list[str] = ["osascript"]
61
+ for line in script:
62
+ command.extend(["-e", line])
63
+ output = run_command(command)
64
+ writable: dict[str, list[bool]] = {}
65
+ if not output:
66
+ return writable
67
+ for line in output.splitlines():
68
+ if not line.strip():
69
+ continue
70
+ try:
71
+ name, value = line.rsplit("\t", 1)
72
+ except ValueError:
73
+ continue
74
+ writable.setdefault(name, []).append(value.lower() == "true")
75
+ return writable
76
+
77
+
78
+ def query_calendars(db_path: Path) -> list[dict[str, Any]]:
79
+ if not db_path.exists():
80
+ raise SystemExit(f"Calendar database not found: {db_path}")
81
+
82
+ uri = f"file:{db_path}?mode=ro"
83
+ query = """
84
+ select
85
+ c.ROWID as calendar_rowid,
86
+ c.UUID as calendar_uuid,
87
+ c.title as calendar_title,
88
+ c.external_id as calendar_external_id,
89
+ c.type as calendar_type,
90
+ c.self_identity_email,
91
+ c.owner_identity_email,
92
+ c.shared_owner_address,
93
+ c.subcal_url,
94
+ s.ROWID as store_rowid,
95
+ s.name as store_name,
96
+ s.owner_name as store_owner,
97
+ s.type as store_type,
98
+ s.external_id as store_external_id,
99
+ s.persistent_id as store_persistent_id
100
+ from Calendar c
101
+ left join Store s on c.store_id = s.ROWID
102
+ order by s.name, c.display_order, c.title
103
+ """
104
+ with sqlite3.connect(uri, uri=True) as conn:
105
+ conn.row_factory = sqlite3.Row
106
+ rows = [dict(row) for row in conn.execute(query)]
107
+ for row in rows:
108
+ row["store_type_label"] = STORE_TYPES.get(row.get("store_type"), "unknown")
109
+ return rows
110
+
111
+
112
+ def build_report(db_path: Path) -> dict[str, Any]:
113
+ calendars = query_calendars(db_path)
114
+ writable_names = load_writable_names()
115
+ defaults = load_ical_defaults()
116
+
117
+ by_uuid = {row["calendar_uuid"]: row for row in calendars if row.get("calendar_uuid")}
118
+ default_uuid = defaults.get("defaultCalendarID")
119
+ default_calendar = by_uuid.get(default_uuid) if default_uuid else None
120
+
121
+ for row in calendars:
122
+ values = writable_names.get(row["calendar_title"], [])
123
+ if not values:
124
+ row["applescript_writable_by_name"] = None
125
+ elif len(values) == 1:
126
+ row["applescript_writable_by_name"] = values[0]
127
+ else:
128
+ row["applescript_writable_by_name"] = "ambiguous"
129
+
130
+ counts = Counter(row["calendar_title"] for row in calendars)
131
+ duplicates = sorted(name for name, count in counts.items() if count > 1)
132
+
133
+ return {
134
+ "database": str(db_path),
135
+ "default_policy": defaults.get("CalDefaultCalendar"),
136
+ "default_calendar_id": default_uuid,
137
+ "default_calendar": default_calendar,
138
+ "duplicate_display_names": duplicates,
139
+ "calendars": calendars,
140
+ }
141
+
142
+
143
+ def print_text(report: dict[str, Any]) -> None:
144
+ print(f"Database: {report['database']}")
145
+ print(f"Default policy: {report.get('default_policy') or '(unknown)'}")
146
+ default_calendar = report.get("default_calendar")
147
+ if default_calendar:
148
+ print(
149
+ "Default calendar: "
150
+ f"{default_calendar['calendar_title']} "
151
+ f"[store={default_calendar.get('store_name') or ''}, "
152
+ f"uuid={default_calendar.get('calendar_uuid') or ''}]"
153
+ )
154
+ elif report.get("default_calendar_id"):
155
+ print(f"Default calendar UUID: {report['default_calendar_id']} (not found)")
156
+ else:
157
+ print("Default calendar: (unknown)")
158
+
159
+ duplicates = report.get("duplicate_display_names") or []
160
+ if duplicates:
161
+ print("Duplicate display names: " + ", ".join(duplicates))
162
+ else:
163
+ print("Duplicate display names: none")
164
+
165
+ print()
166
+ print(
167
+ "Calendar\tStore\tStore type\tStore owner\tSelf identity\tOwner identity\tWritable by name\tUUID"
168
+ )
169
+ for row in report["calendars"]:
170
+ values = [
171
+ row.get("calendar_title") or "",
172
+ row.get("store_name") or "",
173
+ row.get("store_type_label") or "",
174
+ row.get("store_owner") or "",
175
+ row.get("self_identity_email") or "",
176
+ row.get("owner_identity_email") or "",
177
+ str(row.get("applescript_writable_by_name")),
178
+ row.get("calendar_uuid") or "",
179
+ ]
180
+ print("\t".join(values))
181
+
182
+
183
+ def main() -> int:
184
+ parser = argparse.ArgumentParser(
185
+ description="Read-only audit of macOS Calendar.app sources and default calendar."
186
+ )
187
+ parser.add_argument("--db", default=str(DEFAULT_DB), help="Calendar.sqlitedb path.")
188
+ parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
189
+ args = parser.parse_args()
190
+
191
+ db_path = Path(os.path.expanduser(args.db)).resolve()
192
+ report = build_report(db_path)
193
+ if args.json:
194
+ print(json.dumps(report, indent=2, ensure_ascii=False, default=str))
195
+ else:
196
+ print_text(report)
197
+ return 0
198
+
199
+
200
+ if __name__ == "__main__":
201
+ sys.exit(main())
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import datetime as dt
5
+ import subprocess
6
+ import sys
7
+
8
+
9
+ MONTH_NAMES = {
10
+ 1: "january",
11
+ 2: "february",
12
+ 3: "march",
13
+ 4: "april",
14
+ 5: "may",
15
+ 6: "june",
16
+ 7: "july",
17
+ 8: "august",
18
+ 9: "september",
19
+ 10: "october",
20
+ 11: "november",
21
+ 12: "december",
22
+ }
23
+
24
+
25
+ def parse_local_timestamp(value: str) -> dt.datetime:
26
+ try:
27
+ return dt.datetime.strptime(value, "%Y-%m-%d %H:%M")
28
+ except ValueError as exc:
29
+ raise SystemExit(f"Invalid timestamp '{value}'. Use 'YYYY-MM-DD HH:MM'.") from exc
30
+
31
+
32
+ def run_osascript(lines: list[str], args: list[str] | None = None) -> str:
33
+ command = ["osascript"]
34
+ for line in lines:
35
+ command.extend(["-e", line])
36
+ if args:
37
+ command.extend(args)
38
+
39
+ result = subprocess.run(command, capture_output=True, text=True)
40
+ if result.returncode != 0:
41
+ message = result.stderr.strip() or result.stdout.strip() or "osascript failed"
42
+ raise SystemExit(message)
43
+ return result.stdout.strip()
44
+
45
+
46
+ def applescript_date(var_name: str, value: dt.datetime) -> list[str]:
47
+ return [
48
+ f"set {var_name} to (current date)",
49
+ f"set year of {var_name} to {value.year}",
50
+ f"set month of {var_name} to {MONTH_NAMES[value.month]}",
51
+ f"set day of {var_name} to {value.day}",
52
+ f"set time of {var_name} to ({value.hour} * hours + {value.minute} * minutes + {value.second})",
53
+ ]
54
+
55
+
56
+ def cmd_list_calendars(_: argparse.Namespace) -> int:
57
+ output = run_osascript(
58
+ [
59
+ 'tell application "Calendar"',
60
+ "set calendarNames to name of every calendar",
61
+ "set AppleScript's text item delimiters to linefeed",
62
+ "return calendarNames as text",
63
+ "end tell",
64
+ ]
65
+ )
66
+ if output:
67
+ print(output)
68
+ return 0
69
+
70
+
71
+ def cmd_create_event(args: argparse.Namespace) -> int:
72
+ start = parse_local_timestamp(args.start)
73
+ end = parse_local_timestamp(args.end)
74
+ if end <= start:
75
+ raise SystemExit("--end must be later than --start.")
76
+
77
+ output = run_osascript(
78
+ [
79
+ "on run argv",
80
+ "set calendarName to item 1 of argv",
81
+ "set eventTitle to item 2 of argv",
82
+ "set eventLocation to item 3 of argv",
83
+ "set eventNotes to item 4 of argv",
84
+ *applescript_date("startDate", start),
85
+ *applescript_date("endDate", end),
86
+ 'tell application "Calendar"',
87
+ "set matchingCalendars to every calendar whose name is calendarName",
88
+ 'if (count of matchingCalendars) is 0 then error "Calendar not found: " & calendarName',
89
+ "set targetCalendar to first item of matchingCalendars",
90
+ "tell targetCalendar",
91
+ "set newEvent to make new event with properties {summary:eventTitle, start date:startDate, end date:endDate, location:eventLocation, description:eventNotes}",
92
+ 'return (summary of newEvent as text) & " | " & (start date of newEvent as text) & " | " & (end date of newEvent as text)',
93
+ "end tell",
94
+ "end tell",
95
+ "end run",
96
+ ],
97
+ [args.calendar, args.title, args.location or "", args.notes or ""],
98
+ )
99
+ print(output)
100
+ return 0
101
+
102
+
103
+ def cmd_verify_event(args: argparse.Namespace) -> int:
104
+ start = parse_local_timestamp(args.start)
105
+ output = run_osascript(
106
+ [
107
+ "on run argv",
108
+ "set calendarName to item 1 of argv",
109
+ "set eventTitle to item 2 of argv",
110
+ *applescript_date("targetStart", start),
111
+ 'tell application "Calendar"',
112
+ "set matchingCalendars to every calendar whose name is calendarName",
113
+ 'if (count of matchingCalendars) is 0 then error "Calendar not found: " & calendarName',
114
+ "set targetCalendar to first item of matchingCalendars",
115
+ "tell targetCalendar",
116
+ "set matchingEvents to every event whose summary is eventTitle and start date is targetStart",
117
+ 'if (count of matchingEvents) is 0 then error "Event not found"',
118
+ "set foundEvent to first item of matchingEvents",
119
+ 'return (summary of foundEvent as text) & " | " & (start date of foundEvent as text) & " | " & (end date of foundEvent as text) & " | " & (location of foundEvent as text)',
120
+ "end tell",
121
+ "end tell",
122
+ "end run",
123
+ ],
124
+ [args.calendar, args.title],
125
+ )
126
+ print(output)
127
+ return 0
128
+
129
+
130
+ def build_parser() -> argparse.ArgumentParser:
131
+ parser = argparse.ArgumentParser(
132
+ description="List, create, and verify events in macOS Calendar.app."
133
+ )
134
+ subparsers = parser.add_subparsers(dest="command", required=True)
135
+
136
+ list_parser = subparsers.add_parser("list-calendars", help="Print all calendar names.")
137
+ list_parser.set_defaults(func=cmd_list_calendars)
138
+
139
+ create_parser = subparsers.add_parser("create-event", help="Create a calendar event.")
140
+ create_parser.add_argument("--calendar", required=True, help="Target calendar name.")
141
+ create_parser.add_argument("--title", required=True, help="Event title.")
142
+ create_parser.add_argument("--start", required=True, help="Local start time in 'YYYY-MM-DD HH:MM'.")
143
+ create_parser.add_argument("--end", required=True, help="Local end time in 'YYYY-MM-DD HH:MM'.")
144
+ create_parser.add_argument("--location", default="", help="Event location or meeting URL.")
145
+ create_parser.add_argument("--notes", default="", help="Event notes.")
146
+ create_parser.set_defaults(func=cmd_create_event)
147
+
148
+ verify_parser = subparsers.add_parser("verify-event", help="Verify an event exists.")
149
+ verify_parser.add_argument("--calendar", required=True, help="Target calendar name.")
150
+ verify_parser.add_argument("--title", required=True, help="Event title.")
151
+ verify_parser.add_argument("--start", required=True, help="Local start time in 'YYYY-MM-DD HH:MM'.")
152
+ verify_parser.set_defaults(func=cmd_verify_event)
153
+
154
+ return parser
155
+
156
+
157
+ def main() -> int:
158
+ parser = build_parser()
159
+ args = parser.parse_args()
160
+ return args.func(args)
161
+
162
+
163
+ if __name__ == "__main__":
164
+ sys.exit(main())