From 8aa234c2139df1a2d8572f7896905202a6554288 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:01 -0700 Subject: [PATCH 01/92] Add agentfeeds skill: LICENSE --- skills/agentfeeds/LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 skills/agentfeeds/LICENSE diff --git a/skills/agentfeeds/LICENSE b/skills/agentfeeds/LICENSE new file mode 100644 index 00000000..c9dad29d --- /dev/null +++ b/skills/agentfeeds/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Agent Feeds contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From b549be8be9f6c80113d1eb3e6bfe63562cd6e9a0 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:02 -0700 Subject: [PATCH 02/92] Add agentfeeds skill: pyproject.toml --- skills/agentfeeds/pyproject.toml | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 skills/agentfeeds/pyproject.toml diff --git a/skills/agentfeeds/pyproject.toml b/skills/agentfeeds/pyproject.toml new file mode 100644 index 00000000..81feef3e --- /dev/null +++ b/skills/agentfeeds/pyproject.toml @@ -0,0 +1,38 @@ +[project] +name = "agentfeeds-skill" +version = "0.1.1" +description = "Local-first ambient context streams for compatible personal agents" +readme = "SKILL.md" +requires-python = ">=3.11" +license = "MIT" +dependencies = [ + "feedparser>=6.0.11", + "icalendar>=5.0.13", + "jmespath>=1.0.1", + "jsonschema>=4.22.0", + "PyYAML>=6.0.1", + "requests>=2.32.3", +] + +[project.scripts] +agentfeeds = "agentfeeds_runtime.commands:main" +agentfeeds-fetch = "agentfeeds_runtime.fetcher:main" +agentfeeds-install-poll = "agentfeeds_runtime.polling.install:main" +agentfeeds-uninstall-poll = "agentfeeds_runtime.polling.uninstall:main" + +[build-system] +requires = ["setuptools>=69"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["scripts/lib"] +include = ["agentfeeds_runtime*"] + +[dependency-groups] +dev = [ + "pillow>=10.0.0", + "pytest>=8.2.0", +] + +[tool.pytest.ini_options] +testpaths = ["tests"] From fb89b7be70165f32c0f9c78106564c17bb777b6d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:03 -0700 Subject: [PATCH 03/92] Add agentfeeds skill: SKILL.md --- skills/agentfeeds/SKILL.md | 142 +++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 skills/agentfeeds/SKILL.md diff --git a/skills/agentfeeds/SKILL.md b/skills/agentfeeds/SKILL.md new file mode 100644 index 00000000..107cde27 --- /dev/null +++ b/skills/agentfeeds/SKILL.md @@ -0,0 +1,142 @@ +--- +name: agentfeeds +description: Use Agent Feeds for ambient awareness from continuously refreshed local streams under ~/.agentfeeds. Use at session start to install/check background refresh and insert a compact stream brief, and before web search or expensive source-specific queries when a prompt may be covered by changing local context such as RSS/news, GitHub, calendars, weather, local files, personal sources, templates, subscriptions, or subscribed stream state. +version: 0.1.1 +author: verkyyi +license: MIT +metadata: + hermes: + tags: [Productivity, AI Agents, Personal Context, Local First] +--- + +# Agent Feeds + +Agent Feeds is a local-first ambient context layer for agents. A background fetcher keeps changing stream state warm on disk so agents can answer from local, inspectable context before re-searching, querying, processing, or asking the user to repeat information. + +Use this skill at session start, when managing feeds/subscriptions/templates, and before web search or expensive source-specific work if subscribed local state may already cover the prompt. + +Requires shell access, Python 3.11+, and either `pip` or `uv` for setup. Background polling is supported on macOS, Linux, FreeBSD, and WSL-style POSIX environments. The bundle includes a frozen template catalog for first use; network access is needed for setup, remote catalog updates, and public feed refreshes. + +## Command Map + +Use the bundled scripts from the skill root: + +```bash +python3 scripts/setup.py +python3 scripts/agentfeeds.py brief +python3 scripts/agentfeeds.py search --json +python3 scripts/agentfeeds.py streams health --json +python3 scripts/agentfeeds.py streams read --limit 20 --json +python3 scripts/agentfeeds.py streams find --json +python3 scripts/agentfeeds.py templates find +python3 scripts/agentfeeds.py subscribe [key=value ...] --dry-run --json +python3 scripts/agentfeeds.py subscribe [key=value ...] +python3 scripts/agentfeeds.py refresh --stream +``` + +`python3 scripts/agentfeeds.py` is the agent-facing CLI. `python3 scripts/agentfeeds_fetch.py` is an internal refresh worker used by polling and wrappers; prefer `agentfeeds.py refresh` in agent instructions. Runtime state defaults to `~/.agentfeeds/`; treat the file layout as an implementation detail except when debugging or editing a scaffolded local template. + +Vocabulary: + +- Template: reusable feed definition. Some templates are ready to subscribe with no parameters; others require parameters. +- Subscription: configured active instance of a template. +- Stream: refreshed readable data for an active subscription. + +References to load only when needed: + +- Runtime setup details: `references/runtime-setup.md` +- Template authoring details: `references/template-authoring.md` +- Background refresh details: `references/background-refresh.md` +- macOS personal source setup: `references/macos-personal-sources.md` + +Built-in templates come from the standalone catalog repo `https://github.com/verkyyi/agentfeeds-catalog` and are cached locally; user-local templates live under `~/.agentfeeds/templates/`. +The release bundle includes a frozen built-in catalog fallback so first-run discovery does not depend on GitHub being reachable. + +## Session Start + +At the start of each session: + +1. If the bundled CLI fails because dependencies are missing, run `python3 scripts/setup.py`. +2. Check background refresh with `python3 scripts/agentfeeds.py admin polling status --json`; if missing, run `python3 scripts/agentfeeds.py admin polling install`. +3. Check stream health with `python3 scripts/agentfeeds.py streams health --json`. +4. Generate stable compact context with `python3 scripts/agentfeeds.py brief`. +5. If the host supports prompt slots, place the exact brief output in a system-level or persistent context slot so stable stream metadata can benefit from model-side prompt caching. + +The default brief avoids volatile timestamps. Use `python3 scripts/agentfeeds.py brief --include-freshness` only for freshness/debugging questions. + +If health reports errors, missing state, or stale state, continue with available local context but tell the user ambient awareness is degraded when it affects the answer. + +## Answering Flow + +When a user prompt may be covered by subscribed changing context: + +1. Search local state first: `python3 scripts/agentfeeds.py search --json`. +2. If matches are non-stale and answer the prompt, read the matching stream with `python3 scripts/agentfeeds.py streams read --limit 20 --json` and answer from local state. +3. Refresh on demand only when the user asks about current/time-bounded data, the stream is older than 2x its poll interval, or the user explicitly asks to refresh. Use `python3 scripts/agentfeeds.py refresh --stream `, then search/read again. +4. If health shows a fetch error or missing state, explain the degraded source and ask for reconfiguration only when needed. +5. Use web search or source-specific external tools only when local streams do not cover the prompt, are stale and cannot refresh, or the user explicitly asks for outside/current web information beyond subscribed data. + +Use `streams find` only for stream metadata discovery. Use top-level `search` for content snippets. + +## Subscribe And Manage + +When the user asks to subscribe to a source: + +1. Search built-ins first with `python3 scripts/agentfeeds.py templates find `. +2. Inspect likely matches with `python3 scripts/agentfeeds.py templates show --json`. +3. Prefer a built-in template when it fits the source shape and auth model. +4. Collect only required parameters, preview with `python3 scripts/agentfeeds.py subscribe [key=value ...] --dry-run --json`, then subscribe with `python3 scripts/agentfeeds.py subscribe [key=value ...]`. +5. Confirm with `python3 scripts/agentfeeds.py streams health --json` and, if useful, one stream read. + +Default to answering first. Subscribe only when the user explicitly asks to subscribe, asks about a recurring topic, or a follow-up would clearly benefit from warm local state. If the user names a category rather than a source, list candidate templates and ask which source they mean. + +For unsubscribe: + +```bash +python3 scripts/agentfeeds.py streams list +python3 scripts/agentfeeds.py unsubscribe +``` + +If the user names a template instead of a concrete subscription, list matching active streams and ask which one to remove. + +## Template Strategy + +Balance built-in templates and local authoring this way: + +- Use built-in templates for common source classes, stable public APIs, shared schemas, and anything many operators would reuse. +- Use parameterized built-ins for source families: RSS/Atom URLs, GitHub repos, iCalendar URLs, weather coordinates, public JSON APIs. +- Use local templates for private files, private dashboards, one-off APIs, local tools, experimental sources, and operator-specific commands. +- Do not author a local template until built-ins have been checked and no suitable template fits. +- If a local template proves broadly useful, suggest upstreaming it to the catalog rather than keeping many near-duplicate local templates. +- Prefer local/private read-only sources before suggesting public feeds when the user wants personal context. + +When no built-in template fits: + +```bash +python3 scripts/agentfeeds.py admin templates adapters +python3 scripts/agentfeeds.py admin templates scaffold +python3 scripts/agentfeeds.py admin templates validate +python3 scripts/agentfeeds.py admin templates test key=value +``` + +Read `references/template-authoring.md` before editing scaffolded template YAML. + +For `local_command` templates, use argv arrays only. Only create command templates for explicitly requested or approved read-only commands. Avoid commands that mutate files, cloud resources, accounts, or external services. New `local_command` templates are pending and cannot run until approved by the operator. + +After scaffolding or installing a `local_command` template, tell the user to run `python3 scripts/agentfeeds.py admin templates approve-command [key=value ...]` themselves in an interactive terminal. Do not approve on the user's behalf, even if asked. Explain that approval is tied to the exact template and command digest, and edits revoke it. + +For macOS personal context, install local templates with: + +```bash +python3 scripts/agentfeeds.py admin macos install-templates +``` + +This installs pending read-only templates for Calendar, Reminders, and Mail. The operator must approve each one before subscribing or refreshing. + +## Safety Rules + +- Use `subscribe` and `unsubscribe` for subscription changes. +- Use `agentfeeds.py refresh` for refreshes. +- Do not hand-write state or status files. +- Do not include secret values in template YAML. Use `{{secret:name}}` references and tell the user to set values with `python3 scripts/agentfeeds.py admin secrets set `. +- Treat Agent Feeds as warm changing context, not durable memory, semantic search, or a data warehouse. From 1fbd472de7ae931c9a9782d49d75e8979d1a7466 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:04 -0700 Subject: [PATCH 04/92] Add agentfeeds skill: references/publishing.md --- skills/agentfeeds/references/publishing.md | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 skills/agentfeeds/references/publishing.md diff --git a/skills/agentfeeds/references/publishing.md b/skills/agentfeeds/references/publishing.md new file mode 100644 index 00000000..942c24c9 --- /dev/null +++ b/skills/agentfeeds/references/publishing.md @@ -0,0 +1,34 @@ +# Publishing + +This repository includes development files for tests, docs, packaging, and GitHub presentation. A Skills Hub release should publish a clean skill bundle instead of the raw repo. + +Build the publishable bundle from the repo root: + +```bash +python3 scripts/bundle/build_skill_bundle.py +``` + +The bundle should include: + +```text +SKILL.md +agents/ +assets/ +references/ +scripts/ +LICENSE +``` + +The bundle should exclude: + +```text +README.md +docs/ +tests/ +dist/ +build/ +*.egg-info/ +__pycache__/ +.pytest_cache/ +.venv/ +``` From c6078b42ee1e1c7ea4ee11a66ea8666e19871578 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:05 -0700 Subject: [PATCH 05/92] Add agentfeeds skill: references/runtime-setup.md --- skills/agentfeeds/references/runtime-setup.md | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 skills/agentfeeds/references/runtime-setup.md diff --git a/skills/agentfeeds/references/runtime-setup.md b/skills/agentfeeds/references/runtime-setup.md new file mode 100644 index 00000000..923cdeed --- /dev/null +++ b/skills/agentfeeds/references/runtime-setup.md @@ -0,0 +1,53 @@ +# Runtime Setup + +Run setup from the skill root before doing real work from a fresh checkout: + +```bash +python3 scripts/setup.py +``` + +The setup script installs an editable Python runtime into: + +```text +~/.agentfeeds/runtime-venv/ +``` + +The script wrappers re-exec through that virtual environment when it exists: + +```bash +python3 scripts/agentfeeds.py --help +python3 scripts/agentfeeds_fetch.py --help +``` + +If Python cannot create a venv with `pip`, `scripts/setup.py` falls back to `uv pip install` when `uv` is available. If both `pip` and `uv` are missing, install one of them and rerun setup. + +Console entry points installed by the package remain acceptable for local development: + +```bash +agentfeeds --help +agentfeeds-fetch --help +``` + +For portable skill usage, prefer the bundled scripts. + +After setup, verify or install background refresh: + +```bash +python3 scripts/agentfeeds.py admin polling status --json +python3 scripts/agentfeeds.py admin polling install +python3 scripts/agentfeeds.py streams health --json +``` + +At session start, generate the compact prompt brief: + +```bash +python3 scripts/agentfeeds.py brief +``` + +Use the default brief for stable prompt/context slots. It intentionally avoids volatile timestamps; use `--include-freshness` only for freshness debugging. + +When a user prompt may be covered by existing ambient context, search local state before rerunning source-specific work: + +```bash +python3 scripts/agentfeeds.py search --json +``` From 73223ae35740da15a07ac32970df928b431df716 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:06 -0700 Subject: [PATCH 06/92] Add agentfeeds skill: references/macos-personal-sources.md --- .../references/macos-personal-sources.md | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 skills/agentfeeds/references/macos-personal-sources.md diff --git a/skills/agentfeeds/references/macos-personal-sources.md b/skills/agentfeeds/references/macos-personal-sources.md new file mode 100644 index 00000000..691ef80f --- /dev/null +++ b/skills/agentfeeds/references/macos-personal-sources.md @@ -0,0 +1,67 @@ +# macOS Personal Sources + +Use this reference when the user wants local personal context from macOS apps such as Calendar, Reminders, or Mail. + +These sources are local `local_command` templates. They read from macOS apps through AppleScript, may trigger macOS Automation or app-data permission prompts on first refresh, and are pending until the operator approves each command. + +## Install Templates + +Install the local templates: + +```bash +python3 scripts/agentfeeds.py admin macos install-templates --json +``` + +This creates: + +- `macos/calendar-today`: today's local Calendar events +- `macos/reminders-open`: incomplete Reminders items +- `macos/mail-inbox-recent`: recent Mail inbox messages + +## Approval + +For each source the user wants, tell the user to run the approval command in an interactive terminal: + +```bash +python3 scripts/agentfeeds.py admin templates approve-command macos/calendar-today +python3 scripts/agentfeeds.py admin templates approve-command macos/reminders-open +python3 scripts/agentfeeds.py admin templates approve-command macos/mail-inbox-recent +``` + +Do not approve on the user's behalf. Approval prints the exact command and requires typing `APPROVE`. + +## Subscribe + +After approval, subscribe only the sources the user chose: + +```bash +python3 scripts/agentfeeds.py subscribe macos/calendar-today --title "Calendar today" +python3 scripts/agentfeeds.py subscribe macos/reminders-open --title "Open reminders" +python3 scripts/agentfeeds.py subscribe macos/mail-inbox-recent --title "Recent inbox mail" +``` + +Then check health: + +```bash +python3 scripts/agentfeeds.py streams health --json +``` + +If a source fails with a macOS permission error, tell the user to grant the requested Automation/app permission in System Settings, then refresh that one stream: + +```bash +python3 scripts/agentfeeds.py refresh --stream +``` + +## Answering + +For questions like "what is on my calendar today?", "what reminders are still open?", or "what recent mail needs attention?", search local state first: + +```bash +python3 scripts/agentfeeds.py search --json +``` + +Read the matching stream before answering: + +```bash +python3 scripts/agentfeeds.py streams read --limit 20 --json +``` From adc7b16ff1981717d7e84c89a830789c4d1162b9 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:06 -0700 Subject: [PATCH 07/92] Add agentfeeds skill: references/template-authoring.md --- .../references/template-authoring.md | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 skills/agentfeeds/references/template-authoring.md diff --git a/skills/agentfeeds/references/template-authoring.md b/skills/agentfeeds/references/template-authoring.md new file mode 100644 index 00000000..54bf8b82 --- /dev/null +++ b/skills/agentfeeds/references/template-authoring.md @@ -0,0 +1,77 @@ +# Template Authoring + +Use local templates when no built-in template fits a private file, dashboard, API, or approved read-only command. + +Built-in templates are sourced from the standalone catalog repository: + +```text +https://github.com/verkyyi/agentfeeds-catalog +``` + +The skill bundle includes a frozen catalog snapshot for offline first-run discovery. Updated catalog files are cached under `~/.agentfeeds/catalog-cache/` by the refresh worker. User-local templates live under `~/.agentfeeds/templates/` and are merged into discovery at runtime. + +Start by checking built-ins: + +```bash +python3 scripts/agentfeeds.py templates find +python3 scripts/agentfeeds.py templates show +``` + +List supported adapter kinds: + +```bash +python3 scripts/agentfeeds.py admin templates adapters +``` + +Scaffold a draft: + +```bash +python3 scripts/agentfeeds.py admin templates scaffold +``` + +The scaffold command prints the generated file paths. Edit the template YAML, then validate and dry-run it: + +```bash +python3 scripts/agentfeeds.py admin templates validate +python3 scripts/agentfeeds.py admin templates test key=value +``` + +Local templates live under the Agent Feeds runtime root: + +```text +~/.agentfeeds/templates/streams/ +~/.agentfeeds/templates/schemas/event-types/ +``` + +Use `local_command` only for explicitly approved read-only commands. Commands must be argv arrays, not shell strings. Avoid commands that mutate files, cloud resources, accounts, or external services. New command templates are written with `pending: true` and cannot run until the operator approves them. + +Local command templates require an interactive command digest approval before they can run: + +```bash +python3 scripts/agentfeeds.py admin templates approve-command [key=value ...] +``` + +Tell the user to run approval themselves in a terminal. Do not approve on the user's behalf. If the command, parameters, or template YAML change, approve the new digest before testing, subscribing, or refreshing. + +For templates that need secrets, store only references in YAML: + +```yaml +headers: + Authorization: "Bearer {{secret:github_token}}" +``` + +Tell the user to set the value with: + +```bash +python3 scripts/agentfeeds.py admin secrets set github_token +``` + +On macOS this uses Keychain when available; other platforms fall back to a local 0600 secret file under the Agent Feeds root. + +macOS local templates can be installed with: + +```bash +python3 scripts/agentfeeds.py admin macos install-templates +``` + +This writes pending Calendar, Reminders, and Mail templates. Approve only the ones the user wants enabled. From da54733d461c7933c66d2a9dec9995477fcb2987 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:07 -0700 Subject: [PATCH 08/92] Add agentfeeds skill: references/background-refresh.md --- .../references/background-refresh.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skills/agentfeeds/references/background-refresh.md diff --git a/skills/agentfeeds/references/background-refresh.md b/skills/agentfeeds/references/background-refresh.md new file mode 100644 index 00000000..b6f157e9 --- /dev/null +++ b/skills/agentfeeds/references/background-refresh.md @@ -0,0 +1,35 @@ +# Background Refresh + +Background refresh is required for normal Agent Feeds use. It keeps subscriptions warm between conversations so the agent can answer from local state instead of rerunning source-specific fetching, searching, querying, or processing on demand. + +Check scheduler status: + +```bash +python3 scripts/agentfeeds.py admin polling status --json +python3 scripts/agentfeeds.py streams health --json +``` + +Install or update background polling: + +```bash +python3 scripts/agentfeeds.py admin polling install +``` + +Uninstall polling only when the user no longer wants ambient refresh: + +```bash +python3 scripts/agentfeeds.py admin polling uninstall +``` + +On macOS, polling uses launchd. On Linux, FreeBSD, and WSL-style POSIX environments, polling uses a tagged crontab block. Native Windows polling is not currently supported. The runtime computes the shortest configured subscription interval and floors it at 5 minutes. + +Agents should try to verify or install polling at session start. If the scheduler is unsupported or unavailable, report that Agent Feeds can still refresh explicitly but ambient refresh is degraded. + +Use stream health to distinguish scheduler setup from per-stream fetch problems. `streams health --json` reports missing state, stale state, last success, last error, and consecutive failure count for each active subscription. + +Explicit refresh remains useful for immediate freshness: + +```bash +python3 scripts/agentfeeds.py refresh --stream +python3 scripts/agentfeeds.py refresh --all +``` From 1720ff68f36a642a1a8664b3822b5d2608d3fcfc Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:08 -0700 Subject: [PATCH 09/92] Add agentfeeds skill: catalog/INDEX.json --- skills/agentfeeds/catalog/INDEX.json | 609 +++++++++++++++++++++++++++ 1 file changed, 609 insertions(+) create mode 100644 skills/agentfeeds/catalog/INDEX.json diff --git a/skills/agentfeeds/catalog/INDEX.json b/skills/agentfeeds/catalog/INDEX.json new file mode 100644 index 00000000..f2151469 --- /dev/null +++ b/skills/agentfeeds/catalog/INDEX.json @@ -0,0 +1,609 @@ +{ + "generated_at": "2026-05-03T22:31:45Z", + "spec_version": "0.3", + "stream_count": 25, + "streams": [ + { + "auth": "none", + "catalog_order": 10, + "catalog_tier": 1, + "description": "Today's agenda from every calendar configured in Calendar.app.", + "id": "mac/calendar-today", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/calendar-today.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "calendar_tcc" + ], + "tags": [ + "mac", + "calendar", + "agenda", + "eventkit", + "tcc", + "no-auth" + ], + "title": "Today's Calendar.app agenda", + "type": "ical.event" + }, + { + "auth": "none", + "catalog_order": 20, + "catalog_tier": 1, + "description": "Next 7 days from every calendar configured in Calendar.app.", + "id": "mac/calendar-upcoming", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/calendar-upcoming.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "calendar_tcc" + ], + "tags": [ + "mac", + "calendar", + "upcoming", + "eventkit", + "tcc", + "no-auth" + ], + "title": "Upcoming Calendar.app events", + "type": "ical.event" + }, + { + "auth": "none", + "catalog_order": 30, + "catalog_tier": 1, + "description": "Open reminders with due dates and list grouping from Reminders.app.", + "id": "mac/reminders-pending", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/reminders-pending.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "reminders_tcc" + ], + "tags": [ + "mac", + "reminders", + "tasks", + "tcc", + "no-auth" + ], + "title": "Pending Reminders.app items", + "type": "mac.reminder" + }, + { + "auth": "none", + "catalog_order": 40, + "catalog_tier": 1, + "description": "Recently modified Notes.app notes with titles and snippets.", + "id": "mac/notes-recent", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/notes-recent.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "notes_automation_tcc" + ], + "tags": [ + "mac", + "notes", + "recent", + "automation", + "tcc", + "no-auth" + ], + "title": "Recent Notes.app notes", + "type": "mac.note" + }, + { + "auth": "none", + "catalog_order": 50, + "catalog_tier": 1, + "description": "Unread Mail.app subjects, senders, and snippets without message bodies.", + "id": "mac/mail-unread", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/mail-unread.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "mail_automation_tcc" + ], + "tags": [ + "mac", + "mail", + "inbox", + "unread", + "automation", + "tcc", + "no-auth" + ], + "title": "Unread Mail.app messages", + "type": "mac.mail-message" + }, + { + "auth": "none", + "catalog_order": 60, + "catalog_tier": 1, + "description": "Recent iMessage conversations with unread counts and last-message snippets.", + "id": "mac/imessage-unread", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/imessage-unread.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "requires": [ + "full_disk_access" + ], + "tags": [ + "mac", + "imessage", + "messages", + "unread", + "sqlite", + "tcc", + "no-auth" + ], + "title": "Unread iMessage conversations", + "type": "mac.imessage-thread" + }, + { + "auth": "none", + "catalog_order": 10, + "catalog_tier": 2, + "description": "Read-only snapshot of one local text, Markdown, or JSON file.", + "id": "local/file", + "mode": "snapshot", + "parameters": [ + "path" + ], + "path": "catalog/streams/local/file.yaml", + "quality_tier": "verified", + "tags": [ + "local", + "file", + "markdown", + "text", + "json", + "no-auth" + ], + "title": "Local file", + "type": "local.file" + }, + { + "auth": "none", + "catalog_order": 20, + "catalog_tier": 2, + "description": "Last modified files in a watched directory.", + "id": "local/directory-recent", + "mode": "event", + "parameters": [ + "path" + ], + "path": "catalog/streams/local/directory-recent.yaml", + "quality_tier": "experimental", + "tags": [ + "local", + "files", + "directory", + "recent", + "no-auth" + ], + "title": "Recent files in a directory", + "type": "local.directory-entry" + }, + { + "auth": "none", + "catalog_order": 30, + "catalog_tier": 2, + "description": "Recently modified Markdown notes from a local vault directory.", + "id": "local/markdown-vault", + "mode": "event", + "parameters": [ + "path" + ], + "path": "catalog/streams/local/markdown-vault.yaml", + "quality_tier": "experimental", + "tags": [ + "local", + "markdown", + "obsidian", + "notes", + "vault", + "no-auth" + ], + "title": "Markdown vault", + "type": "local.markdown-document" + }, + { + "auth": "none", + "catalog_order": 40, + "catalog_tier": 2, + "description": "Current branch, dirty files, and ahead or behind counts for a watched repo.", + "id": "local/git-status", + "mode": "snapshot", + "parameters": [ + "path" + ], + "path": "catalog/streams/local/git-status.yaml", + "quality_tier": "experimental", + "tags": [ + "local", + "git", + "dev", + "repo", + "status", + "no-auth" + ], + "title": "Local Git status", + "type": "local.git-status" + }, + { + "auth": "none", + "catalog_order": 50, + "catalog_tier": 2, + "description": "Items saved to Safari Reading List from the local bookmarks file.", + "id": "mac/safari-reading-list", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/safari-reading-list.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "tags": [ + "mac", + "safari", + "reading-list", + "bookmarks", + "no-auth" + ], + "title": "Safari Reading List", + "type": "mac.reading-list-item" + }, + { + "auth": "none", + "catalog_order": 60, + "catalog_tier": 2, + "description": "Last modified items in the local Downloads folder.", + "id": "mac/finder-recent-downloads", + "mode": "event", + "parameters": [], + "path": "catalog/streams/mac/finder-recent-downloads.yaml", + "platforms": [ + "macos" + ], + "quality_tier": "experimental", + "tags": [ + "mac", + "finder", + "downloads", + "files", + "recent", + "no-auth" + ], + "title": "Recent Downloads", + "type": "local.directory-entry" + }, + { + "auth": "bearer_token", + "catalog_order": 10, + "catalog_tier": 3, + "description": "Authenticated GitHub notifications for the current user.", + "id": "dev/github-notifications", + "mode": "event", + "parameters": [], + "path": "catalog/streams/dev/github-notifications.yaml", + "quality_tier": "experimental", + "requires": [ + "keychain_bearer_token" + ], + "tags": [ + "dev", + "github", + "notifications", + "keychain", + "auth" + ], + "title": "My GitHub notifications", + "type": "github.notification" + }, + { + "auth": "bearer_token", + "catalog_order": 20, + "catalog_tier": 3, + "description": "Pull requests authored by or assigned for review to the current GitHub user.", + "id": "dev/github-prs-mine", + "mode": "event", + "parameters": [], + "path": "catalog/streams/dev/github-prs-mine.yaml", + "quality_tier": "experimental", + "requires": [ + "keychain_bearer_token" + ], + "tags": [ + "dev", + "github", + "pull-request", + "review", + "keychain", + "auth" + ], + "title": "My GitHub pull requests", + "type": "github.pull-request" + }, + { + "auth": "bearer_token", + "catalog_order": 30, + "catalog_tier": 3, + "description": "Linear issues assigned to the authenticated user.", + "id": "tasks/linear-mine", + "mode": "event", + "parameters": [], + "path": "catalog/streams/tasks/linear-mine.yaml", + "quality_tier": "experimental", + "requires": [ + "keychain_bearer_token" + ], + "tags": [ + "tasks", + "linear", + "issues", + "assigned", + "keychain", + "auth" + ], + "title": "My Linear issues", + "type": "linear.issue" + }, + { + "auth": "bearer_token", + "catalog_order": 40, + "catalog_tier": 3, + "description": "Todoist tasks due today for the authenticated user.", + "id": "tasks/todoist-today", + "mode": "event", + "parameters": [], + "path": "catalog/streams/tasks/todoist-today.yaml", + "quality_tier": "experimental", + "requires": [ + "keychain_bearer_token" + ], + "tags": [ + "tasks", + "todoist", + "today", + "keychain", + "auth" + ], + "title": "Today's Todoist tasks", + "type": "todoist.task" + }, + { + "auth": "bearer_token", + "catalog_order": 50, + "catalog_tier": 3, + "description": "Recently edited Notion pages visible to the authenticated integration.", + "id": "notes/notion-recent", + "mode": "event", + "parameters": [], + "path": "catalog/streams/notes/notion-recent.yaml", + "quality_tier": "experimental", + "requires": [ + "keychain_bearer_token" + ], + "tags": [ + "notes", + "notion", + "recent", + "keychain", + "auth" + ], + "title": "Recent Notion pages", + "type": "notion.page" + }, + { + "auth": "none", + "catalog_order": 10, + "catalog_tier": 4, + "description": "Subscribe to any public RSS or Atom URL.", + "id": "news/rss-generic", + "mode": "event", + "parameters": [ + "url" + ], + "path": "catalog/streams/news/rss-generic.yaml", + "quality_tier": "verified", + "tags": [ + "news", + "rss", + "atom", + "free", + "no-auth" + ], + "title": "Generic RSS feed", + "type": "rss.item" + }, + { + "auth": "none", + "catalog_order": 20, + "catalog_tier": 4, + "description": "Events from a public iCalendar URL.", + "id": "calendar/ics", + "mode": "event", + "parameters": [ + "url" + ], + "path": "catalog/streams/calendar/ics.yaml", + "quality_tier": "verified", + "tags": [ + "calendar", + "ics", + "ical", + "events", + "free", + "no-auth" + ], + "title": "iCalendar feed", + "type": "ical.event" + }, + { + "auth": "none", + "catalog_order": 30, + "catalog_tier": 4, + "description": "Free, no-API-key weather observations for any latitude and longitude.", + "id": "weather/openmeteo-current", + "mode": "snapshot", + "parameters": [ + "lat", + "lon" + ], + "path": "catalog/streams/weather/openmeteo-current.yaml", + "quality_tier": "verified", + "tags": [ + "weather", + "free", + "no-auth", + "global" + ], + "title": "Current weather conditions (Open-Meteo)", + "type": "weather.observation" + }, + { + "auth": "none", + "catalog_order": 40, + "catalog_tier": 4, + "description": "Free, no-API-key 7-day forecast for any latitude and longitude.", + "id": "weather/openmeteo-forecast", + "mode": "snapshot", + "parameters": [ + "lat", + "lon" + ], + "path": "catalog/streams/weather/openmeteo-forecast.yaml", + "quality_tier": "verified", + "tags": [ + "weather", + "forecast", + "free", + "no-auth", + "global" + ], + "title": "7-day weather forecast (Open-Meteo)", + "type": "weather.forecast" + }, + { + "auth": "none", + "catalog_order": 50, + "catalog_tier": 4, + "description": "Current currency exchange rates for a base currency via a no-auth public API.", + "id": "finance/exchangerate", + "mode": "snapshot", + "parameters": [ + "base" + ], + "path": "catalog/streams/finance/exchangerate.yaml", + "quality_tier": "verified", + "tags": [ + "finance", + "currency", + "exchange-rate", + "free", + "no-auth" + ], + "title": "Current exchange rates", + "type": "finance.exchange-rate" + }, + { + "auth": "none", + "catalog_order": 60, + "catalog_tier": 4, + "description": "Recent pull requests for a public GitHub repository.", + "id": "dev/github-prs", + "mode": "event", + "parameters": [ + "owner", + "repo", + "state" + ], + "path": "catalog/streams/dev/github-prs.yaml", + "quality_tier": "verified", + "tags": [ + "dev", + "github", + "pull-request", + "prs", + "free", + "no-auth" + ], + "title": "GitHub repository pull requests", + "type": "github.pull-request" + }, + { + "auth": "none", + "catalog_order": 70, + "catalog_tier": 4, + "description": "Recent issues for a public GitHub repository.", + "id": "dev/github-issues", + "mode": "event", + "parameters": [ + "owner", + "repo", + "state" + ], + "path": "catalog/streams/dev/github-issues.yaml", + "quality_tier": "verified", + "tags": [ + "dev", + "github", + "issues", + "free", + "no-auth" + ], + "title": "GitHub repository issues", + "type": "github.issue" + }, + { + "auth": "none", + "catalog_order": 80, + "catalog_tier": 4, + "description": "Latest release events for a public GitHub repository.", + "id": "dev/github-releases", + "mode": "event", + "parameters": [ + "owner", + "repo" + ], + "path": "catalog/streams/dev/github-releases.yaml", + "quality_tier": "verified", + "tags": [ + "dev", + "github", + "releases", + "free", + "no-auth" + ], + "title": "GitHub repository releases", + "type": "github.release" + } + ] +} From 224b3015abd3db4f9b958154201e4a8223ac7ac3 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:09 -0700 Subject: [PATCH 10/92] Add agentfeeds skill: catalog/streams/tasks/linear-mine.yaml --- .../catalog/streams/tasks/linear-mine.yaml | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/tasks/linear-mine.yaml diff --git a/skills/agentfeeds/catalog/streams/tasks/linear-mine.yaml b/skills/agentfeeds/catalog/streams/tasks/linear-mine.yaml new file mode 100644 index 00000000..5841aa64 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/tasks/linear-mine.yaml @@ -0,0 +1,58 @@ +id: tasks/linear-mine +title: My Linear issues +description: Linear issues assigned to the authenticated user. +type: linear.issue +mode: event +schema_url: https://agentfeeds.dev/schemas/linear.issue.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://api.linear.app/graphql/issues/mine" +adapter: + kind: json_http + url: "https://api.linear.app/graphql" + method: POST + auth_service: linear + headers: + Content-Type: application/json + body: + query: | + query AgentFeedsMyIssues { + viewer { + assignedIssues(first: 50) { + nodes { + id + identifier + title + priority + url + updatedAt + dueDate + state { name } + team { name } + } + } + } + } + transform: + language: jmespath + expression: | + data.viewer.assignedIssues.nodes[].{ + id: id, + identifier: identifier, + title: title, + state: state.name, + priority: priority, + team: team.name, + url: url, + updated_at: updatedAt, + due_at: dueDate + } + id_from: id +recommended_poll_interval_seconds: 600 +auth: bearer_token +tags: [tasks, linear, issues, assigned, keychain, auth] +quality_tier: experimental +catalog_tier: 3 +catalog_order: 30 +requires: [keychain_bearer_token] +contributed_by: agentfeeds From 53ab5d3223a0238bfa4d386649ecb8ee9084ad3f Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:10 -0700 Subject: [PATCH 11/92] Add agentfeeds skill: catalog/streams/tasks/todoist-today.yaml --- .../catalog/streams/tasks/todoist-today.yaml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/tasks/todoist-today.yaml diff --git a/skills/agentfeeds/catalog/streams/tasks/todoist-today.yaml b/skills/agentfeeds/catalog/streams/tasks/todoist-today.yaml new file mode 100644 index 00000000..a262a57a --- /dev/null +++ b/skills/agentfeeds/catalog/streams/tasks/todoist-today.yaml @@ -0,0 +1,35 @@ +id: tasks/todoist-today +title: Today's Todoist tasks +description: Todoist tasks due today for the authenticated user. +type: todoist.task +mode: event +schema_url: https://agentfeeds.dev/schemas/todoist.task.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://api.todoist.com/tasks/today" +adapter: + kind: json_http + url: "https://api.todoist.com/rest/v2/tasks?filter=today" + method: GET + auth_service: todoist + transform: + language: jmespath + expression: | + [].{ + id: id, + content: content, + description: description, + url: url, + priority: priority, + project_id: project_id, + due: due + } + id_from: id +recommended_poll_interval_seconds: 600 +auth: bearer_token +tags: [tasks, todoist, today, keychain, auth] +quality_tier: experimental +catalog_tier: 3 +catalog_order: 40 +requires: [keychain_bearer_token] +contributed_by: agentfeeds From 0870a8ba0f63070eec9563578a23f0f73cea8337 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:10 -0700 Subject: [PATCH 12/92] Add agentfeeds skill: catalog/streams/calendar/ics.yaml --- .../catalog/streams/calendar/ics.yaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/calendar/ics.yaml diff --git a/skills/agentfeeds/catalog/streams/calendar/ics.yaml b/skills/agentfeeds/catalog/streams/calendar/ics.yaml new file mode 100644 index 00000000..7c6bd909 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/calendar/ics.yaml @@ -0,0 +1,23 @@ +id: calendar/ics +title: iCalendar feed +description: Events from a public iCalendar URL. +type: ical.event +mode: event +schema_url: https://agentfeeds.dev/schemas/ical-event.v1.json +schema_version: 1.0.0 +parameters: + - name: url + type: string + description: Public iCalendar URL + required: true +source_uri_template: "feed://calendar.local/ics?url={url}" +adapter: + kind: ical + url: "{url}" +recommended_poll_interval_seconds: 1800 +auth: none +tags: [calendar, ics, ical, events, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 20 +contributed_by: agentfeeds From 3a6e5ca34cd973b6ebad1027c8b6d519f2eda670 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:11 -0700 Subject: [PATCH 13/92] Add agentfeeds skill: catalog/streams/local/markdown-vault.yaml --- .../catalog/streams/local/markdown-vault.yaml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/local/markdown-vault.yaml diff --git a/skills/agentfeeds/catalog/streams/local/markdown-vault.yaml b/skills/agentfeeds/catalog/streams/local/markdown-vault.yaml new file mode 100644 index 00000000..3836537b --- /dev/null +++ b/skills/agentfeeds/catalog/streams/local/markdown-vault.yaml @@ -0,0 +1,26 @@ +id: local/markdown-vault +title: Markdown vault +description: Recently modified Markdown notes from a local vault directory. +type: local.markdown-document +mode: event +schema_url: https://agentfeeds.dev/schemas/local.markdown-document.v1.json +schema_version: 1.0.0 +parameters: + - name: path + type: string + description: Absolute or home-relative vault directory path + required: true +source_uri_template: "feed://local.markdown/vault?path={path}" +adapter: + kind: markdown_scan + path: "{path}" + parse_frontmatter: true + order_by: modified_at + limit: 25 +recommended_poll_interval_seconds: 300 +auth: none +tags: [local, markdown, obsidian, notes, vault, no-auth] +quality_tier: experimental +catalog_tier: 2 +catalog_order: 30 +contributed_by: agentfeeds From 2cca715bc6376959a30fadfda35b94d905a06a82 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:12 -0700 Subject: [PATCH 14/92] Add agentfeeds skill: catalog/streams/local/directory-recent.yaml --- .../streams/local/directory-recent.yaml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/local/directory-recent.yaml diff --git a/skills/agentfeeds/catalog/streams/local/directory-recent.yaml b/skills/agentfeeds/catalog/streams/local/directory-recent.yaml new file mode 100644 index 00000000..33ad8e02 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/local/directory-recent.yaml @@ -0,0 +1,25 @@ +id: local/directory-recent +title: Recent files in a directory +description: Last modified files in a watched directory. +type: local.directory-entry +mode: event +schema_url: https://agentfeeds.dev/schemas/local.directory-entry.v1.json +schema_version: 1.0.0 +parameters: + - name: path + type: string + description: Absolute or home-relative directory path + required: true +source_uri_template: "feed://local.directory/recent?path={path}" +adapter: + kind: filesystem_scan + path: "{path}" + order_by: modified_at + limit: 25 +recommended_poll_interval_seconds: 300 +auth: none +tags: [local, files, directory, recent, no-auth] +quality_tier: experimental +catalog_tier: 2 +catalog_order: 20 +contributed_by: agentfeeds From 8549172221cb5ba220eb4174030a7314409386d0 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:13 -0700 Subject: [PATCH 15/92] Add agentfeeds skill: catalog/streams/local/file.yaml --- .../catalog/streams/local/file.yaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/local/file.yaml diff --git a/skills/agentfeeds/catalog/streams/local/file.yaml b/skills/agentfeeds/catalog/streams/local/file.yaml new file mode 100644 index 00000000..e7714679 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/local/file.yaml @@ -0,0 +1,23 @@ +id: local/file +title: Local file +description: Read-only snapshot of one local text, Markdown, or JSON file. +type: local.file +mode: snapshot +schema_url: https://agentfeeds.dev/schemas/local.file.v1.json +schema_version: 1.0.0 +parameters: + - name: path + type: string + description: Absolute or home-relative file path + required: true +source_uri_template: "feed://local.file/file?path={path}" +adapter: + kind: local_file + path: "{path}" +recommended_poll_interval_seconds: 300 +auth: none +tags: [local, file, markdown, text, json, no-auth] +quality_tier: verified +catalog_tier: 2 +catalog_order: 10 +contributed_by: agentfeeds From 4804113229c3518f39064f2ccbc76e7427c2cf1c Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:14 -0700 Subject: [PATCH 16/92] Add agentfeeds skill: catalog/streams/local/git-status.yaml --- .../catalog/streams/local/git-status.yaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/local/git-status.yaml diff --git a/skills/agentfeeds/catalog/streams/local/git-status.yaml b/skills/agentfeeds/catalog/streams/local/git-status.yaml new file mode 100644 index 00000000..ce2b209f --- /dev/null +++ b/skills/agentfeeds/catalog/streams/local/git-status.yaml @@ -0,0 +1,23 @@ +id: local/git-status +title: Local Git status +description: Current branch, dirty files, and ahead or behind counts for a watched repo. +type: local.git-status +mode: snapshot +schema_url: https://agentfeeds.dev/schemas/local.git-status.v1.json +schema_version: 1.0.0 +parameters: + - name: path + type: string + description: Absolute or home-relative Git repository path + required: true +source_uri_template: "feed://local.git/status?path={path}" +adapter: + kind: git_status + path: "{path}" +recommended_poll_interval_seconds: 300 +auth: none +tags: [local, git, dev, repo, status, no-auth] +quality_tier: experimental +catalog_tier: 2 +catalog_order: 40 +contributed_by: agentfeeds From 2dd3164f24b12ba23e12f7c4c0c7c4d8f1fd396d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:14 -0700 Subject: [PATCH 17/92] Add agentfeeds skill: catalog/streams/mac/mail-unread.yaml --- .../catalog/streams/mac/mail-unread.yaml | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/mail-unread.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/mail-unread.yaml b/skills/agentfeeds/catalog/streams/mac/mail-unread.yaml new file mode 100644 index 00000000..c69241e5 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/mail-unread.yaml @@ -0,0 +1,47 @@ +id: mac/mail-unread +title: Unread Mail.app messages +description: Unread Mail.app subjects, senders, and snippets without message bodies. +type: mac.mail-message +mode: event +schema_url: https://agentfeeds.dev/schemas/mac.mail-message.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.mail/unread?limit=50" +adapter: + kind: apple_automation + tcc_permission: Automation + script: | + set rows to {} + tell application "Mail" + set matches to messages of inbox whose read status is false + set maxItems to count of matches + if maxItems > 50 then set maxItems to 50 + repeat with i from 1 to maxItems + set msg to item i of matches + set previewText to "" + try + set previewText to content of msg + if length of previewText > 300 then set previewText to text 1 thru 300 of previewText + end try + set end of rows to (message id of msg) & tab & (subject of msg) & tab & (sender of msg) & tab & ((date received of msg) as string) & tab & previewText + end repeat + end tell + set AppleScript's text item delimiters to linefeed + return rows as text + columns: [id, subject, sender, received_at, snippet] + static: + unread: true + mailbox: Inbox + from_email: null + account: null + id_column: id + time_column: received_at +recommended_poll_interval_seconds: 900 +auth: none +tags: [mac, mail, inbox, unread, automation, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 50 +platforms: [macos] +requires: [mail_automation_tcc] +contributed_by: agentfeeds From 5f588b2b111f0b4166c7e8130f157090bcdfad96 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:15 -0700 Subject: [PATCH 18/92] Add agentfeeds skill: catalog/streams/mac/calendar-today.yaml --- .../catalog/streams/mac/calendar-today.yaml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/calendar-today.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/calendar-today.yaml b/skills/agentfeeds/catalog/streams/mac/calendar-today.yaml new file mode 100644 index 00000000..14df5b23 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/calendar-today.yaml @@ -0,0 +1,46 @@ +id: mac/calendar-today +title: Today's Calendar.app agenda +description: Today's agenda from every calendar configured in Calendar.app. +type: ical.event +mode: event +schema_url: https://agentfeeds.dev/schemas/ical-event.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.calendar/today" +adapter: + kind: apple_automation + tcc_permission: Calendar + script: | + set startDate to current date + set hours of startDate to 0 + set minutes of startDate to 0 + set seconds of startDate to 0 + set endDate to startDate + (1 * days) + set rows to {} + tell application "Calendar" + repeat with cal in calendars + set calName to name of cal + set matches to every event of cal whose start date is greater than or equal to startDate and start date is less than endDate + repeat with ev in matches + set evLocation to "" + try + set evLocation to location of ev + end try + set end of rows to (uid of ev) & tab & (summary of ev) & tab & ((start date of ev) as string) & tab & ((end date of ev) as string) & tab & evLocation & tab & calName + end repeat + end repeat + end tell + set AppleScript's text item delimiters to linefeed + return rows as text + columns: [uid, summary, starts_at, ends_at, location, calendar_name] + id_column: uid + time_column: starts_at +recommended_poll_interval_seconds: 900 +auth: none +tags: [mac, calendar, agenda, eventkit, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 10 +platforms: [macos] +requires: [calendar_tcc] +contributed_by: agentfeeds From 0ded0ad9d202e5ad7cb8020a8b01875cc5d1d7c6 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:16 -0700 Subject: [PATCH 19/92] Add agentfeeds skill: catalog/streams/mac/calendar-upcoming.yaml --- .../streams/mac/calendar-upcoming.yaml | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/calendar-upcoming.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/calendar-upcoming.yaml b/skills/agentfeeds/catalog/streams/mac/calendar-upcoming.yaml new file mode 100644 index 00000000..18977b5f --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/calendar-upcoming.yaml @@ -0,0 +1,46 @@ +id: mac/calendar-upcoming +title: Upcoming Calendar.app events +description: Next 7 days from every calendar configured in Calendar.app. +type: ical.event +mode: event +schema_url: https://agentfeeds.dev/schemas/ical-event.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.calendar/upcoming?days=7" +adapter: + kind: apple_automation + tcc_permission: Calendar + script: | + set startDate to current date + set hours of startDate to 0 + set minutes of startDate to 0 + set seconds of startDate to 0 + set endDate to startDate + (7 * days) + set rows to {} + tell application "Calendar" + repeat with cal in calendars + set calName to name of cal + set matches to every event of cal whose start date is greater than or equal to startDate and start date is less than endDate + repeat with ev in matches + set evLocation to "" + try + set evLocation to location of ev + end try + set end of rows to (uid of ev) & tab & (summary of ev) & tab & ((start date of ev) as string) & tab & ((end date of ev) as string) & tab & evLocation & tab & calName + end repeat + end repeat + end tell + set AppleScript's text item delimiters to linefeed + return rows as text + columns: [uid, summary, starts_at, ends_at, location, calendar_name] + id_column: uid + time_column: starts_at +recommended_poll_interval_seconds: 3600 +auth: none +tags: [mac, calendar, upcoming, eventkit, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 20 +platforms: [macos] +requires: [calendar_tcc] +contributed_by: agentfeeds From 7bb22e77b8ddb90a5892ddec489d64c1766ae74d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:17 -0700 Subject: [PATCH 20/92] Add agentfeeds skill: catalog/streams/mac/notes-recent.yaml --- .../catalog/streams/mac/notes-recent.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/notes-recent.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/notes-recent.yaml b/skills/agentfeeds/catalog/streams/mac/notes-recent.yaml new file mode 100644 index 00000000..3d1d1869 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/notes-recent.yaml @@ -0,0 +1,45 @@ +id: mac/notes-recent +title: Recent Notes.app notes +description: Recently modified Notes.app notes with titles and snippets. +type: mac.note +mode: event +schema_url: https://agentfeeds.dev/schemas/mac.note.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.notes/recent?limit=20" +adapter: + kind: apple_automation + tcc_permission: Automation + script: | + set rows to {} + tell application "Notes" + set allNotes to notes + set maxItems to count of allNotes + if maxItems > 20 then set maxItems to 20 + repeat with i from 1 to maxItems + set n to item i of allNotes + set bodyText to plaintext of n + if length of bodyText > 300 then set bodyText to text 1 thru 300 of bodyText + set folderName to "" + try + set folderName to name of container of n + end try + set end of rows to (id of n) & tab & (name of n) & tab & bodyText & tab & ((modification date of n) as string) & tab & folderName + end repeat + end tell + set AppleScript's text item delimiters to linefeed + return rows as text + columns: [id, title, snippet, modified_at, folder] + static: + account: null + id_column: id + time_column: modified_at +recommended_poll_interval_seconds: 900 +auth: none +tags: [mac, notes, recent, automation, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 40 +platforms: [macos] +requires: [notes_automation_tcc] +contributed_by: agentfeeds From 08d10ec02941dc9bb99bcb791a10e4deda13b9aa Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:18 -0700 Subject: [PATCH 21/92] Add agentfeeds skill: catalog/streams/mac/finder-recent-downloads.yaml --- .../streams/mac/finder-recent-downloads.yaml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/finder-recent-downloads.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/finder-recent-downloads.yaml b/skills/agentfeeds/catalog/streams/mac/finder-recent-downloads.yaml new file mode 100644 index 00000000..61ff02e1 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/finder-recent-downloads.yaml @@ -0,0 +1,22 @@ +id: mac/finder-recent-downloads +title: Recent Downloads +description: Last modified items in the local Downloads folder. +type: local.directory-entry +mode: event +schema_url: https://agentfeeds.dev/schemas/local.directory-entry.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.finder/recent-downloads" +adapter: + kind: filesystem_scan + path: "~/Downloads" + order_by: modified_at + limit: 25 +recommended_poll_interval_seconds: 300 +auth: none +tags: [mac, finder, downloads, files, recent, no-auth] +quality_tier: experimental +catalog_tier: 2 +catalog_order: 60 +platforms: [macos] +contributed_by: agentfeeds From b0ea5cb902c0faad4554297411f66b8d477e2639 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:19 -0700 Subject: [PATCH 22/92] Add agentfeeds skill: catalog/streams/mac/imessage-unread.yaml --- .../catalog/streams/mac/imessage-unread.yaml | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/imessage-unread.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/imessage-unread.yaml b/skills/agentfeeds/catalog/streams/mac/imessage-unread.yaml new file mode 100644 index 00000000..da85c749 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/imessage-unread.yaml @@ -0,0 +1,39 @@ +id: mac/imessage-unread +title: Unread iMessage conversations +description: Recent iMessage conversations with unread counts and last-message snippets. +type: mac.imessage-thread +mode: event +schema_url: https://agentfeeds.dev/schemas/mac.imessage-thread.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.imessage/unread?limit=25" +adapter: + kind: sqlite_query + database: "~/Library/Messages/chat.db" + read_only: true + tcc_permission: Full Disk Access + query: | + SELECT chat.ROWID, chat.display_name, message.text, message.date + FROM chat + JOIN chat_message_join ON chat.ROWID = chat_message_join.chat_id + JOIN message ON message.ROWID = chat_message_join.message_id + WHERE message.is_read = 0 + ORDER BY message.date DESC + LIMIT 25 + columns: [thread_id, display_name, snippet, last_message_at] + timestamp_columns: + last_message_at: mac_absolute_ns + static: + participants: [] + unread_count: 1 + id_column: thread_id + time_column: last_message_at +recommended_poll_interval_seconds: 900 +auth: none +tags: [mac, imessage, messages, unread, sqlite, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 60 +platforms: [macos] +requires: [full_disk_access] +contributed_by: agentfeeds From 4791650dd930533e9d7405a5260b467f876c067c Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:20 -0700 Subject: [PATCH 23/92] Add agentfeeds skill: catalog/streams/mac/reminders-pending.yaml --- .../streams/mac/reminders-pending.yaml | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/reminders-pending.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/reminders-pending.yaml b/skills/agentfeeds/catalog/streams/mac/reminders-pending.yaml new file mode 100644 index 00000000..8b60f908 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/reminders-pending.yaml @@ -0,0 +1,60 @@ +id: mac/reminders-pending +title: Pending Reminders.app items +description: Open reminders with due dates and list grouping from Reminders.app. +type: mac.reminder +mode: event +schema_url: https://agentfeeds.dev/schemas/mac.reminder.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.reminders/pending" +adapter: + kind: apple_automation + tcc_permission: Reminders + script: | + set rows to {} + tell application "Reminders" + repeat with listRef in lists + set listName to name of listRef + set matches to every reminder of listRef whose completed is false + repeat with remRef in matches + set bodyText to "" + set dueText to "" + set priorityValue to 0 + try + set bodyText to body of remRef + end try + try + set dueText to (due date of remRef) as string + end try + try + set priorityValue to priority of remRef + end try + set end of rows to (id of remRef) & tab & (name of remRef) & tab & ((completed of remRef) as string) & tab & listName & tab & dueText & tab & (priorityValue as string) & tab & bodyText + end repeat + end repeat + end tell + set AppleScript's text item delimiters to linefeed + return rows as text + columns: + - id + - title + - name: completed + type: boolean + - list_name + - due_at + - name: priority + type: integer + - notes_snippet + static: + url: null + id_column: id + time_column: due_at +recommended_poll_interval_seconds: 900 +auth: none +tags: [mac, reminders, tasks, tcc, no-auth] +quality_tier: experimental +catalog_tier: 1 +catalog_order: 30 +platforms: [macos] +requires: [reminders_tcc] +contributed_by: agentfeeds From 96c8938c2b55c92713243a2cb8b2fd5518a2627b Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:21 -0700 Subject: [PATCH 24/92] Add agentfeeds skill: catalog/streams/mac/safari-reading-list.yaml --- .../streams/mac/safari-reading-list.yaml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/mac/safari-reading-list.yaml diff --git a/skills/agentfeeds/catalog/streams/mac/safari-reading-list.yaml b/skills/agentfeeds/catalog/streams/mac/safari-reading-list.yaml new file mode 100644 index 00000000..6f75e037 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/mac/safari-reading-list.yaml @@ -0,0 +1,21 @@ +id: mac/safari-reading-list +title: Safari Reading List +description: Items saved to Safari Reading List from the local bookmarks file. +type: mac.reading-list-item +mode: event +schema_url: https://agentfeeds.dev/schemas/mac.reading-list-item.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://mac.safari/reading-list" +adapter: + kind: plist_reading_list + path: "~/Library/Safari/Bookmarks.plist" + limit: 50 +recommended_poll_interval_seconds: 3600 +auth: none +tags: [mac, safari, reading-list, bookmarks, no-auth] +quality_tier: experimental +catalog_tier: 2 +catalog_order: 50 +platforms: [macos] +contributed_by: agentfeeds From b3bacfc2c8092efac1be81985042f24eef95caf7 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:21 -0700 Subject: [PATCH 25/92] Add agentfeeds skill: catalog/streams/notes/notion-recent.yaml --- .../catalog/streams/notes/notion-recent.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/notes/notion-recent.yaml diff --git a/skills/agentfeeds/catalog/streams/notes/notion-recent.yaml b/skills/agentfeeds/catalog/streams/notes/notion-recent.yaml new file mode 100644 index 00000000..f96f1018 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/notes/notion-recent.yaml @@ -0,0 +1,45 @@ +id: notes/notion-recent +title: Recent Notion pages +description: Recently edited Notion pages visible to the authenticated integration. +type: notion.page +mode: event +schema_url: https://agentfeeds.dev/schemas/notion.page.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://api.notion.com/pages/recent" +adapter: + kind: json_http + url: "https://api.notion.com/v1/search" + method: POST + auth_service: notion + headers: + Content-Type: application/json + Notion-Version: "2022-06-28" + body: + page_size: 50 + filter: + property: object + value: page + sort: + direction: descending + timestamp: last_edited_time + transform: + language: jmespath + expression: | + results[].{ + id: id, + title: properties.*.title[0].plain_text | [0], + url: url, + last_edited_at: last_edited_time, + created_at: created_time, + parent_type: parent.type + } + id_from: id +recommended_poll_interval_seconds: 900 +auth: bearer_token +tags: [notes, notion, recent, keychain, auth] +quality_tier: experimental +catalog_tier: 3 +catalog_order: 50 +requires: [keychain_bearer_token] +contributed_by: agentfeeds From a489e1a1820d3aac0359fa877fefeae236513342 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:22 -0700 Subject: [PATCH 26/92] Add agentfeeds skill: catalog/streams/news/rss-generic.yaml --- .../catalog/streams/news/rss-generic.yaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/news/rss-generic.yaml diff --git a/skills/agentfeeds/catalog/streams/news/rss-generic.yaml b/skills/agentfeeds/catalog/streams/news/rss-generic.yaml new file mode 100644 index 00000000..10892f6d --- /dev/null +++ b/skills/agentfeeds/catalog/streams/news/rss-generic.yaml @@ -0,0 +1,23 @@ +id: news/rss-generic +title: Generic RSS feed +description: Subscribe to any public RSS or Atom URL. +type: rss.item +mode: event +schema_url: https://agentfeeds.dev/schemas/rss-item.v1.json +schema_version: 1.0.0 +parameters: + - name: url + type: string + description: Public RSS or Atom feed URL + required: true +source_uri_template: "feed://rss.local/generic?url={url}" +adapter: + kind: rss + url: "{url}" +recommended_poll_interval_seconds: 900 +auth: none +tags: [news, rss, atom, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 10 +contributed_by: agentfeeds From 9961d00808c6f1d8dfa91d65808b88bae7134e4d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:23 -0700 Subject: [PATCH 27/92] Add agentfeeds skill: catalog/streams/dev/github-notifications.yaml --- .../streams/dev/github-notifications.yaml | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/dev/github-notifications.yaml diff --git a/skills/agentfeeds/catalog/streams/dev/github-notifications.yaml b/skills/agentfeeds/catalog/streams/dev/github-notifications.yaml new file mode 100644 index 00000000..8a317d28 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/dev/github-notifications.yaml @@ -0,0 +1,38 @@ +id: dev/github-notifications +title: My GitHub notifications +description: Authenticated GitHub notifications for the current user. +type: github.notification +mode: event +schema_url: https://agentfeeds.dev/schemas/github.notification.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://api.github.com/notifications" +adapter: + kind: paginated_json_http + url: "https://api.github.com/notifications?per_page=50" + method: GET + auth_service: github + headers: + Accept: application/vnd.github+json + transform: + language: jmespath + expression: | + [].{ + id: id, + reason: reason, + unread: unread, + updated_at: updated_at, + repository: repository.full_name, + subject_title: subject.title, + subject_type: subject.type, + url: subject.url + } + id_from: id +recommended_poll_interval_seconds: 300 +auth: bearer_token +tags: [dev, github, notifications, keychain, auth] +quality_tier: experimental +catalog_tier: 3 +catalog_order: 10 +requires: [keychain_bearer_token] +contributed_by: agentfeeds From 662f8db005e256c6c9435d059bd6ab4267465763 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:24 -0700 Subject: [PATCH 28/92] Add agentfeeds skill: catalog/streams/dev/github-issues.yaml --- .../catalog/streams/dev/github-issues.yaml | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/dev/github-issues.yaml diff --git a/skills/agentfeeds/catalog/streams/dev/github-issues.yaml b/skills/agentfeeds/catalog/streams/dev/github-issues.yaml new file mode 100644 index 00000000..240249a4 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/dev/github-issues.yaml @@ -0,0 +1,50 @@ +id: dev/github-issues +title: GitHub repository issues +description: Recent issues for a public GitHub repository. +type: github.issue +mode: event +schema_url: https://agentfeeds.dev/schemas/github.issue.v1.json +schema_version: 1.0.0 +parameters: + - name: owner + type: string + description: GitHub repository owner + required: true + - name: repo + type: string + description: GitHub repository name + required: true + - name: state + type: string + description: Issue state, such as open, closed, or all + required: true +source_uri_template: "feed://api.github.com/repos/{owner}/{repo}/issues?state={state}" +adapter: + kind: paginated_json_http + url: "https://api.github.com/repos/{owner}/{repo}/issues?state={state}&per_page=30" + method: GET + headers: + Accept: application/vnd.github+json + transform: + language: jmespath + expression: | + [?pull_request==`null`].{ + number: number, + title: title, + state: state, + html_url: html_url, + user: user.login, + labels: labels[].name, + created_at: created_at, + updated_at: updated_at, + closed_at: closed_at, + body: body + } + id_from: number +recommended_poll_interval_seconds: 900 +auth: none +tags: [dev, github, issues, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 70 +contributed_by: agentfeeds From 53e2611ea8b230428ae8d9f1e43510241ebc481e Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:25 -0700 Subject: [PATCH 29/92] Add agentfeeds skill: catalog/streams/dev/github-prs.yaml --- .../catalog/streams/dev/github-prs.yaml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/dev/github-prs.yaml diff --git a/skills/agentfeeds/catalog/streams/dev/github-prs.yaml b/skills/agentfeeds/catalog/streams/dev/github-prs.yaml new file mode 100644 index 00000000..7b92957a --- /dev/null +++ b/skills/agentfeeds/catalog/streams/dev/github-prs.yaml @@ -0,0 +1,53 @@ +id: dev/github-prs +title: GitHub repository pull requests +description: Recent pull requests for a public GitHub repository. +type: github.pull-request +mode: event +schema_url: https://agentfeeds.dev/schemas/github.pull-request.v1.json +schema_version: 1.0.0 +parameters: + - name: owner + type: string + description: GitHub repository owner + required: true + - name: repo + type: string + description: GitHub repository name + required: true + - name: state + type: string + description: Pull request state, such as open, closed, or all + required: true +source_uri_template: "feed://api.github.com/repos/{owner}/{repo}/pulls?state={state}" +adapter: + kind: paginated_json_http + url: "https://api.github.com/repos/{owner}/{repo}/pulls?state={state}&per_page=30" + method: GET + headers: + Accept: application/vnd.github+json + transform: + language: jmespath + expression: | + [].{ + number: number, + title: title, + state: state, + html_url: html_url, + user: user.login, + draft: draft, + head_ref: head.ref, + base_ref: base.ref, + created_at: created_at, + updated_at: updated_at, + closed_at: closed_at, + merged_at: merged_at, + body: body + } + id_from: number +recommended_poll_interval_seconds: 900 +auth: none +tags: [dev, github, pull-request, prs, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 60 +contributed_by: agentfeeds From 70520dd218b8f1b2c5e9a305770153dc75d50725 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:25 -0700 Subject: [PATCH 30/92] Add agentfeeds skill: catalog/streams/dev/github-releases.yaml --- .../catalog/streams/dev/github-releases.yaml | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/dev/github-releases.yaml diff --git a/skills/agentfeeds/catalog/streams/dev/github-releases.yaml b/skills/agentfeeds/catalog/streams/dev/github-releases.yaml new file mode 100644 index 00000000..4a091579 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/dev/github-releases.yaml @@ -0,0 +1,41 @@ +id: dev/github-releases +title: GitHub repository releases +description: Latest release events for a public GitHub repository. +type: github.release +mode: event +schema_url: https://agentfeeds.dev/schemas/github.release.v1.json +schema_version: 1.0.0 +parameters: + - name: owner + type: string + description: GitHub repository owner + required: true + - name: repo + type: string + description: GitHub repository name + required: true +source_uri_template: "feed://api.github.com/repos/{owner}/{repo}/releases" +adapter: + kind: paginated_json_http + url: "https://api.github.com/repos/{owner}/{repo}/releases" + method: GET + headers: + Accept: application/vnd.github+json + transform: + language: jmespath + expression: | + [].{ + tag_name: tag_name, + name: name, + published_at: published_at, + html_url: html_url, + body: body + } + id_from: tag_name +recommended_poll_interval_seconds: 3600 +auth: none +tags: [dev, github, releases, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 80 +contributed_by: agentfeeds From 748d4c57c45dccc0d5c4b687879946b4d21006a9 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:26 -0700 Subject: [PATCH 31/92] Add agentfeeds skill: catalog/streams/dev/github-prs-mine.yaml --- .../catalog/streams/dev/github-prs-mine.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/dev/github-prs-mine.yaml diff --git a/skills/agentfeeds/catalog/streams/dev/github-prs-mine.yaml b/skills/agentfeeds/catalog/streams/dev/github-prs-mine.yaml new file mode 100644 index 00000000..da875b05 --- /dev/null +++ b/skills/agentfeeds/catalog/streams/dev/github-prs-mine.yaml @@ -0,0 +1,43 @@ +id: dev/github-prs-mine +title: My GitHub pull requests +description: Pull requests authored by or assigned for review to the current GitHub user. +type: github.pull-request +mode: event +schema_url: https://agentfeeds.dev/schemas/github.pull-request.v1.json +schema_version: 1.0.0 +parameters: [] +source_uri_template: "feed://api.github.com/search/issues?q=type:pr+involves:@me" +adapter: + kind: paginated_json_http + url: "https://api.github.com/search/issues?q=type:pr+involves:@me+state:open&per_page=50" + method: GET + auth_service: github + headers: + Accept: application/vnd.github+json + transform: + language: jmespath + expression: | + items[].{ + number: number, + title: title, + state: state, + html_url: html_url, + user: user.login, + draft: false, + head_ref: null, + base_ref: null, + created_at: created_at, + updated_at: updated_at, + closed_at: closed_at, + merged_at: null, + body: body + } + id_from: html_url +recommended_poll_interval_seconds: 600 +auth: bearer_token +tags: [dev, github, pull-request, review, keychain, auth] +quality_tier: experimental +catalog_tier: 3 +catalog_order: 20 +requires: [keychain_bearer_token] +contributed_by: agentfeeds From c1dd214be514842ad7bfdf0b9f3a633685ff41ed Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:27 -0700 Subject: [PATCH 32/92] Add agentfeeds skill: catalog/streams/finance/exchangerate.yaml --- .../catalog/streams/finance/exchangerate.yaml | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/finance/exchangerate.yaml diff --git a/skills/agentfeeds/catalog/streams/finance/exchangerate.yaml b/skills/agentfeeds/catalog/streams/finance/exchangerate.yaml new file mode 100644 index 00000000..36ca6e9c --- /dev/null +++ b/skills/agentfeeds/catalog/streams/finance/exchangerate.yaml @@ -0,0 +1,34 @@ +id: finance/exchangerate +title: Current exchange rates +description: Current currency exchange rates for a base currency via a no-auth public API. +type: finance.exchange-rate +mode: snapshot +schema_url: https://agentfeeds.dev/schemas/finance.exchange-rate.v1.json +schema_version: 1.0.0 +parameters: + - name: base + type: string + description: Base currency code such as USD or EUR + required: true +source_uri_template: "feed://open.er-api.com/v6/latest/{base}" +adapter: + kind: json_http + url: "https://open.er-api.com/v6/latest/{base}" + method: GET + headers: {} + transform: + language: jmespath + expression: | + { + base: base_code, + date: time_last_update_utc, + rates: rates + } + id_from: "join('-', [base_code, to_string(time_last_update_unix)])" +recommended_poll_interval_seconds: 3600 +auth: none +tags: [finance, currency, exchange-rate, free, no-auth] +quality_tier: verified +catalog_tier: 4 +catalog_order: 50 +contributed_by: agentfeeds From 20a1c7a6042de03b74caf9168a0eed4af8c0d231 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:28 -0700 Subject: [PATCH 33/92] Add agentfeeds skill: catalog/streams/weather/openmeteo-current.yaml --- .../streams/weather/openmeteo-current.yaml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/weather/openmeteo-current.yaml diff --git a/skills/agentfeeds/catalog/streams/weather/openmeteo-current.yaml b/skills/agentfeeds/catalog/streams/weather/openmeteo-current.yaml new file mode 100644 index 00000000..9e95c12b --- /dev/null +++ b/skills/agentfeeds/catalog/streams/weather/openmeteo-current.yaml @@ -0,0 +1,40 @@ +id: weather/openmeteo-current +title: Current weather conditions (Open-Meteo) +description: Free, no-API-key weather observations for any latitude and longitude. +type: weather.observation +mode: snapshot +schema_url: https://agentfeeds.dev/schemas/weather.observation.v1.json +schema_version: 1.0.0 +parameters: + - name: lat + type: number + description: Latitude (-90 to 90) + required: true + - name: lon + type: number + description: Longitude (-180 to 180) + required: true +source_uri_template: "feed://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}" +adapter: + kind: json_http + url: "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code" + method: GET + headers: {} + transform: + language: jmespath + expression: | + { + temperature_c: current.temperature_2m, + humidity_pct: current.relative_humidity_2m, + wind_kph: current.wind_speed_10m, + conditions_code: current.weather_code, + observed_at: current.time + } + id_from: "join('-', [current.time, to_string(latitude), to_string(longitude)])" +recommended_poll_interval_seconds: 600 +auth: none +tags: [weather, free, no-auth, global] +quality_tier: verified +catalog_tier: 4 +catalog_order: 30 +contributed_by: agentfeeds From 9f3856b85c17ce94b0e752d31661df628e5f7bdf Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:29 -0700 Subject: [PATCH 34/92] Add agentfeeds skill: catalog/streams/weather/openmeteo-forecast.yaml --- .../streams/weather/openmeteo-forecast.yaml | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 skills/agentfeeds/catalog/streams/weather/openmeteo-forecast.yaml diff --git a/skills/agentfeeds/catalog/streams/weather/openmeteo-forecast.yaml b/skills/agentfeeds/catalog/streams/weather/openmeteo-forecast.yaml new file mode 100644 index 00000000..7983b1df --- /dev/null +++ b/skills/agentfeeds/catalog/streams/weather/openmeteo-forecast.yaml @@ -0,0 +1,37 @@ +id: weather/openmeteo-forecast +title: 7-day weather forecast (Open-Meteo) +description: Free, no-API-key 7-day forecast for any latitude and longitude. +type: weather.forecast +mode: snapshot +schema_url: https://agentfeeds.dev/schemas/weather.forecast.v1.json +schema_version: 1.0.0 +parameters: + - name: lat + type: number + description: Latitude (-90 to 90) + required: true + - name: lon + type: number + description: Longitude (-180 to 180) + required: true +source_uri_template: "feed://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,weather_code" +adapter: + kind: json_http + url: "https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,weather_code&forecast_days=7" + method: GET + headers: {} + transform: + language: jmespath + expression: | + { + generated_at: timezone, + daily: daily + } + id_from: "join('-', ['forecast', to_string(latitude), to_string(longitude)])" +recommended_poll_interval_seconds: 3600 +auth: none +tags: [weather, forecast, free, no-auth, global] +quality_tier: verified +catalog_tier: 4 +catalog_order: 40 +contributed_by: agentfeeds From ed5a36985e778f1a0fd04cedef5034276211d021 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:30 -0700 Subject: [PATCH 35/92] Add agentfeeds skill: catalog/schemas/envelope.v0.3.json --- .../catalog/schemas/envelope.v0.3.json | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/envelope.v0.3.json diff --git a/skills/agentfeeds/catalog/schemas/envelope.v0.3.json b/skills/agentfeeds/catalog/schemas/envelope.v0.3.json new file mode 100644 index 00000000..7503976b --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/envelope.v0.3.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/envelope.v0.3.json", + "title": "Agent Feeds Event Envelope", + "type": "object", + "additionalProperties": false, + "required": [ + "specversion", + "id", + "source", + "type", + "time", + "schema_url", + "schema_version", + "mode", + "data" + ], + "properties": { + "specversion": { + "const": "agentfeeds/0.3" + }, + "id": { + "type": "string", + "minLength": 1 + }, + "source": { + "type": "string", + "pattern": "^feed://" + }, + "type": { + "type": "string", + "minLength": 1 + }, + "time": { + "type": "string", + "format": "date-time" + }, + "schema_url": { + "type": "string", + "format": "uri" + }, + "schema_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "mode": { + "enum": ["snapshot", "event", "delta"] + }, + "data": { + "type": "object" + } + } +} From eca6b736b2634a134748fc4a606107dc806d96ad Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:31 -0700 Subject: [PATCH 36/92] Add agentfeeds skill: catalog/schemas/stream-definition.v0.3.json --- .../schemas/stream-definition.v0.3.json | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/stream-definition.v0.3.json diff --git a/skills/agentfeeds/catalog/schemas/stream-definition.v0.3.json b/skills/agentfeeds/catalog/schemas/stream-definition.v0.3.json new file mode 100644 index 00000000..9badfe63 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/stream-definition.v0.3.json @@ -0,0 +1,121 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/stream-definition.v0.3.json", + "title": "Agent Feeds Stream Definition", + "type": "object", + "additionalProperties": true, + "required": [ + "id", + "title", + "description", + "type", + "mode", + "schema_url", + "schema_version", + "source_uri_template", + "adapter", + "recommended_poll_interval_seconds", + "auth", + "tags", + "quality_tier" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z0-9-]+/[a-z0-9-]+$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "minLength": 1 + }, + "mode": { + "enum": ["snapshot", "event", "delta"] + }, + "schema_url": { + "type": "string", + "format": "uri" + }, + "schema_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "type", "description", "required"], + "properties": { + "name": { "type": "string" }, + "type": { "enum": ["string", "number", "integer", "boolean"] }, + "description": { "type": "string" }, + "required": { "type": "boolean" } + } + }, + "default": [] + }, + "source_uri_template": { + "type": "string", + "pattern": "^feed://" + }, + "adapter": { + "type": "object", + "required": ["kind"], + "properties": { + "kind": { + "enum": [ + "json_http", + "paginated_json_http", + "rss", + "ical", + "local_file", + "filesystem_scan", + "markdown_scan", + "git_status", + "local_command", + "apple_automation", + "sqlite_query", + "plist_reading_list" + ] + } + } + }, + "recommended_poll_interval_seconds": { + "type": "integer", + "minimum": 60 + }, + "auth": { + "enum": ["none", "bearer_token"] + }, + "tags": { + "type": "array", + "items": { "type": "string" } + }, + "catalog_tier": { + "type": "integer", + "minimum": 1 + }, + "catalog_order": { + "type": "integer", + "minimum": 1 + }, + "platforms": { + "type": "array", + "items": { "type": "string" } + }, + "requires": { + "type": "array", + "items": { "type": "string" } + }, + "quality_tier": { + "enum": ["verified", "community", "experimental"] + } + } +} From e10d3d970a495ad786923aefbebf58a685486ac4 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:31 -0700 Subject: [PATCH 37/92] Add agentfeeds skill: catalog/schemas/event-types/mac.reading-list-item.v1.json --- .../event-types/mac.reading-list-item.v1.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/mac.reading-list-item.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/mac.reading-list-item.v1.json b/skills/agentfeeds/catalog/schemas/event-types/mac.reading-list-item.v1.json new file mode 100644 index 00000000..632ab35e --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/mac.reading-list-item.v1.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/mac.reading-list-item.v1.json", + "title": "Safari Reading List Item", + "type": "object", + "required": ["title", "url"], + "properties": { + "title": { "type": "string" }, + "url": { "type": "string" }, + "added_at": { "type": ["string", "null"] }, + "preview_text": { "type": ["string", "null"] } + } +} From aef299ed203c8998ab12efcbbfab316d654fb93b Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:32 -0700 Subject: [PATCH 38/92] Add agentfeeds skill: catalog/schemas/event-types/github.release.v1.json --- .../schemas/event-types/github.release.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/github.release.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/github.release.v1.json b/skills/agentfeeds/catalog/schemas/event-types/github.release.v1.json new file mode 100644 index 00000000..a85bd582 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/github.release.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/github.release.v1.json", + "title": "GitHub Release", + "type": "object", + "required": ["tag_name", "name", "published_at", "html_url"], + "properties": { + "tag_name": { "type": "string" }, + "name": { "type": ["string", "null"] }, + "published_at": { "type": "string" }, + "html_url": { "type": "string" }, + "body": { "type": ["string", "null"] } + } +} From 6e3d6ae7a0e3d0fa859b7e72e3cd5b0c524bfc51 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:33 -0700 Subject: [PATCH 39/92] Add agentfeeds skill: catalog/schemas/event-types/github.notification.v1.json --- .../event-types/github.notification.v1.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/github.notification.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/github.notification.v1.json b/skills/agentfeeds/catalog/schemas/event-types/github.notification.v1.json new file mode 100644 index 00000000..1ac32883 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/github.notification.v1.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/github.notification.v1.json", + "title": "GitHub Notification", + "type": "object", + "required": ["id", "reason", "unread", "updated_at", "repository", "subject_title"], + "properties": { + "id": { "type": "string" }, + "reason": { "type": "string" }, + "unread": { "type": "boolean" }, + "updated_at": { "type": "string" }, + "repository": { "type": "string" }, + "subject_title": { "type": "string" }, + "subject_type": { "type": ["string", "null"] }, + "url": { "type": ["string", "null"] } + } +} From 1232ea5bf09cdd31c66ac59c0e6ba523c9d7601b Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:34 -0700 Subject: [PATCH 40/92] Add agentfeeds skill: catalog/schemas/event-types/github.pull-request.v1.json --- .../event-types/github.pull-request.v1.json | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/github.pull-request.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/github.pull-request.v1.json b/skills/agentfeeds/catalog/schemas/event-types/github.pull-request.v1.json new file mode 100644 index 00000000..2bc38ba9 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/github.pull-request.v1.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/github.pull-request.v1.json", + "title": "GitHub Pull Request", + "type": "object", + "required": ["number", "title", "state", "html_url", "created_at", "updated_at"], + "properties": { + "number": { "type": "integer" }, + "title": { "type": "string" }, + "state": { "type": "string" }, + "html_url": { "type": "string" }, + "user": { "type": ["string", "null"] }, + "draft": { "type": "boolean" }, + "head_ref": { "type": ["string", "null"] }, + "base_ref": { "type": ["string", "null"] }, + "created_at": { "type": "string" }, + "updated_at": { "type": "string" }, + "closed_at": { "type": ["string", "null"] }, + "merged_at": { "type": ["string", "null"] }, + "body": { "type": ["string", "null"] } + } +} From bf55a42a6524de81d821bfc6e6a33e9454682320 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:35 -0700 Subject: [PATCH 41/92] Add agentfeeds skill: catalog/schemas/event-types/todoist.task.v1.json --- .../schemas/event-types/todoist.task.v1.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/todoist.task.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/todoist.task.v1.json b/skills/agentfeeds/catalog/schemas/event-types/todoist.task.v1.json new file mode 100644 index 00000000..0148195f --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/todoist.task.v1.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/todoist.task.v1.json", + "title": "Todoist Task", + "type": "object", + "required": ["id", "content", "url", "priority", "due"], + "properties": { + "id": { "type": "string" }, + "content": { "type": "string" }, + "description": { "type": ["string", "null"] }, + "url": { "type": "string" }, + "priority": { "type": "integer" }, + "project_id": { "type": ["string", "null"] }, + "due": { + "type": "object", + "additionalProperties": true + } + } +} From b494e22e77369cf2809c846ae0d7d7ac80ec9ba8 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:35 -0700 Subject: [PATCH 42/92] Add agentfeeds skill: catalog/schemas/event-types/linear.issue.v1.json --- .../schemas/event-types/linear.issue.v1.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/linear.issue.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/linear.issue.v1.json b/skills/agentfeeds/catalog/schemas/event-types/linear.issue.v1.json new file mode 100644 index 00000000..1283579d --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/linear.issue.v1.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/linear.issue.v1.json", + "title": "Linear Issue", + "type": "object", + "required": ["id", "identifier", "title", "state", "url", "updated_at"], + "properties": { + "id": { "type": "string" }, + "identifier": { "type": "string" }, + "title": { "type": "string" }, + "state": { "type": "string" }, + "priority": { "type": ["integer", "null"] }, + "team": { "type": ["string", "null"] }, + "url": { "type": "string" }, + "updated_at": { "type": "string" }, + "due_at": { "type": ["string", "null"] } + } +} From 9c7e54edba8638f968fefd2833a0a6087f5191cf Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:36 -0700 Subject: [PATCH 43/92] Add agentfeeds skill: catalog/schemas/event-types/mac.note.v1.json --- .../catalog/schemas/event-types/mac.note.v1.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/mac.note.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/mac.note.v1.json b/skills/agentfeeds/catalog/schemas/event-types/mac.note.v1.json new file mode 100644 index 00000000..65d6cdd2 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/mac.note.v1.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/mac.note.v1.json", + "title": "macOS Note", + "type": "object", + "required": ["id", "title", "snippet", "modified_at"], + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "snippet": { "type": "string" }, + "modified_at": { "type": "string" }, + "folder": { "type": ["string", "null"] }, + "account": { "type": ["string", "null"] } + } +} From e6179d35d6e5bc96e33efb1e93b776be8a7a4f31 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:37 -0700 Subject: [PATCH 44/92] Add agentfeeds skill: catalog/schemas/event-types/rss-item.v1.json --- .../catalog/schemas/event-types/rss-item.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/rss-item.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/rss-item.v1.json b/skills/agentfeeds/catalog/schemas/event-types/rss-item.v1.json new file mode 100644 index 00000000..9bd22039 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/rss-item.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/rss-item.v1.json", + "title": "RSS Item", + "type": "object", + "required": ["title"], + "properties": { + "title": { "type": "string" }, + "link": { "type": ["string", "null"] }, + "summary": { "type": ["string", "null"] }, + "published": { "type": ["string", "null"] }, + "id": { "type": ["string", "null"] } + } +} From ce096235ea33df58c188cefc311b1c5bd0328f6a Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:38 -0700 Subject: [PATCH 45/92] Add agentfeeds skill: catalog/schemas/event-types/mac.imessage-thread.v1.json --- .../event-types/mac.imessage-thread.v1.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/mac.imessage-thread.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/mac.imessage-thread.v1.json b/skills/agentfeeds/catalog/schemas/event-types/mac.imessage-thread.v1.json new file mode 100644 index 00000000..243ba76c --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/mac.imessage-thread.v1.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/mac.imessage-thread.v1.json", + "title": "iMessage Conversation", + "type": "object", + "required": ["thread_id", "display_name", "unread_count", "last_message_at", "snippet"], + "properties": { + "thread_id": { "type": "string" }, + "display_name": { "type": "string" }, + "participants": { + "type": "array", + "items": { "type": "string" } + }, + "unread_count": { "type": "integer" }, + "last_message_at": { "type": "string" }, + "snippet": { "type": "string" } + } +} From 8df2453ba110f159bd50cb1fe9d5b46500178cf6 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:39 -0700 Subject: [PATCH 46/92] Add agentfeeds skill: catalog/schemas/event-types/geo.earthquake.v1.json --- .../schemas/event-types/geo.earthquake.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json b/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json new file mode 100644 index 00000000..00561dea --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/geo.earthquake.v1.json", + "title": "Earthquake Event", + "type": "object", + "required": ["id", "time", "magnitude", "place"], + "properties": { + "id": { "type": "string" }, + "time": { "type": "string" }, + "magnitude": { "type": "number" }, + "place": { "type": "string" }, + "url": { "type": "string" } + } +} From b1a390a925d3caeae090ef322d6830993051d78e Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:39 -0700 Subject: [PATCH 47/92] Add agentfeeds skill: catalog/schemas/event-types/local.markdown-document.v1.json --- .../local.markdown-document.v1.json | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/local.markdown-document.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/local.markdown-document.v1.json b/skills/agentfeeds/catalog/schemas/event-types/local.markdown-document.v1.json new file mode 100644 index 00000000..e7e4ed80 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/local.markdown-document.v1.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/local.markdown-document.v1.json", + "title": "Local Markdown Document", + "type": "object", + "required": ["path", "title", "snippet", "modified_at", "frontmatter"], + "properties": { + "path": { "type": "string" }, + "title": { "type": "string" }, + "snippet": { "type": "string" }, + "modified_at": { "type": "string" }, + "frontmatter": { + "type": "object", + "additionalProperties": true + }, + "tags": { + "type": "array", + "items": { "type": "string" } + } + } +} From 6866c22615e7da7003bfddd7806c17a3dd3ca62a Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:40 -0700 Subject: [PATCH 48/92] Add agentfeeds skill: catalog/schemas/event-types/notion.page.v1.json --- .../schemas/event-types/notion.page.v1.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/notion.page.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/notion.page.v1.json b/skills/agentfeeds/catalog/schemas/event-types/notion.page.v1.json new file mode 100644 index 00000000..619d996b --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/notion.page.v1.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/notion.page.v1.json", + "title": "Notion Page", + "type": "object", + "required": ["id", "title", "url", "last_edited_at"], + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "url": { "type": "string" }, + "last_edited_at": { "type": "string" }, + "created_at": { "type": ["string", "null"] }, + "parent_type": { "type": ["string", "null"] } + } +} From db3f52c42a5a460cf189f6ceb78fb5cb244880cb Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:41 -0700 Subject: [PATCH 49/92] Add agentfeeds skill: catalog/schemas/event-types/space.iss-location.v1.json --- .../schemas/event-types/space.iss-location.v1.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json b/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json new file mode 100644 index 00000000..4d5344b7 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/space.iss-location.v1.json", + "title": "ISS Location", + "type": "object", + "required": ["latitude", "longitude", "timestamp"], + "properties": { + "latitude": { "type": "number" }, + "longitude": { "type": "number" }, + "timestamp": { "type": "integer" } + } +} From c6d0fb7720fca8464551b65e9912b2ebaa8469e1 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:42 -0700 Subject: [PATCH 50/92] Add agentfeeds skill: catalog/schemas/event-types/ical-event.v1.json --- .../catalog/schemas/event-types/ical-event.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/ical-event.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/ical-event.v1.json b/skills/agentfeeds/catalog/schemas/event-types/ical-event.v1.json new file mode 100644 index 00000000..acbf9c69 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/ical-event.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/ical-event.v1.json", + "title": "iCalendar Event", + "type": "object", + "required": ["uid", "summary"], + "properties": { + "uid": { "type": "string" }, + "summary": { "type": "string" }, + "starts_at": { "type": ["string", "null"] }, + "ends_at": { "type": ["string", "null"] }, + "location": { "type": ["string", "null"] } + } +} From fec853f11d21a231194b49b6aeaaaec815e28620 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:43 -0700 Subject: [PATCH 51/92] Add agentfeeds skill: catalog/schemas/event-types/local.directory-entry.v1.json --- .../event-types/local.directory-entry.v1.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/local.directory-entry.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/local.directory-entry.v1.json b/skills/agentfeeds/catalog/schemas/event-types/local.directory-entry.v1.json new file mode 100644 index 00000000..4951b74b --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/local.directory-entry.v1.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/local.directory-entry.v1.json", + "title": "Local Directory Entry", + "type": "object", + "required": ["path", "name", "kind", "modified_at"], + "properties": { + "path": { "type": "string" }, + "name": { "type": "string" }, + "kind": { "enum": ["file", "directory", "symlink", "other"] }, + "extension": { "type": ["string", "null"] }, + "size_bytes": { "type": ["integer", "null"] }, + "modified_at": { "type": "string" } + } +} From 525e81d271ff001e04863eff00a9c8cbd6dd8e8f Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:43 -0700 Subject: [PATCH 52/92] Add agentfeeds skill: catalog/schemas/event-types/hn.story.v1.json --- .../catalog/schemas/event-types/hn.story.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json b/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json new file mode 100644 index 00000000..91581ca8 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/hn.story.v1.json", + "title": "Hacker News Story", + "type": "object", + "required": ["id", "title"], + "properties": { + "id": { "type": "integer" }, + "title": { "type": "string" }, + "url": { "type": ["string", "null"] }, + "score": { "type": ["integer", "null"] }, + "by": { "type": ["string", "null"] } + } +} From 788a00eb1482027b6bce69c2cd105d814c761794 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:44 -0700 Subject: [PATCH 53/92] Add agentfeeds skill: catalog/schemas/event-types/local.command.v1.json --- .../schemas/event-types/local.command.v1.json | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/local.command.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/local.command.v1.json b/skills/agentfeeds/catalog/schemas/event-types/local.command.v1.json new file mode 100644 index 00000000..0d2e1b6f --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/local.command.v1.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/local.command.v1.json", + "title": "Local Command Snapshot", + "type": "object", + "required": ["command", "exit_code", "stdout", "ran_at"], + "properties": { + "command": { + "type": "array", + "items": { "type": "string" } + }, + "cwd": { "type": ["string", "null"] }, + "exit_code": { "type": "integer" }, + "stdout": { "type": "string" }, + "stderr": { "type": "string" }, + "stdout_truncated": { "type": "boolean" }, + "stderr_truncated": { "type": "boolean" }, + "parsed_json": {}, + "transformed": {}, + "started_at": { "type": "string" }, + "ran_at": { "type": "string" } + } +} From 6da36627a7e14e83b566d5179ec398cce26de918 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:45 -0700 Subject: [PATCH 54/92] Add agentfeeds skill: catalog/schemas/event-types/finance.exchange-rate.v1.json --- .../event-types/finance.exchange-rate.v1.json | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/finance.exchange-rate.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/finance.exchange-rate.v1.json b/skills/agentfeeds/catalog/schemas/event-types/finance.exchange-rate.v1.json new file mode 100644 index 00000000..1beca07f --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/finance.exchange-rate.v1.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/finance.exchange-rate.v1.json", + "title": "Exchange Rates", + "type": "object", + "required": ["base", "rates"], + "properties": { + "base": { "type": "string" }, + "date": { "type": "string" }, + "rates": { + "type": "object", + "additionalProperties": { "type": "number" } + } + } +} From 4bbf8715d88f741506c0c98ff4b91263ab01adf9 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:46 -0700 Subject: [PATCH 55/92] Add agentfeeds skill: catalog/schemas/event-types/local.file.v1.json --- .../schemas/event-types/local.file.v1.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/local.file.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/local.file.v1.json b/skills/agentfeeds/catalog/schemas/event-types/local.file.v1.json new file mode 100644 index 00000000..76266e42 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/local.file.v1.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/local.file.v1.json", + "title": "Local File Snapshot", + "type": "object", + "required": ["path", "name", "content", "size_bytes", "sha256", "modified_at"], + "properties": { + "path": { "type": "string" }, + "name": { "type": "string" }, + "extension": { "type": "string" }, + "content": { "type": "string" }, + "size_bytes": { "type": "integer" }, + "sha256": { "type": "string" }, + "modified_at": { "type": "string" } + } +} From e17eed548b9604bcf5b844105b7b9d8f5cb5f9fe Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:47 -0700 Subject: [PATCH 56/92] Add agentfeeds skill: catalog/schemas/event-types/local.git-status.v1.json --- .../event-types/local.git-status.v1.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/local.git-status.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/local.git-status.v1.json b/skills/agentfeeds/catalog/schemas/event-types/local.git-status.v1.json new file mode 100644 index 00000000..8c0ef190 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/local.git-status.v1.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/local.git-status.v1.json", + "title": "Local Git Status", + "type": "object", + "required": ["path", "branch", "clean", "dirty_files", "ahead", "behind"], + "properties": { + "path": { "type": "string" }, + "branch": { "type": "string" }, + "clean": { "type": "boolean" }, + "dirty_files": { + "type": "array", + "items": { "type": "string" } + }, + "ahead": { "type": "integer" }, + "behind": { "type": "integer" } + } +} From 9a1798e0a2742ab67c8f0b94736a369c48df5b66 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:48 -0700 Subject: [PATCH 57/92] Add agentfeeds skill: catalog/schemas/event-types/mac.mail-message.v1.json --- .../event-types/mac.mail-message.v1.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/mac.mail-message.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/mac.mail-message.v1.json b/skills/agentfeeds/catalog/schemas/event-types/mac.mail-message.v1.json new file mode 100644 index 00000000..bbf350e2 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/mac.mail-message.v1.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/mac.mail-message.v1.json", + "title": "macOS Mail Message", + "type": "object", + "required": ["id", "subject", "sender", "received_at", "unread"], + "properties": { + "id": { "type": "string" }, + "subject": { "type": "string" }, + "sender": { "type": "string" }, + "from_email": { "type": ["string", "null"] }, + "received_at": { "type": "string" }, + "unread": { "type": "boolean" }, + "mailbox": { "type": ["string", "null"] }, + "account": { "type": ["string", "null"] }, + "snippet": { "type": ["string", "null"] } + } +} From 3a315933f888b322e45ac0eaed0ae8c0f55a43fc Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:49 -0700 Subject: [PATCH 58/92] Add agentfeeds skill: catalog/schemas/event-types/github.issue.v1.json --- .../schemas/event-types/github.issue.v1.json | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/github.issue.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/github.issue.v1.json b/skills/agentfeeds/catalog/schemas/event-types/github.issue.v1.json new file mode 100644 index 00000000..02a2cf26 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/github.issue.v1.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/github.issue.v1.json", + "title": "GitHub Issue", + "type": "object", + "required": ["number", "title", "state", "html_url", "created_at", "updated_at"], + "properties": { + "number": { "type": "integer" }, + "title": { "type": "string" }, + "state": { "type": "string" }, + "html_url": { "type": "string" }, + "user": { "type": ["string", "null"] }, + "labels": { + "type": "array", + "items": { "type": "string" } + }, + "created_at": { "type": "string" }, + "updated_at": { "type": "string" }, + "closed_at": { "type": ["string", "null"] }, + "body": { "type": ["string", "null"] } + } +} From 8c1efbce9b50993baff640fef868cb7e7e3b4289 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:49 -0700 Subject: [PATCH 59/92] Add agentfeeds skill: catalog/schemas/event-types/weather.forecast.v1.json --- .../schemas/event-types/weather.forecast.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/weather.forecast.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/weather.forecast.v1.json b/skills/agentfeeds/catalog/schemas/event-types/weather.forecast.v1.json new file mode 100644 index 00000000..523eeec0 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/weather.forecast.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/weather.forecast.v1.json", + "title": "Weather Forecast", + "type": "object", + "required": ["generated_at", "daily"], + "properties": { + "generated_at": { "type": "string" }, + "daily": { + "type": "array", + "items": { "type": "object" } + } + } +} From 34f36c5c78f86616f74ed57d1a66e41fdd5f864f Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:50 -0700 Subject: [PATCH 60/92] Add agentfeeds skill: catalog/schemas/event-types/mac.reminder.v1.json --- .../schemas/event-types/mac.reminder.v1.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/mac.reminder.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/mac.reminder.v1.json b/skills/agentfeeds/catalog/schemas/event-types/mac.reminder.v1.json new file mode 100644 index 00000000..b257dc9c --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/mac.reminder.v1.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/mac.reminder.v1.json", + "title": "macOS Reminder", + "type": "object", + "required": ["id", "title", "completed", "list_name"], + "properties": { + "id": { "type": "string" }, + "title": { "type": "string" }, + "completed": { "type": "boolean" }, + "list_name": { "type": "string" }, + "due_at": { "type": ["string", "null"] }, + "priority": { "type": ["integer", "null"] }, + "notes_snippet": { "type": ["string", "null"] }, + "updated_at": { "type": ["string", "null"] }, + "url": { "type": ["string", "null"] } + } +} From 4b37e65cd62d50d58b6355311ea7fbd3226f9ff6 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:51 -0700 Subject: [PATCH 61/92] Add agentfeeds skill: catalog/schemas/event-types/weather.observation.v1.json --- .../event-types/weather.observation.v1.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 skills/agentfeeds/catalog/schemas/event-types/weather.observation.v1.json diff --git a/skills/agentfeeds/catalog/schemas/event-types/weather.observation.v1.json b/skills/agentfeeds/catalog/schemas/event-types/weather.observation.v1.json new file mode 100644 index 00000000..28fe3ed5 --- /dev/null +++ b/skills/agentfeeds/catalog/schemas/event-types/weather.observation.v1.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/weather.observation.v1.json", + "title": "Weather Observation", + "type": "object", + "required": ["observed_at"], + "properties": { + "temperature_c": { "type": "number" }, + "humidity_pct": { "type": "number" }, + "wind_kph": { "type": "number" }, + "conditions_code": { "type": ["integer", "string"] }, + "observed_at": { "type": "string" } + } +} From dcc297da1b04ac9674278ba3d4c8c86d39708ea2 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:52 -0700 Subject: [PATCH 62/92] Add agentfeeds skill: agents/openai.yaml --- skills/agentfeeds/agents/openai.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 skills/agentfeeds/agents/openai.yaml diff --git a/skills/agentfeeds/agents/openai.yaml b/skills/agentfeeds/agents/openai.yaml new file mode 100644 index 00000000..a7e60ef0 --- /dev/null +++ b/skills/agentfeeds/agents/openai.yaml @@ -0,0 +1,7 @@ +interface: + display_name: "Agent Feeds" + short_description: "Use warm local feeds for ambient personal context" + default_prompt: "Use $agentfeeds to check background refresh, insert my stream brief, and help subscribe local or Mac personal sources." + +policy: + allow_implicit_invocation: true From 7ead0848301a26ceba3e42d01820a479837961e3 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:52 -0700 Subject: [PATCH 63/92] Add agentfeeds skill: scripts/agentfeeds_fetch.py --- skills/agentfeeds/scripts/agentfeeds_fetch.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/scripts/agentfeeds_fetch.py diff --git a/skills/agentfeeds/scripts/agentfeeds_fetch.py b/skills/agentfeeds/scripts/agentfeeds_fetch.py new file mode 100644 index 00000000..725d897b --- /dev/null +++ b/skills/agentfeeds/scripts/agentfeeds_fetch.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Run the Agent Feeds refresh worker from the skill checkout.""" + +from __future__ import annotations + +import sys +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" +if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): + os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) +LIB = ROOT / "scripts" / "lib" +if str(LIB) not in sys.path: + sys.path.insert(0, str(LIB)) + +from agentfeeds_runtime.fetcher import main # noqa: E402 + + +if __name__ == "__main__": + raise SystemExit(main()) From f784b41b775ee3c02a5406bf5cd207f544c4f101 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:53 -0700 Subject: [PATCH 64/92] Add agentfeeds skill: scripts/agentfeeds.py --- skills/agentfeeds/scripts/agentfeeds.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/scripts/agentfeeds.py diff --git a/skills/agentfeeds/scripts/agentfeeds.py b/skills/agentfeeds/scripts/agentfeeds.py new file mode 100644 index 00000000..bcc9a000 --- /dev/null +++ b/skills/agentfeeds/scripts/agentfeeds.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Run the Agent Feeds management CLI from the skill checkout.""" + +from __future__ import annotations + +import sys +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" +if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): + os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) +LIB = ROOT / "scripts" / "lib" +if str(LIB) not in sys.path: + sys.path.insert(0, str(LIB)) + +from agentfeeds_runtime.commands import main # noqa: E402 + + +if __name__ == "__main__": + raise SystemExit(main()) From 81a2c9a3364c7833a715e8fbc4cbdbf4305e49e5 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:54 -0700 Subject: [PATCH 65/92] Add agentfeeds skill: scripts/setup.py --- skills/agentfeeds/scripts/setup.py | 78 ++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 skills/agentfeeds/scripts/setup.py diff --git a/skills/agentfeeds/scripts/setup.py b/skills/agentfeeds/scripts/setup.py new file mode 100644 index 00000000..4db118be --- /dev/null +++ b/skills/agentfeeds/scripts/setup.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Install the Agent Feeds runtime into a local virtual environment.""" + +from __future__ import annotations + +import argparse +import shutil +import subprocess +import sys +import venv +from pathlib import Path + + +DEFAULT_VENV = Path.home() / ".agentfeeds" / "runtime-venv" + + +def venv_python(path: Path) -> Path: + if sys.platform == "win32": + return path / "Scripts" / "python.exe" + return path / "bin" / "python" + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Install Agent Feeds skill runtime") + parser.add_argument("--venv", type=Path, default=DEFAULT_VENV, help="runtime virtualenv path") + return parser + + +def create_venv(path: Path) -> None: + if venv_python(path).exists(): + return + try: + venv.EnvBuilder(with_pip=True).create(path) + return + except Exception: + if path.exists(): + import shutil as _shutil + + _shutil.rmtree(path) + uv = shutil.which("uv") + if not uv: + raise + subprocess.run([uv, "venv", "--python", sys.executable, str(path)], check=True) + + +def install_runtime(python: Path, root: Path) -> None: + has_pip = subprocess.run( + [str(python), "-m", "pip", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ).returncode == 0 + if has_pip: + subprocess.run([str(python), "-m", "pip", "install", "--upgrade", "pip"], check=True) + subprocess.run([str(python), "-m", "pip", "install", "-e", str(root)], check=True) + return + uv = shutil.which("uv") + if not uv: + raise RuntimeError(f"pip is unavailable in {python}; install pip or install uv") + subprocess.run([uv, "pip", "install", "--python", str(python), "-e", str(root)], check=True) + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + root = Path(__file__).resolve().parents[1] + venv_path = args.venv.expanduser() + venv_path.parent.mkdir(parents=True, exist_ok=True) + create_venv(venv_path) + python = venv_python(venv_path) + install_runtime(python, root) + print(f"installed Agent Feeds runtime: {venv_path}") + print(f"management CLI: {python} {root / 'scripts' / 'agentfeeds.py'}") + print(f"refresh worker: {python} {root / 'scripts' / 'agentfeeds_fetch.py'}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e0d400fab4fca922c3c637152f00daf365abd646 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:55 -0700 Subject: [PATCH 66/92] Add agentfeeds skill: scripts/evals/run_evals.py --- skills/agentfeeds/scripts/evals/run_evals.py | 166 +++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 skills/agentfeeds/scripts/evals/run_evals.py diff --git a/skills/agentfeeds/scripts/evals/run_evals.py b/skills/agentfeeds/scripts/evals/run_evals.py new file mode 100644 index 00000000..f42a29a3 --- /dev/null +++ b/skills/agentfeeds/scripts/evals/run_evals.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""Run AgentSkills-style eval cases with Codex CLI.""" + +from __future__ import annotations + +import argparse +from concurrent.futures import ThreadPoolExecutor, as_completed +import json +import shutil +import subprocess +import time +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +DEFAULT_EVALS = ROOT / "evals" / "evals.json" +DEFAULT_WORKSPACE = ROOT.parent / "agentfeeds-eval-workspace" + + +def slug(value: str) -> str: + return "".join(ch if ch.isalnum() else "-" for ch in value.lower()).strip("-") + + +def load_evals(path: Path) -> dict: + data = json.loads(path.read_text(encoding="utf-8")) + if data.get("skill_name") != "agentfeeds": + raise SystemExit(f"unexpected skill_name in {path}: {data.get('skill_name')}") + for item in data.get("evals") or []: + for file in item.get("files") or []: + if not (ROOT / file).exists(): + raise SystemExit(f"missing eval input file: {file}") + return data + + +def write_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def build_prompt(case: dict, output_dir: Path, *, with_skill: bool) -> str: + files = case.get("files") or [] + skill_line = f"- Skill path: {ROOT}" if with_skill else "- Skill path: none; answer without using the Agent Feeds skill instructions." + file_lines = "\n".join(f" - {ROOT / file}" for file in files) or " - none" + assertions = "\n".join(f" - {item}" for item in case.get("assertions") or []) + return f"""Execute this eval task in a clean context. + +{skill_line} +- Task: {case["prompt"]} +- Input files: +{file_lines} +- Save any produced files to: {output_dir} + +Expected output: +{case["expected_output"]} + +Assertions to satisfy: +{assertions} + +Keep the final answer concise and include the commands you used when relevant. +Do not edit files in the skill repository. Put all artifacts, temporary Agent Feeds roots, draft templates, reports, logs, and generated files under the output directory above. +""" + + +def run_case(case: dict, target: Path, *, with_skill: bool, model: str | None) -> dict: + outputs = target / "outputs" + outputs.mkdir(parents=True, exist_ok=True) + prompt = build_prompt(case, outputs, with_skill=with_skill) + prompt_path = target / "prompt.txt" + output_path = target / "output.txt" + prompt_path.write_text(prompt, encoding="utf-8") + + command = [ + "codex", + "exec", + "--cd", + str(ROOT), + "--sandbox", + "danger-full-access", + "--dangerously-bypass-approvals-and-sandbox", + "--ephemeral", + "--output-last-message", + str(output_path), + ] + if model: + command.extend(["--model", model]) + command.append(prompt) + + started = time.monotonic() + result = subprocess.run(command, text=True, capture_output=True, check=False) + duration_ms = round((time.monotonic() - started) * 1000) + (target / "stdout.log").write_text(result.stdout, encoding="utf-8") + (target / "stderr.log").write_text(result.stderr, encoding="utf-8") + timing = {"duration_ms": duration_ms, "returncode": result.returncode} + write_json(target / "timing.json", timing) + return timing + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Run Agent Feeds eval cases") + parser.add_argument("--evals", type=Path, default=DEFAULT_EVALS) + parser.add_argument("--workspace", type=Path, default=DEFAULT_WORKSPACE) + parser.add_argument("--iteration", default="iteration-1") + parser.add_argument("--case", action="append", help="run only this eval id; repeatable") + parser.add_argument("--with-skill-only", action="store_true", help="skip baseline runs") + parser.add_argument("--model", help="optional Codex model override") + parser.add_argument("--jobs", type=int, default=6, help="parallel model runs when --execute is set") + parser.add_argument("--execute", action="store_true", help="actually run Codex model calls") + return parser + + +def main() -> int: + args = build_parser().parse_args() + data = load_evals(args.evals) + selected = set(args.case or []) + cases = [case for case in data["evals"] if not selected or case["id"] in selected] + if selected and len(cases) != len(selected): + found = {case["id"] for case in cases} + raise SystemExit(f"unknown eval case(s): {', '.join(sorted(selected - found))}") + + iteration = args.workspace / args.iteration + plan = [] + for case in cases: + case_dir = iteration / f"eval-{slug(case['id'])}" + targets = [("with_skill", True)] + if not args.with_skill_only: + targets.append(("without_skill", False)) + for name, with_skill in targets: + target = case_dir / name + plan.append((case, target, with_skill)) + + print(f"workspace: {iteration}") + print(f"planned runs: {len(plan)}") + for case, target, with_skill in plan: + print(f"- {case['id']} -> {target.relative_to(args.workspace)}") + if not args.execute: + target.mkdir(parents=True, exist_ok=True) + (target / "prompt.txt").write_text(build_prompt(case, target / "outputs", with_skill=with_skill), encoding="utf-8") + + if not args.execute: + print("dry run only; pass --execute to spend model calls") + return 0 + + if not shutil.which("codex"): + raise SystemExit("codex CLI not found on PATH") + + jobs = max(1, args.jobs) + results = [] + with ThreadPoolExecutor(max_workers=jobs) as pool: + futures = {} + for case, target, with_skill in plan: + label = "with_skill" if with_skill else "without_skill" + print(f"starting {case['id']} ({label})") + future = pool.submit(run_case, case, target, with_skill=with_skill, model=args.model) + futures[future] = (case, target, label) + for future in as_completed(futures): + case, target, label = futures[future] + timing = future.result() + print(f"finished {case['id']} ({label}) returncode={timing['returncode']} duration_ms={timing['duration_ms']}") + results.append({"id": case["id"], "variant": label, "target": str(target), **timing}) + results.sort(key=lambda item: (item["id"], item["variant"])) + write_json(iteration / "run-summary.json", {"runs": results}) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 8e027a8f35ca2ece3ab9a2ffaa6be95c15dc0e7d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:55 -0700 Subject: [PATCH 67/92] Add agentfeeds skill: scripts/macos/reminders_open.py --- .../scripts/macos/reminders_open.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 skills/agentfeeds/scripts/macos/reminders_open.py diff --git a/skills/agentfeeds/scripts/macos/reminders_open.py b/skills/agentfeeds/scripts/macos/reminders_open.py new file mode 100644 index 00000000..f91ad193 --- /dev/null +++ b/skills/agentfeeds/scripts/macos/reminders_open.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Read incomplete macOS Reminders as Agent Feeds local_command JSON.""" + +from __future__ import annotations + +import json +import platform +import subprocess +import sys + + +SCRIPT = r''' +set rows to {} +tell application "Reminders" + repeat with listRef in lists + set listName to name of listRef + set matches to every reminder of listRef whose completed is false + repeat with remRef in matches + set bodyText to "" + set dueText to "" + try + set bodyText to body of remRef + end try + try + set dueText to (due date of remRef) as string + end try + set end of rows to (id of remRef) & tab & (name of remRef) & tab & bodyText & tab & dueText & tab & listName + end repeat + end repeat +end tell +set AppleScript's text item delimiters to linefeed +return rows as text +''' + + +def main() -> int: + if platform.system() != "Darwin": + print(json.dumps({"items": []})) + return 0 + result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) + if result.returncode: + print(json.dumps({"items": []})) + print(result.stderr.strip(), file=sys.stderr) + return 0 + items = [] + for line in result.stdout.splitlines(): + parts = line.split("\t") + if len(parts) < 5: + continue + reminder_id, title, body, due_at, list_name = parts[:5] + items.append( + { + "id": reminder_id or f"{list_name}:{title}:{due_at}", + "title": title or "(untitled reminder)", + "content": body or None, + "source": list_name, + "starts_at": due_at or None, + "updated_at": due_at or None, + } + ) + print(json.dumps({"items": items}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e0cd096c23068bd8808df9d765789941a6d9041f Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:56 -0700 Subject: [PATCH 68/92] Add agentfeeds skill: scripts/macos/calendar_today.py --- .../scripts/macos/calendar_today.py | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 skills/agentfeeds/scripts/macos/calendar_today.py diff --git a/skills/agentfeeds/scripts/macos/calendar_today.py b/skills/agentfeeds/scripts/macos/calendar_today.py new file mode 100644 index 00000000..2fd147ed --- /dev/null +++ b/skills/agentfeeds/scripts/macos/calendar_today.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Read today's macOS Calendar events as Agent Feeds local_command JSON.""" + +from __future__ import annotations + +import json +import platform +import subprocess +import sys + + +SCRIPT = r''' +set startDate to current date +set hours of startDate to 0 +set minutes of startDate to 0 +set seconds of startDate to 0 +set endDate to startDate + (1 * days) +set rows to {} +tell application "Calendar" + repeat with cal in calendars + set calName to name of cal + set matches to every event of cal whose start date is greater than or equal to startDate and start date is less than endDate + repeat with ev in matches + set evLocation to "" + try + set evLocation to location of ev + end try + set end of rows to (uid of ev) & tab & (summary of ev) & tab & ((start date of ev) as string) & tab & ((end date of ev) as string) & tab & evLocation & tab & calName + end repeat + end repeat +end tell +set AppleScript's text item delimiters to linefeed +return rows as text +''' + + +def main() -> int: + if platform.system() != "Darwin": + print(json.dumps({"items": []})) + return 0 + result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) + if result.returncode: + print(json.dumps({"items": []})) + print(result.stderr.strip(), file=sys.stderr) + return 0 + items = [] + for line in result.stdout.splitlines(): + parts = line.split("\t") + if len(parts) < 6: + continue + uid, title, starts_at, ends_at, location, calendar_name = parts[:6] + items.append( + { + "id": uid or f"{calendar_name}:{starts_at}:{title}", + "title": title or "(untitled event)", + "content": location or None, + "source": calendar_name, + "starts_at": starts_at, + "ends_at": ends_at, + "updated_at": starts_at, + } + ) + print(json.dumps({"items": items}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From a60fbdd8f3a8d5e2207692646451913472d6b383 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:57 -0700 Subject: [PATCH 69/92] Add agentfeeds skill: scripts/macos/mail_inbox_recent.py --- .../scripts/macos/mail_inbox_recent.py | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 skills/agentfeeds/scripts/macos/mail_inbox_recent.py diff --git a/skills/agentfeeds/scripts/macos/mail_inbox_recent.py b/skills/agentfeeds/scripts/macos/mail_inbox_recent.py new file mode 100644 index 00000000..9ebb1bed --- /dev/null +++ b/skills/agentfeeds/scripts/macos/mail_inbox_recent.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +"""Read recent macOS Mail inbox messages as Agent Feeds local_command JSON.""" + +from __future__ import annotations + +import json +import platform +import subprocess +import sys + + +SCRIPT = r''' +set rows to {} +tell application "Mail" + set messageCount to count of messages of inbox + set maxItems to 20 + if messageCount < maxItems then set maxItems to messageCount + repeat with i from 1 to maxItems + set msg to message i of inbox + set senderText to "" + set subjectText to "" + set dateText to "" + set previewText to "" + try + set senderText to sender of msg + end try + try + set subjectText to subject of msg + end try + try + set dateText to (date received of msg) as string + end try + try + set previewText to content of msg + if length of previewText > 500 then set previewText to text 1 thru 500 of previewText + end try + set end of rows to (message id of msg) & tab & subjectText & tab & senderText & tab & dateText & tab & previewText + end repeat +end tell +set AppleScript's text item delimiters to linefeed +return rows as text +''' + + +def main() -> int: + if platform.system() != "Darwin": + print(json.dumps({"items": []})) + return 0 + result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) + if result.returncode: + print(json.dumps({"items": []})) + print(result.stderr.strip(), file=sys.stderr) + return 0 + items = [] + for line in result.stdout.splitlines(): + parts = line.split("\t") + if len(parts) < 5: + continue + message_id, subject, sender, received_at, preview = parts[:5] + items.append( + { + "id": message_id or f"{sender}:{received_at}:{subject}", + "title": subject or "(no subject)", + "content": preview or None, + "source": "Mail Inbox", + "sender": sender or None, + "updated_at": received_at or None, + } + ) + print(json.dumps({"items": items}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e4e551a7704488e0231839f688ab5d867a31d6df Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:58 -0700 Subject: [PATCH 70/92] Add agentfeeds skill: scripts/polling/uninstall.py --- .../agentfeeds/scripts/polling/uninstall.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/scripts/polling/uninstall.py diff --git a/skills/agentfeeds/scripts/polling/uninstall.py b/skills/agentfeeds/scripts/polling/uninstall.py new file mode 100644 index 00000000..40e7fab7 --- /dev/null +++ b/skills/agentfeeds/scripts/polling/uninstall.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Uninstall background polling from the skill checkout.""" + +from __future__ import annotations + +import sys +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" +if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): + os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) +LIB = ROOT / "scripts" / "lib" +if str(LIB) not in sys.path: + sys.path.insert(0, str(LIB)) + +from agentfeeds_runtime.polling.uninstall import main # noqa: E402 + + +if __name__ == "__main__": + raise SystemExit(main()) From 8c4eb1d0960be01bd8d8a3f13c12beba23dc8f23 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:59 -0700 Subject: [PATCH 71/92] Add agentfeeds skill: scripts/polling/install.py --- skills/agentfeeds/scripts/polling/install.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 skills/agentfeeds/scripts/polling/install.py diff --git a/skills/agentfeeds/scripts/polling/install.py b/skills/agentfeeds/scripts/polling/install.py new file mode 100644 index 00000000..7578e74b --- /dev/null +++ b/skills/agentfeeds/scripts/polling/install.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Install background polling from the skill checkout.""" + +from __future__ import annotations + +import sys +import os +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" +if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): + os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) +LIB = ROOT / "scripts" / "lib" +if str(LIB) not in sys.path: + sys.path.insert(0, str(LIB)) + +from agentfeeds_runtime.polling.install import main # noqa: E402 + + +if __name__ == "__main__": + raise SystemExit(main()) From 6adf58828321221edde331e22d6fcb813bbf5bc6 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:42:59 -0700 Subject: [PATCH 72/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/constants.py --- .../agentfeeds/scripts/lib/agentfeeds_runtime/constants.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/constants.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/constants.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/constants.py new file mode 100644 index 00000000..23f433ef --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/constants.py @@ -0,0 +1,6 @@ +"""Shared runtime constants for Agent Feeds.""" + +AGENTFEEDS_VERSION = "agentfeeds/0.3" +REQUEST_TIMEOUT_SECONDS = 20 +COMMAND_TIMEOUT_SECONDS = 20 +COMMAND_MAX_OUTPUT_BYTES = 1024 * 1024 From a3ff6f47676c56d3dd0d371e5dfcef085fefab7a Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:00 -0700 Subject: [PATCH 73/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/__init__.py --- skills/agentfeeds/scripts/lib/agentfeeds_runtime/__init__.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/__init__.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__init__.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__init__.py new file mode 100644 index 00000000..33e82b34 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__init__.py @@ -0,0 +1,3 @@ +"""Agent Feeds reference implementation.""" + +__version__ = "0.0.0" From d8f54d02f1b2df5dea1f2cd3629df518893f066e Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:01 -0700 Subject: [PATCH 74/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/commands.py --- .../lib/agentfeeds_runtime/commands.py | 1768 +++++++++++++++++ 1 file changed, 1768 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py new file mode 100644 index 00000000..cccd0fd2 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py @@ -0,0 +1,1768 @@ +"""User-facing Agent Feeds management CLI.""" + +from __future__ import annotations + +import argparse +import getpass +import hashlib +import json +import os +import plistlib +import platform +import re +import subprocess +import sys +from collections import Counter +from datetime import UTC, datetime, timedelta +from pathlib import Path +from typing import Any +from urllib.parse import urlparse + +import yaml + +from agentfeeds_runtime import fetcher as fetch +from agentfeeds_runtime.polling import install as polling_install +from agentfeeds_runtime.polling import uninstall as polling_uninstall + + +INSTANCE_ID_PATTERN = re.compile(r"^[a-z0-9-]+/[a-z0-9][a-z0-9-]*$") +ADAPTER_KINDS = { + "local_file": "Read one local text, Markdown, or JSON file as a snapshot.", + "filesystem_scan": "Scan a local directory and emit recent file entries.", + "markdown_scan": "Scan a local Markdown directory and emit recent documents.", + "git_status": "Read local Git branch, dirty files, and ahead/behind status.", + "local_command": "Run an argv-only local command for a snapshot or JSON-derived events.", + "json_http": "Fetch one HTTP JSON document and transform it into a snapshot.", + "paginated_json_http": "Fetch an HTTP JSON array and transform it into event items.", + "rss": "Fetch an RSS or Atom feed as event items.", + "ical": "Fetch an iCalendar URL as event items.", + "apple_automation": "Run read-only AppleScript automation and map tab-delimited rows to events.", + "sqlite_query": "Run a read-only SQLite query and map rows to events.", + "plist_reading_list": "Read Safari-style Reading List entries from a property-list file.", +} +POLLING_LABEL = "dev.agentfeeds.fetch" +POLLING_BEGIN_MARKER = "# BEGIN Agent Feeds polling" +POLLING_END_MARKER = "# END Agent Feeds polling" +SECRET_REF_PATTERN = re.compile(r"\{\{secret:([A-Za-z_][A-Za-z0-9_.-]*)\}\}") +QUERY_TOKEN_PATTERN = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*") +QUERY_STOPWORDS = { + "about", + "after", + "also", + "and", + "are", + "can", + "could", + "did", + "does", + "for", + "from", + "has", + "have", + "how", + "into", + "latest", + "me", + "my", + "now", + "of", + "on", + "please", + "say", + "show", + "tell", + "that", + "the", + "this", + "to", + "was", + "what", + "when", + "where", + "who", + "why", + "with", +} + + +def parse_value(value: str) -> Any: + lowered = value.lower() + if lowered == "true": + return True + if lowered == "false": + return False + try: + if "." in value: + return float(value) + return int(value) + except ValueError: + return value + + +def parse_params(items: list[str]) -> dict[str, Any]: + params = {} + for item in items: + if "=" not in item: + raise SystemExit(f"parameters must be key=value, got: {item}") + key, value = item.split("=", 1) + if not key: + raise SystemExit(f"parameter key cannot be empty: {item}") + params[key] = parse_value(value) + return params + + +def save_subscriptions(root: Path, config: dict) -> None: + root.mkdir(parents=True, exist_ok=True) + path = root / "subscriptions.yaml" + tmp_path = path.with_suffix(".yaml.tmp") + tmp_path.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8") + tmp_path.replace(path) + + +def secret_refs_in_template(value: object) -> set[str]: + refs: set[str] = set() + if isinstance(value, str): + refs.update(SECRET_REF_PATTERN.findall(value)) + elif isinstance(value, list): + for item in value: + refs.update(secret_refs_in_template(item)) + elif isinstance(value, dict): + for item in value.values(): + refs.update(secret_refs_in_template(item)) + return refs + + +def active_subscriptions(root: Path) -> dict: + fetch.ensure_root(root) + return fetch.load_subscriptions(root) + + +def stream_summary(root: Path, stream_id: str) -> dict: + return fetch.load_stream_definition(root, stream_id) + + +def template_id_for(subscription: dict) -> str: + return str(subscription["template"]) + + +def state_path_for_subscription(root: Path, subscription: dict) -> Path | None: + try: + stream = fetch.load_stream_definition(root, template_id_for(subscription)) + stream_uri = fetch.source_uri_for(stream, subscription.get("parameters") or {}) + return fetch.state_path_for_stream(stream_uri, root) + except Exception: + return None + + +def _slugify(value: str) -> str: + slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") + return slug or "feed" + + +def _hash_suffix(value: object) -> str: + encoded = json.dumps(value, sort_keys=True, default=str).encode("utf-8") + return hashlib.sha256(encoded).hexdigest()[:6] + + +def _template_id_parts(template_id: str) -> tuple[str, str]: + if not INSTANCE_ID_PATTERN.match(template_id): + raise ValueError("template id must look like category/name using lowercase letters, numbers, and hyphens") + return tuple(template_id.split("/", 1)) # type: ignore[return-value] + + +def _title_from_slug(slug: str) -> str: + return slug.replace("-", " ").title() + + +def _schema_payload(template_id: str, mode: str) -> dict: + category, name = _template_id_parts(template_id) + type_name = f"{category}.{name.replace('-', '.')}" + data = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": f"https://agentfeeds.dev/schemas/{type_name}.v1.json", + "title": _title_from_slug(name), + "type": "object", + "required": ["title"], + "properties": { + "title": {"type": "string"}, + "url": {"type": ["string", "null"]}, + "content": {"type": ["string", "null"]}, + "updated_at": {"type": ["string", "null"]}, + "stdout": {"type": ["string", "null"]}, + "transformed": {}, + }, + } + if mode == "event": + data["required"] = ["title"] + return data + + +def scaffold_stream(template_id: str, adapter_kind: str) -> tuple[dict, dict, Path, Path]: + if adapter_kind not in ADAPTER_KINDS: + raise ValueError(f"unsupported adapter kind: {adapter_kind}") + category, name = _template_id_parts(template_id) + title = _title_from_slug(name) + type_name = f"{category}.{name.replace('-', '.')}" + mode = ( + "event" + if adapter_kind in { + "paginated_json_http", + "rss", + "ical", + "filesystem_scan", + "markdown_scan", + "apple_automation", + "sqlite_query", + "plist_reading_list", + } + else "snapshot" + ) + schema_name = f"{type_name}.v1.json" + stream_path = Path(category) / f"{name}.yaml" + schema_path = Path(schema_name) + + parameters = [] + source_uri_template = f"feed://{type_name}/source" + adapter: dict[str, object] = {"kind": adapter_kind} + transform = { + "language": "jmespath", + "expression": "{title: title, url: url, content: body, updated_at: updated_at}", + } + + if adapter_kind == "local_file": + parameters = [{"name": "path", "type": "string", "description": "Local file path", "required": True}] + source_uri_template = f"feed://{type_name}/file?path={{path}}" + adapter = {"kind": "local_file", "path": "{path}"} + elif adapter_kind == "filesystem_scan": + parameters = [{"name": "path", "type": "string", "description": "Local directory path", "required": True}] + source_uri_template = f"feed://{type_name}/directory?path={{path}}" + adapter = {"kind": "filesystem_scan", "path": "{path}", "order_by": "modified_at", "limit": 25} + elif adapter_kind == "markdown_scan": + parameters = [{"name": "path", "type": "string", "description": "Markdown directory path", "required": True}] + source_uri_template = f"feed://{type_name}/markdown?path={{path}}" + adapter = {"kind": "markdown_scan", "path": "{path}", "parse_frontmatter": True, "order_by": "modified_at", "limit": 25} + elif adapter_kind == "git_status": + parameters = [{"name": "path", "type": "string", "description": "Git repository path", "required": True}] + source_uri_template = f"feed://{type_name}/git?path={{path}}" + adapter = {"kind": "git_status", "path": "{path}"} + elif adapter_kind == "local_command": + parameters = [] + source_uri_template = f"feed://{type_name}/command" + adapter = { + "kind": "local_command", + "command": ["echo", "{\"title\":\"example\",\"status\":\"ok\"}"], + "timeout_seconds": 20, + "max_output_bytes": 1048576, + "parse": "json", + "transform": { + "language": "jmespath", + "expression": "{title: title, content: status}", + }, + } + type_name = "local.command" + schema_name = "local.command.v1.json" + schema_path = Path(schema_name) + elif adapter_kind == "json_http": + parameters = [{"name": "url", "type": "string", "description": "JSON API URL", "required": True}] + source_uri_template = f"feed://{type_name}/json?url={{url}}" + adapter = {"kind": "json_http", "url": "{url}", "method": "GET", "headers": {}, "transform": transform} + elif adapter_kind == "paginated_json_http": + parameters = [{"name": "url", "type": "string", "description": "JSON API URL", "required": True}] + source_uri_template = f"feed://{type_name}/items?url={{url}}" + adapter = {"kind": "paginated_json_http", "url": "{url}", "method": "GET", "headers": {}, "transform": transform, "id_from": "url"} + elif adapter_kind == "rss": + parameters = [{"name": "url", "type": "string", "description": "RSS or Atom URL", "required": True}] + source_uri_template = f"feed://{type_name}/rss?url={{url}}" + adapter = {"kind": "rss", "url": "{url}"} + type_name = "rss.item" + schema_name = "rss-item.v1.json" + schema_path = Path(schema_name) + elif adapter_kind == "ical": + parameters = [{"name": "url", "type": "string", "description": "iCalendar URL", "required": True}] + source_uri_template = f"feed://{type_name}/ics?url={{url}}" + adapter = {"kind": "ical", "url": "{url}"} + type_name = "ical.event" + schema_name = "ical-event.v1.json" + schema_path = Path(schema_name) + elif adapter_kind == "apple_automation": + source_uri_template = f"feed://{type_name}/apple-automation" + adapter = { + "kind": "apple_automation", + "tcc_permission": "Automation", + "script": "set rows to {\"example-id\" & tab & \"Example title\"}\nset AppleScript's text item delimiters to linefeed\nreturn rows as text", + "columns": ["id", "title"], + "id_column": "id", + } + elif adapter_kind == "sqlite_query": + parameters = [{"name": "database", "type": "string", "description": "SQLite database path", "required": True}] + source_uri_template = f"feed://{type_name}/sqlite?database={{database}}" + adapter = { + "kind": "sqlite_query", + "database": "{database}", + "tcc_permission": "Full Disk Access", + "query": "SELECT 1 AS id, 'Example title' AS title", + "columns": ["id", "title"], + "id_column": "id", + } + elif adapter_kind == "plist_reading_list": + parameters = [{"name": "path", "type": "string", "description": "Property-list file path", "required": True}] + source_uri_template = f"feed://{type_name}/plist-reading-list?path={{path}}" + adapter = {"kind": "plist_reading_list", "path": "{path}", "limit": 50} + + stream = { + "id": template_id, + "title": title, + "description": f"Draft template for {title}.", + "type": type_name, + "mode": mode, + "schema_url": f"https://agentfeeds.dev/schemas/{schema_name}", + "schema_version": "1.0.0", + "parameters": parameters, + "source_uri_template": source_uri_template, + "adapter": adapter, + "recommended_poll_interval_seconds": 300, + "auth": "none", + "tags": [category, adapter_kind.replace("_", "-")], + "quality_tier": "experimental", + "contributed_by": "local", + } + if adapter_kind == "local_command": + stream["pending"] = True + return stream, _schema_payload(template_id, mode), stream_path, schema_path + + +def _domain_slug(domain: str) -> str: + return _slugify(domain.removeprefix("www.").rstrip(".").replace(".", "-")) + + +def _domain_title(domain: str, suffix: str = "RSS feed") -> str: + base = domain.removeprefix("www.").split(".")[0] + return f"{base.replace('-', ' ').title()} {suffix}".strip() + + +def _default_identity(template: dict, params: dict[str, Any]) -> tuple[str, str]: + template_id = template["id"] + parameters = template.get("parameters") or [] + if not parameters: + return template_id, template["title"] + + category = template_id.split("/", 1)[0] + title = template["title"] + + if template_id == "local/file" and params.get("path"): + path = Path(str(params["path"])).expanduser() + name = path.name or "file" + return f"{category}/{_slugify(name)}", name + + if params.get("owner") and params.get("repo"): + owner = _slugify(str(params["owner"])) + repo = _slugify(str(params["repo"])) + suffixes = { + "dev/github-issues": "issues", + "dev/github-prs": "prs", + "dev/github-releases": "releases", + } + suffix = suffixes.get(template_id, template_id.split("/", 1)[1]) + return f"{category}/{owner}-{repo}-{suffix}", f"{params['owner']}/{params['repo']} {suffix}" + + if template_id == "calendar/ics" and params.get("url"): + parsed = urlparse(str(params["url"])) + if parsed.netloc: + return f"{category}/{_domain_slug(parsed.netloc)}", f"{_domain_title(parsed.netloc, 'calendar')}" + + if template_id == "news/rss-generic" and params.get("url"): + domain = urlparse(str(params["url"])).netloc.lower() or None + if domain: + return f"{category}/{_domain_slug(domain)}", _domain_title(domain) + + if params.get("url"): + parsed = urlparse(str(params["url"])) + if parsed.netloc: + return f"{category}/{_domain_slug(parsed.netloc)}", _domain_title(parsed.netloc) + + if params.get("base"): + base = str(params["base"]).upper() + return f"{category}/{_slugify(base)}-exchange-rates", f"{base} exchange rates" + + if params.get("lat") is not None and params.get("lon") is not None: + lat = _slugify(str(params["lat"])) + lon = _slugify(str(params["lon"])) + tail = _slugify(template_id.split("/", 1)[1]) + return f"{category}/{tail}-{lat}-{lon}", title + + return f"{category}/{_slugify(template_id)}-{_hash_suffix(params)}", title + + +def _append_collision_suffix(instance_id: str, template: dict, params: dict[str, Any], existing_ids: set[str]) -> str: + if instance_id not in existing_ids: + return instance_id + + path = "" + if params.get("url"): + parsed_path = urlparse(str(params["url"])).path.strip("/") + if parsed_path: + path = _slugify(parsed_path.split("/")[-2] if parsed_path.endswith("/") else parsed_path.rsplit("/", 1)[-1]) + if path: + candidate = f"{instance_id}-{path}" + if candidate not in existing_ids: + return candidate + + return f"{instance_id}-{_hash_suffix({'template': template['id'], 'parameters': params})}" + + +def materialize_subscription( + template: dict, + params: dict[str, Any], + existing_ids: set[str], + instance_id: str | None = None, + title: str | None = None, +) -> dict: + default_id, default_title = _default_identity(template, params) + if instance_id: + resolved_id = instance_id + elif not template.get("parameters"): + resolved_id = default_id + else: + resolved_id = _append_collision_suffix(default_id, template, params, existing_ids) + resolved_title = title or default_title + if not INSTANCE_ID_PATTERN.match(resolved_id): + raise ValueError( + "subscription id must look like category/name using lowercase letters, numbers, and hyphens" + ) + if resolved_id in existing_ids: + raise ValueError(f"subscription id already exists: {resolved_id}") + + subscription = { + "id": resolved_id, + "title": resolved_title, + "template": template["id"], + } + if params: + subscription["parameters"] = params + return subscription + + +def subscription_preview(root: Path, stream: dict, subscription: dict) -> dict: + parameters = subscription.get("parameters") or {} + stream_uri = fetch.source_uri_for(stream, parameters) + state_path = fetch.state_path_for_stream(stream_uri, root) + preview = { + "subscription": subscription, + "stream": stream_uri, + "state_path": str(state_path.relative_to(root)), + "requires_secrets": sorted(secret_refs_in_template(stream)), + "next_actions": [], + } + if stream.get("pending") and stream.get("adapter", {}).get("kind") == "local_command": + preview["next_actions"].append( + { + "action": "approve_local_command", + "template_id": stream["id"], + "command": f"python3 scripts/agentfeeds.py admin templates approve-command {stream['id']}", + "reason": "local_command template is pending operator approval", + } + ) + return preview + + +def local_template_file(root: Path, template_id: str) -> Path | None: + for path in sorted(fetch.template_streams_root(root).glob("**/*.yaml")): + try: + payload = yaml.safe_load(path.read_text(encoding="utf-8")) or {} + except yaml.YAMLError: + continue + if payload.get("id") == template_id: + return path + return None + + +def cmd_templates_search(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + index = fetch.load_catalog_index(args.root) + query = " ".join(args.query).lower().strip() + streams = index.get("streams") or [] + if query: + streams = [ + stream + for stream in streams + if query + in " ".join( + [ + stream.get("id", ""), + stream.get("title", ""), + stream.get("description", ""), + " ".join(stream.get("tags") or []), + stream.get("type", ""), + ] + ).lower() + ] + for stream in streams: + params = ", ".join(stream.get("parameters") or []) or "none" + source = f", source: {stream.get('source')}" if args.verbose and stream.get("source") else "" + print(f"{stream['id']}: {stream['title']} [params: {params}, mode: {stream['mode']}{source}]") + if args.verbose: + print(f" {stream.get('description', '')}") + return 0 + + +def cmd_templates_show(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + try: + stream = fetch.load_stream_definition(args.root, args.template_id) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + + result = { + "id": stream["id"], + "title": stream["title"], + "description": stream["description"], + "type": stream["type"], + "mode": stream["mode"], + "parameters": stream.get("parameters") or [], + "auth": stream.get("auth"), + "quality_tier": stream.get("quality_tier"), + "tags": stream.get("tags") or [], + "recommended_poll_interval_seconds": stream.get("recommended_poll_interval_seconds"), + } + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + required = [ + parameter["name"] + for parameter in result["parameters"] + if parameter.get("required") + ] + optional = [ + parameter["name"] + for parameter in result["parameters"] + if not parameter.get("required") + ] + print(f"{result['id']}: {result['title']}") + print(result["description"]) + print(f"Type: {result['type']}") + print(f"Mode: {result['mode']}") + print(f"Required parameters: {', '.join(required) or 'none'}") + print(f"Optional parameters: {', '.join(optional) or 'none'}") + print(f"Auth: {result['auth']}") + print(f"Quality: {result['quality_tier']}") + return 0 + + +def cmd_subscribe(args: argparse.Namespace) -> int: + config = active_subscriptions(args.root) + config.setdefault("version", fetch.SPEC_VERSION) + config.setdefault("defaults", {"poll_interval_seconds": 600, "history_limit": 50}) + subscriptions = config.setdefault("subscriptions", []) + + try: + stream = fetch.load_stream_definition(args.root, args.template_id) + params = parse_params(args.parameters) + fetch.validate_parameters(stream, params) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 2 + + existing_ids = {str(item.get("id")) for item in subscriptions} + try: + if args.update: + match = next((item for item in subscriptions if item.get("id") == args.update), None) + if not match: + raise ValueError(f"subscription id not found for update: {args.update}") + subscription = dict(match) + subscription["template"] = stream["id"] + if args.title: + subscription["title"] = args.title + elif "title" not in subscription: + subscription["title"] = stream.get("title") or args.update + if params: + subscription["parameters"] = params + elif "parameters" in subscription: + subscription.pop("parameters") + else: + subscription = materialize_subscription(stream, params, existing_ids, args.instance_id, args.title) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + if args.poll_interval_seconds: + subscription["poll_interval_seconds"] = args.poll_interval_seconds + if args.history_limit: + subscription["history_limit"] = args.history_limit + + preview = subscription_preview(args.root, stream, subscription) + if preview["requires_secrets"]: + preview["next_actions"].append( + { + "action": "set_secret", + "names": preview["requires_secrets"], + "command": "python3 scripts/agentfeeds.py admin secrets set ", + } + ) + if args.dry_run: + if args.json: + print(json.dumps(preview, indent=2, sort_keys=True)) + else: + print(f"Subscription: {subscription['id']} ({subscription['title']})") + print(f"Template: {subscription['template']}") + print(f"State path: {preview['state_path']}") + if preview["requires_secrets"]: + print(f"Secrets: {', '.join(preview['requires_secrets'])}") + return 0 + + pending_approval = next((action for action in preview["next_actions"] if action["action"] == "approve_local_command"), None) + if pending_approval: + if args.json: + print(json.dumps({**preview, "error": "local_command template is pending operator approval"}, indent=2, sort_keys=True)) + else: + print(f"{stream['id']}: local_command template is pending operator approval", file=sys.stderr) + print(f"Next: {pending_approval['command']}", file=sys.stderr) + return 2 + + if args.update: + for index, item in enumerate(subscriptions): + if item.get("id") == args.update: + subscriptions[index] = subscription + break + else: + subscriptions.append(subscription) + save_subscriptions(args.root, config) + if args.json: + result = {**preview, "created": not bool(args.update), "updated": bool(args.update)} + print(json.dumps(result, indent=2, sort_keys=True)) + else: + print(f"{'Updated' if args.update else 'Subscribed'}: {subscription['id']} ({subscription['title']})") + + if args.no_fetch: + fetch.regenerate_catalog(args.root) + return 0 + return fetch.main(["--root", str(args.root), "--once", subscription["id"]]) + + +def cmd_unsubscribe(args: argparse.Namespace) -> int: + config = active_subscriptions(args.root) + subscriptions = config.get("subscriptions") or [] + matches = [ + subscription + for subscription in subscriptions + if subscription.get("id") == args.subscription_id + ] + if not matches: + print(f"No matching subscription: {args.subscription_id}", file=sys.stderr) + return 1 + + remove_keys = {id(match) for match in matches} + state_paths = [state_path_for_subscription(args.root, match) for match in matches] + config["subscriptions"] = [ + subscription for subscription in subscriptions if id(subscription) not in remove_keys + ] + save_subscriptions(args.root, config) + + if not args.keep_state: + for path in state_paths: + if path and path.exists() and args.root in path.parents: + path.unlink() + + fetch.regenerate_catalog(args.root) + print(f"Unsubscribed: {args.subscription_id}") + return 0 + + +def state_status(root: Path, subscription: dict, defaults: dict) -> dict: + stream = stream_summary(root, template_id_for(subscription)) + path = state_path_for_subscription(root, subscription) + interval = fetch.poll_interval(subscription, stream, defaults) + payload = fetch.load_existing_state(path) if path else None + meta = (payload or {}).get("_meta", {}) + updated = fetch.parse_utc(meta.get("last_updated")) + stale = True + due = True + if updated: + age = datetime.now(UTC) - updated + due = age >= timedelta(seconds=interval) + stale = age > timedelta(seconds=interval * 2) + return { + "id": subscription["id"], + "title": subscription.get("title") or stream.get("title"), + "template": subscription.get("template"), + "parameters": subscription.get("parameters") or {}, + "path": str(path.relative_to(root)) if path else "", + "exists": bool(path and path.exists()), + "last_updated": meta.get("last_updated"), + "next_poll_due": meta.get("next_poll_due"), + "due": due, + "stale": stale, + "mode": stream.get("mode"), + } + + +def _stream_rows(root: Path) -> list[dict]: + config = active_subscriptions(root) + defaults = config.get("defaults") or {} + return [state_status(root, subscription, defaults) for subscription in config.get("subscriptions") or []] + + +def _stream_matches(row: dict, query: str) -> bool: + haystack = " ".join( + [ + row.get("id", ""), + row.get("title", ""), + row.get("template", ""), + " ".join(f"{key}={value}" for key, value in sorted((row.get("parameters") or {}).items())), + row.get("mode", ""), + ] + ).lower() + return query.lower() in haystack + + +def _print_stream_rows(rows: list[dict]) -> None: + if not rows: + print("No active streams.") + return + for row in rows: + freshness = "stale" if row["stale"] else "due" if row["due"] else "fresh" + exists = "ok" if row["exists"] else "missing" + print(f"{row['id']}: {row['title']} [{freshness}, {exists}, updated={row['last_updated'] or 'never'}]") + + +def _active_fetch_error(status: dict) -> bool: + if int(status.get("consecutive_failures") or 0) <= 0: + return False + error_at = fetch.parse_utc(status.get("last_error_at")) + success_at = fetch.parse_utc(status.get("last_success_at")) + return bool(error_at and (not success_at or error_at >= success_at)) + + +def _health_state(row: dict, status: dict) -> str: + if _active_fetch_error(status): + return "error" + if not row["exists"]: + return "missing" + if row["stale"]: + return "stale" + if row["due"]: + return "due" + return "ok" + + +def _stream_health(root: Path) -> dict: + rows = _stream_rows(root) + streams = [] + counts = Counter() + for row in rows: + status = fetch.load_fetch_status(root, row["id"]) + state = _health_state(row, status) + counts[state] += 1 + streams.append( + { + **row, + "health": state, + "last_attempt_at": status.get("last_attempt_at"), + "last_success_at": status.get("last_success_at"), + "last_error_at": status.get("last_error_at"), + "last_error": status.get("last_error"), + "consecutive_failures": int(status.get("consecutive_failures") or 0), + } + ) + summary = { + "total": len(streams), + "ok": counts.get("ok", 0), + "due": counts.get("due", 0), + "stale": counts.get("stale", 0), + "missing": counts.get("missing", 0), + "error": counts.get("error", 0), + } + summary["healthy"] = summary["total"] > 0 and summary["missing"] == 0 and summary["error"] == 0 and summary["stale"] == 0 + next_actions = [] + if summary["error"]: + next_actions.append( + { + "action": "inspect_errors", + "command": "python3 scripts/agentfeeds.py streams health --json", + "reason": "one or more streams have active fetch errors", + } + ) + stale_stream = next((row for row in streams if row["health"] == "stale"), None) + if stale_stream: + next_actions.append( + { + "action": "refresh_stream", + "subscription_id": stale_stream["id"], + "command": f"python3 scripts/agentfeeds.py refresh --stream {stale_stream['id']}", + "reason": "stream state is stale", + } + ) + missing_stream = next((row for row in streams if row["health"] == "missing"), None) + if missing_stream: + next_actions.append( + { + "action": "refresh_stream", + "subscription_id": missing_stream["id"], + "command": f"python3 scripts/agentfeeds.py refresh --stream {missing_stream['id']}", + "reason": "stream has no local state file yet", + } + ) + return {"summary": summary, "streams": streams, "next_actions": next_actions} + + +def _print_health(result: dict) -> None: + summary = result["summary"] + print( + "Streams: " + f"{summary['total']} total, {summary['ok']} ok, {summary['due']} due, " + f"{summary['stale']} stale, {summary['missing']} missing, {summary['error']} error" + ) + for row in result["streams"]: + print(f"{row['id']}: {row['health']} [updated={row['last_updated'] or 'never'}]") + if row.get("last_error"): + print(f" error: {row['last_error']}") + + +def cmd_streams_health(args: argparse.Namespace) -> int: + result = _stream_health(args.root) + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + _print_health(result) + return 0 + + +def _brief_entries(root: Path, max_streams: int, include_freshness: bool) -> tuple[list[dict], bool]: + rows = _stream_rows(root) + entries = [] + for row in rows[:max_streams]: + entry = {"id": row["id"], "title": row["title"]} + if include_freshness: + entry.update( + { + "freshness": "stale" if row["stale"] else "due" if row["due"] else "fresh", + "exists": row["exists"], + "last_updated": row["last_updated"], + } + ) + entries.append(entry) + return entries, len(rows) > max_streams + + +def _brief_health_summary(health: dict) -> str | None: + summary = health["summary"] + degraded = summary["stale"] or summary["missing"] or summary["error"] + if not degraded: + return None + parts = [] + for key in ("stale", "missing", "error"): + if summary[key]: + parts.append(f"{summary[key]} {key}") + return "Ambient health: degraded (" + ", ".join(parts) + ")" + + +def render_brief(entries: list[dict], truncated: bool, include_freshness: bool, health: dict | None = None) -> str: + lines = [""] + if health: + health_line = _brief_health_summary(health) + if health_line: + lines.append(health_line) + if entries: + lines.append("Available local streams:") + for entry in entries: + line = f"- {entry['id']}: {entry['title']}" + if include_freshness: + line += f" [{entry['freshness']}, updated={entry['last_updated'] or 'never'}]" + lines.append(line) + if truncated: + lines.append("- ...") + else: + lines.append("No active local streams.") + lines.append("") + return "\n".join(lines) + + +def cmd_brief(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + entries, truncated = _brief_entries(args.root, args.max_streams, args.include_freshness) + health = _stream_health(args.root) + brief = render_brief(entries, truncated, args.include_freshness, health) + if args.json: + print( + json.dumps( + { + "brief": brief, + "health": health["summary"], + "streams": entries, + "truncated": truncated, + "stable": not args.include_freshness, + "recommended_prompt_slot": "system", + }, + indent=2, + sort_keys=True, + ) + ) + return 0 + print(brief) + return 0 + + +def _query_terms(query: str) -> list[str]: + terms = [] + seen = set() + for raw in QUERY_TOKEN_PATTERN.findall(query.lower()): + term = raw.strip("._-") + if len(term) < 2 or term in QUERY_STOPWORDS or term in seen: + continue + seen.add(term) + terms.append(term) + return terms + + +def _iter_text_fields(value: object, path: str = "data", depth: int = 0, max_depth: int = 20): + if depth > max_depth: + return + if value is None: + return + if isinstance(value, dict): + for key, item in value.items(): + yield from _iter_text_fields(item, f"{path}.{key}", depth + 1, max_depth) + return + if isinstance(value, list): + for index, item in enumerate(value): + yield from _iter_text_fields(item, f"{path}[{index}]", depth + 1, max_depth) + return + if isinstance(value, (str, int, float, bool)): + text = str(value) + if text: + yield path, text + + +def _best_field_match(value: object, terms: list[str], match_mode: str, max_field_chars: int) -> dict | None: + best = None + fields = [(path, text) for path, text in _iter_text_fields(value)] + for path, text in fields: + searchable = text[:max_field_chars] + lowered = searchable.lower() + matched_terms = [term for term in terms if term in lowered] + if match_mode == "all" and len(matched_terms) != len(terms): + continue + if match_mode == "any" and not matched_terms: + continue + occurrences = sum(lowered.count(term) for term in matched_terms) + score = len(matched_terms) * 100 + occurrences + candidate = { + "path": path, + "text": searchable, + "score": score, + "matched_terms": matched_terms, + } + if best is None or candidate["score"] > best["score"]: + best = candidate + if best is None and fields: + combined = " ".join(text for _path, text in fields)[:max_field_chars] + lowered = combined.lower() + matched_terms = [term for term in terms if term in lowered] + if (match_mode == "all" and len(matched_terms) == len(terms)) or (match_mode == "any" and matched_terms): + occurrences = sum(lowered.count(term) for term in matched_terms) + best = { + "path": "data", + "text": combined, + "score": len(matched_terms) * 100 + occurrences, + "matched_terms": matched_terms, + } + return best + + +def _snippet(text: str, terms: list[str], max_chars: int) -> str: + compact = re.sub(r"\s+", " ", text).strip() + if len(compact) <= max_chars: + return compact + lowered = compact.lower() + positions = [lowered.find(term) for term in terms if lowered.find(term) >= 0] + center = min(positions) if positions else 0 + start = max(0, center - max_chars // 3) + end = min(len(compact), start + max_chars) + start = max(0, end - max_chars) + prefix = "..." if start else "" + suffix = "..." if end < len(compact) else "" + return f"{prefix}{compact[start:end].strip()}{suffix}" + + +def _search_items_for_payload(payload: dict) -> list[dict]: + data = payload.get("data") + meta = payload.get("_meta") or {} + if isinstance(data, list): + items = [] + for index, event in enumerate(data): + if isinstance(event, dict): + items.append( + { + "kind": "event", + "index": index, + "id": event.get("id"), + "time": event.get("time"), + "data": event.get("data", event), + } + ) + return items + return [ + { + "kind": "snapshot", + "index": None, + "id": None, + "time": meta.get("last_updated"), + "data": data, + } + ] + + +def _search_state(root: Path, query: str, *, match_mode: str, limit: int, max_field_chars: int, snippet_chars: int) -> dict: + terms = _query_terms(query) + if not terms: + raise ValueError("search query has no usable terms") + + matches = [] + missing_streams = 0 + for row in _stream_rows(root): + if not row["exists"] or not row.get("path"): + missing_streams += 1 + continue + payload = fetch.load_existing_state(root / row["path"]) + if not payload: + continue + for item in _search_items_for_payload(payload): + best = _best_field_match(item["data"], terms, match_mode, max_field_chars) + if not best: + continue + matches.append( + { + "subscription_id": row["id"], + "title": row["title"], + "template": row["template"], + "mode": row["mode"], + "stale": row["stale"], + "last_updated": row["last_updated"], + "item_kind": item["kind"], + "item_index": item["index"], + "item_id": item["id"], + "item_time": item["time"], + "path": best["path"], + "matched_terms": best["matched_terms"], + "score": best["score"], + "snippet": _snippet(best["text"], terms, snippet_chars), + } + ) + matches.sort( + key=lambda match: ( + match["score"], + match["item_time"] or match["last_updated"] or "", + match["subscription_id"], + ), + reverse=True, + ) + return { + "query": query, + "terms": terms, + "match": match_mode, + "matches": matches[:limit], + "total_matches": len(matches), + "missing_streams": missing_streams, + } + + +def cmd_search(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + query = " ".join(args.query).strip() + if not query: + print("search requires a query", file=sys.stderr) + return 2 + try: + result = _search_state( + args.root, + query, + match_mode=args.match, + limit=args.limit, + max_field_chars=args.max_field_chars, + snippet_chars=args.snippet_chars, + ) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + if not result["matches"]: + print("No matching local stream state.") + return 0 + for match in result["matches"]: + stale = "stale" if match["stale"] else "fresh" + item = f" item={match['item_id']}" if match["item_id"] else "" + print(f"{match['subscription_id']}: {match['title']} [{stale}{item}]") + print(f" {match['path']}: {match['snippet']}") + return 0 + + +def cmd_streams_list(args: argparse.Namespace) -> int: + rows = _stream_rows(args.root) + if args.json: + print(json.dumps({"streams": rows}, indent=2, sort_keys=True)) + return 0 + _print_stream_rows(rows) + return 0 + + +def cmd_streams_search(args: argparse.Namespace) -> int: + query = " ".join(args.query).strip() + rows = _stream_rows(args.root) + if query: + rows = [row for row in rows if _stream_matches(row, query)] + if args.json: + print(json.dumps({"streams": rows}, indent=2, sort_keys=True)) + return 0 + _print_stream_rows(rows) + return 0 + + +def _subscription_by_id(root: Path, subscription_id: str) -> tuple[dict, dict]: + config = active_subscriptions(root) + for subscription in config.get("subscriptions") or []: + if subscription.get("id") == subscription_id: + return config, subscription + raise KeyError(f"No matching subscription: {subscription_id}") + + +def _stream_detail(root: Path, subscription_id: str) -> dict: + config, subscription = _subscription_by_id(root, subscription_id) + defaults = config.get("defaults") or {} + row = state_status(root, subscription, defaults) + stream = fetch.load_stream_definition(root, template_id_for(subscription)) + parameters = subscription.get("parameters") or {} + stream_uri = fetch.source_uri_for(stream, parameters) + path = fetch.state_path_for_stream(stream_uri, root) + payload = fetch.load_existing_state(path) if path.exists() else None + meta = (payload or {}).get("_meta", {}) + data = (payload or {}).get("data") + data_summary = {"kind": type(data).__name__} + if isinstance(data, list): + data_summary["count"] = len(data) + elif isinstance(data, dict): + data_summary["keys"] = sorted(data.keys()) + return { + **row, + "template": row.get("template"), + "stream": stream_uri, + "state_path": str(path.relative_to(root)), + "meta": meta, + "data_summary": data_summary, + } + + +def cmd_streams_show(args: argparse.Namespace) -> int: + try: + result = _stream_detail(args.root, args.subscription_id) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + print(f"{result['id']}: {result['title']}") + print(f"Template: {result['template']}") + print(f"Mode: {result['mode']}") + print(f"Freshness: {'stale' if result['stale'] else 'due' if result['due'] else 'fresh'}") + print(f"Updated: {result['last_updated'] or 'never'}") + print(f"State path: {result['state_path']}") + print(f"Stream: {result['stream']}") + print(f"Data: {json.dumps(result['data_summary'], sort_keys=True)}") + return 0 + + +def _limited_data(data: object, limit: int | None) -> object: + if limit is None: + return data + if isinstance(data, list): + return data[:limit] + return data + + +def cmd_streams_read(args: argparse.Namespace) -> int: + try: + detail = _stream_detail(args.root, args.subscription_id) + path = args.root / detail["state_path"] + payload = fetch.load_existing_state(path) + if payload is None: + raise FileNotFoundError(f"state file not found or invalid: {detail['state_path']}") + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + + data = _limited_data(payload.get("data"), args.limit) + result = { + "id": detail["id"], + "title": detail["title"], + "template": detail["template"], + "state_path": detail["state_path"], + "stale": detail["stale"], + "last_updated": detail["last_updated"], + "data": data, + } + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + print(f"{result['id']}: {result['title']}") + print(f"Template: {result['template']}") + print(f"State path: {result['state_path']}") + print(f"Updated: {result['last_updated'] or 'never'}") + print(f"Stale: {'yes' if result['stale'] else 'no'}") + print("Data:") + print(json.dumps(data, indent=2, sort_keys=True)) + return 0 + + +def cmd_refresh(args: argparse.Namespace) -> int: + if args.all: + return fetch.main(["--root", str(args.root), "--all"]) + if not args.subscription_id: + print("refresh requires a subscription id or --all", file=sys.stderr) + return 2 + return fetch.main(["--root", str(args.root), "--stream", args.subscription_id]) + + +def _current_crontab() -> str: + try: + result = subprocess.run(["crontab", "-l"], check=False, text=True, capture_output=True) + except FileNotFoundError: + return "" + return "" if result.returncode else result.stdout + + +def _cron_block_present(text: str) -> bool: + return POLLING_BEGIN_MARKER in text and POLLING_END_MARKER in text + + +def _argv_uses_root(argv: object, root: Path) -> bool: + if not isinstance(argv, list): + return False + items = [str(item) for item in argv] + if "--root" in items: + index = items.index("--root") + return index + 1 < len(items) and Path(items[index + 1]).expanduser() == root + return root == fetch.DEFAULT_ROOT + + +def _cron_block_uses_root(text: str, root: Path) -> bool: + if not _cron_block_present(text): + return False + if "--root" in text: + return str(root) in text + return root == fetch.DEFAULT_ROOT + + +def _polling_status(root: Path) -> dict: + system = platform.system() + status: dict[str, object] = { + "root": str(root), + "platform": system, + "installed": False, + "method": "unsupported", + "fetcher": None, + "logs": str(root / "logs"), + } + try: + status["fetcher"] = polling_install.fetcher_path() + status["fetcher_available"] = True + except Exception as exc: # noqa: BLE001 - status should be diagnostic, not fatal. + status["fetcher_available"] = False + status["fetcher_error"] = str(exc) + + if system == "Darwin": + plist_path = Path.home() / "Library" / "LaunchAgents" / f"{POLLING_LABEL}.plist" + installed = False + if plist_path.exists(): + try: + payload = plistlib.loads(plist_path.read_bytes()) + installed = _argv_uses_root(payload.get("ProgramArguments"), root) + except Exception: + installed = root == fetch.DEFAULT_ROOT + status.update({"method": "launchd", "installed": installed, "path": str(plist_path)}) + return status + if system in {"Linux", "FreeBSD"}: + text = _current_crontab() + status.update({"method": "cron", "installed": _cron_block_uses_root(text, root)}) + return status + return status + + +def _print_polling_status(status: dict) -> None: + print(f"Polling: {'installed' if status['installed'] else 'not installed'}") + print(f"Method: {status['method']}") + print(f"Root: {status['root']}") + print(f"Fetcher: {status.get('fetcher') or status.get('fetcher_error') or 'unknown'}") + print(f"Logs: {status['logs']}") + + +def cmd_polling_status(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + status = _polling_status(args.root) + if args.json: + print(json.dumps(status, indent=2, sort_keys=True)) + return 0 + _print_polling_status(status) + return 0 + + +def cmd_polling_install(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + return polling_install.main(["--root", str(args.root)]) + + +def cmd_polling_uninstall(_args: argparse.Namespace) -> int: + return polling_uninstall.main([]) + + +def cmd_templates_list(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + index = fetch.load_catalog_index(args.root) + for stream in index.get("streams") or []: + params = ", ".join(stream.get("parameters") or []) or "none" + print(f"{stream['id']}: {stream['title']} [params: {params}, source: {stream.get('source', 'builtin')}]") + return 0 + + +def cmd_templates_path(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + print(fetch.templates_root(args.root)) + return 0 + + +def cmd_templates_validate(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + try: + paths = fetch.validate_template_tree(args.root) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + if not paths: + print(f"No local templates found in {fetch.template_streams_root(args.root)}") + return 0 + for path in paths: + print(f"valid: {path}") + return 0 + + +def cmd_templates_adapters(_args: argparse.Namespace) -> int: + for kind, description in ADAPTER_KINDS.items(): + print(f"{kind}: {description}") + return 0 + + +def cmd_templates_scaffold(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + try: + stream, schema, stream_rel_path, schema_rel_path = scaffold_stream(args.template_id, args.adapter_kind) + except ValueError as exc: + print(str(exc), file=sys.stderr) + return 2 + + stream_path = fetch.template_streams_root(args.root) / stream_rel_path + schema_path = fetch.template_schemas_root(args.root) / schema_rel_path + if stream_path.exists() and not args.force: + print(f"template already exists: {stream_path}", file=sys.stderr) + return 1 + + stream_path.parent.mkdir(parents=True, exist_ok=True) + stream_path.write_text(yaml.safe_dump(stream, sort_keys=False), encoding="utf-8") + + if args.adapter_kind not in {"rss", "ical", "local_command"} and (args.force or not schema_path.exists()): + schema_path.parent.mkdir(parents=True, exist_ok=True) + schema_path.write_text(json.dumps(schema, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + print(f"wrote: {stream_path}") + if args.adapter_kind in {"rss", "ical", "local_command"}: + print(f"schema: built-in {stream['schema_url']}") + else: + print(f"wrote: {schema_path}") + print("Next: edit the draft, then run `python3 scripts/agentfeeds.py admin templates validate`.") + return 0 + + +def _event_sample(events: list[dict]) -> object: + if not events: + return None + return events[0].get("data") + + +def cmd_templates_test(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + try: + stream = fetch.load_stream_definition(args.root, args.template_id) + params = parse_params(args.parameters) + fetch.validate_parameters(stream, params) + stream_uri, events = fetch.run_adapter(stream, params, args.root) + state_path = fetch.state_path_for_stream(stream_uri, args.root) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + + result = { + "template": stream["id"], + "title": stream["title"], + "mode": stream["mode"], + "stream": stream_uri, + "state_path": str(state_path.relative_to(args.root)), + "event_count": len(events), + "sample": _event_sample(events), + } + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + print(f"Template: {result['template']}") + print(f"Title: {result['title']}") + print(f"Mode: {result['mode']}") + print(f"Stream: {result['stream']}") + print(f"State path: {result['state_path']}") + print(f"Events: {result['event_count']}") + print("Sample:") + print(json.dumps(result["sample"], indent=2, sort_keys=True)) + return 0 + + +def cmd_templates_approve_command(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + try: + stream = fetch.load_stream_definition(args.root, args.template_id) + params = parse_params(args.parameters) + fetch.validate_parameters(stream, params) + adapter = fetch.substitute(stream["adapter"], params) + adapter = fetch.resolve_secret_refs(args.root, adapter) + if adapter.get("kind") != "local_command": + raise ValueError(f"{stream['id']}: template is not a local_command template") + if not sys.stdin.isatty(): + raise PermissionError("local_command approval must be run by the operator in an interactive terminal") + output = sys.stderr if args.json else sys.stdout + print("Local command approval required.", file=output) + print(f"Template: {stream['id']}", file=output) + print(f"Command: {json.dumps(adapter.get('command'))}", file=output) + print(f"CWD: {adapter.get('cwd') or os.getcwd()}", file=output) + if args.json: + print("Type APPROVE to approve this exact command: ", end="", file=sys.stderr) + typed = input() + else: + typed = input("Type APPROVE to approve this exact command: ") + if typed != "APPROVE": + raise PermissionError("approval cancelled") + stream_path = local_template_file(args.root, stream["id"]) + if stream_path: + payload = yaml.safe_load(stream_path.read_text(encoding="utf-8")) or {} + payload["pending"] = False + stream_path.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8") + stream = fetch.load_stream_definition(args.root, args.template_id) + approval = fetch.write_local_command_approval(args.root, stream, adapter) + path = fetch.local_command_approval_path(args.root, stream["id"]) + except Exception as exc: + print(str(exc), file=sys.stderr) + return 1 + + result = { + "template": stream["id"], + "approval_path": str(path.relative_to(args.root)), + "digest": approval["digest"], + "command": approval["command"], + "cwd": approval.get("cwd"), + } + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + print(f"Approved: {result['template']}") + print(f"Path: {result['approval_path']}") + print(f"Digest: {result['digest']}") + print(f"Command: {json.dumps(result['command'])}") + return 0 + + +def cmd_secrets_set(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + if not sys.stdin.isatty(): + print("secret input must be run by the user in an interactive terminal", file=sys.stderr) + return 1 + value = getpass.getpass(f"Secret value for {args.name}: ") + fetch.write_secret(args.root, args.name, value) + print(f"Secret set: {args.name}") + return 0 + + +def cmd_secrets_list(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + names = sorted(path.stem for path in (args.root / "secrets").glob("*.txt")) + if args.json: + print(json.dumps({"secrets": names}, indent=2, sort_keys=True)) + return 0 + for name in names: + print(name) + return 0 + + +MACOS_EVENT_SCHEMA = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agentfeeds.dev/schemas/macos.item.v1.json", + "title": "macOS Item", + "type": "object", + "required": ["title"], + "properties": { + "title": {"type": "string"}, + "content": {"type": ["string", "null"]}, + "source": {"type": ["string", "null"]}, + "url": {"type": ["string", "null"]}, + "starts_at": {"type": ["string", "null"]}, + "ends_at": {"type": ["string", "null"]}, + "updated_at": {"type": ["string", "null"]}, + "sender": {"type": ["string", "null"]}, + }, +} + + +def _macos_stream(template_id: str, title: str, description: str, script_name: str, poll_seconds: int) -> dict: + script_path = fetch.repo_root() / "scripts" / "macos" / script_name + return { + "id": template_id, + "title": title, + "description": description, + "type": "macos.item", + "mode": "event", + "schema_url": "https://agentfeeds.dev/schemas/macos.item.v1.json", + "schema_version": "1.0.0", + "parameters": [], + "source_uri_template": f"feed://macos.{template_id.split('/', 1)[1]}/items", + "adapter": { + "kind": "local_command", + "command": [sys.executable, str(script_path)], + "timeout_seconds": 30, + "max_output_bytes": 1048576, + "parse": "json", + "items_from": "items", + "id_from": "id", + "time_from": "updated_at", + "transform": { + "language": "jmespath", + "expression": ( + "{title: title, content: content, source: source, url: url, starts_at: starts_at, " + "ends_at: ends_at, updated_at: updated_at, sender: sender}" + ), + }, + }, + "recommended_poll_interval_seconds": poll_seconds, + "auth": "none", + "tags": ["macos", "local-command"], + "quality_tier": "experimental", + "contributed_by": "local", + "pending": True, + } + + +def macos_native_templates() -> list[dict]: + return [ + _macos_stream( + "macos/calendar-today", + "macOS Calendar today", + "Read-only events from the local macOS Calendar app for today.", + "calendar_today.py", + 300, + ), + _macos_stream( + "macos/reminders-open", + "macOS open reminders", + "Read-only incomplete reminders from the local macOS Reminders app.", + "reminders_open.py", + 600, + ), + _macos_stream( + "macos/mail-inbox-recent", + "macOS recent inbox mail", + "Read-only recent messages from the local macOS Mail inbox.", + "mail_inbox_recent.py", + 300, + ), + ] + + +def cmd_macos_install_templates(args: argparse.Namespace) -> int: + fetch.ensure_root(args.root) + schema_path = fetch.template_schemas_root(args.root) / "macos.item.v1.json" + if args.force or not schema_path.exists(): + schema_path.parent.mkdir(parents=True, exist_ok=True) + schema_path.write_text(json.dumps(MACOS_EVENT_SCHEMA, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + written = [] + skipped = [] + for stream in macos_native_templates(): + category, name = stream["id"].split("/", 1) + path = fetch.template_streams_root(args.root) / category / f"{name}.yaml" + if path.exists() and not args.force: + skipped.append(str(path)) + continue + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(yaml.safe_dump(stream, sort_keys=False), encoding="utf-8") + written.append(str(path)) + + result = { + "schema": str(schema_path), + "written": written, + "skipped": skipped, + "next_actions": [ + { + "action": "approve_local_command", + "command": "python3 scripts/agentfeeds.py admin templates approve-command ", + "reason": "macOS templates are local_command templates and remain pending until the operator approves them", + } + ], + } + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 + + for path in written: + print(f"wrote: {path}") + for path in skipped: + print(f"skipped existing: {path}") + print("Next: run `python3 scripts/agentfeeds.py admin templates approve-command ` in a terminal for each template you want to enable.") + return 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Manage Agent Feeds subscriptions") + parser.add_argument("--root", type=Path, default=fetch.DEFAULT_ROOT, help="agentfeeds root directory") + subparsers = parser.add_subparsers(dest="command", required=True) + + brief = subparsers.add_parser("brief", help="print compact stable stream context for prompt injection") + brief.add_argument("--max-streams", type=int, default=20) + brief.add_argument("--include-freshness", action="store_true", help="include volatile freshness metadata") + brief.add_argument("--json", action="store_true") + brief.set_defaults(func=cmd_brief) + + search = subparsers.add_parser("search", help="search existing local stream state") + search.add_argument("query", nargs="+") + search.add_argument("--match", choices=["all", "any"], default="all", help="require all query terms or any term") + search.add_argument("--limit", type=int, default=10, help="maximum matching items to return") + search.add_argument("--max-field-chars", type=int, default=20000, help="maximum characters searched per field") + search.add_argument("--snippet-chars", type=int, default=240, help="maximum snippet characters") + search.add_argument("--json", action="store_true") + search.set_defaults(func=cmd_search) + + subscribe = subparsers.add_parser("subscribe", help="add a subscription") + subscribe.add_argument("template_id") + subscribe.add_argument("parameters", nargs="*", help="template parameters as key=value") + subscribe.add_argument("--id", dest="instance_id", help="concrete subscription id to create") + subscribe.add_argument("--title", help="concrete subscription title") + subscribe.add_argument("--poll-interval-seconds", type=int) + subscribe.add_argument("--history-limit", type=int) + subscribe.add_argument("--no-fetch", action="store_true") + subscribe.add_argument("--dry-run", action="store_true", help="preview subscription id, state path, and requirements without writing") + subscribe.add_argument("--update", metavar="SUBSCRIPTION_ID", help="update an existing subscription in place") + subscribe.add_argument("--json", action="store_true", help="print machine-readable output") + subscribe.set_defaults(func=cmd_subscribe) + + unsubscribe = subparsers.add_parser("unsubscribe", help="remove a subscription") + unsubscribe.add_argument("subscription_id") + unsubscribe.add_argument("--keep-state", action="store_true") + unsubscribe.set_defaults(func=cmd_unsubscribe) + + refresh = subparsers.add_parser("refresh", help="refresh subscriptions") + refresh.add_argument("--stream", dest="subscription_id", help="refresh one subscription id") + refresh.add_argument("--all", action="store_true") + refresh.set_defaults(func=cmd_refresh) + + streams = subparsers.add_parser("streams", help="inspect and read active streams") + stream_subparsers = streams.add_subparsers(dest="stream_command", required=True) + streams_list = stream_subparsers.add_parser("list", help="list active streams") + streams_list.add_argument("--json", action="store_true") + streams_list.set_defaults(func=cmd_streams_list) + streams_find = stream_subparsers.add_parser("find", help="find active streams by metadata") + streams_find.add_argument("query", nargs="*") + streams_find.add_argument("--json", action="store_true") + streams_find.set_defaults(func=cmd_streams_search) + streams_health = stream_subparsers.add_parser("health", help="show stream freshness and fetch errors") + streams_health.add_argument("--json", action="store_true") + streams_health.set_defaults(func=cmd_streams_health) + streams_read = stream_subparsers.add_parser("read", help="read active stream data") + streams_read.add_argument("subscription_id") + streams_read.add_argument("--limit", type=int, default=20, help="limit event-list data rows") + streams_read.add_argument("--json", action="store_true", help="print machine-readable output") + streams_read.set_defaults(func=cmd_streams_read) + + templates = subparsers.add_parser("templates", help="browse and test feed templates") + template_subparsers = templates.add_subparsers(dest="template_command", required=True) + template_search = template_subparsers.add_parser("find", help="find built-in and local templates") + template_search.add_argument("query", nargs="*") + template_search.add_argument("-v", "--verbose", action="store_true") + template_search.set_defaults(func=cmd_templates_search) + template_show = template_subparsers.add_parser("show", help="show one template") + template_show.add_argument("template_id") + template_show.add_argument("--json", action="store_true") + template_show.set_defaults(func=cmd_templates_show) + + admin = subparsers.add_parser("admin", help="advanced setup, diagnostics, and template authoring") + admin_subparsers = admin.add_subparsers(dest="admin_command", required=True) + admin_polling = admin_subparsers.add_parser("polling", help="manage background refresh") + admin_polling_subparsers = admin_polling.add_subparsers(dest="polling_command", required=True) + admin_polling_status = admin_polling_subparsers.add_parser("status", help="show background refresh status") + admin_polling_status.add_argument("--json", action="store_true") + admin_polling_status.set_defaults(func=cmd_polling_status) + admin_polling_subparsers.add_parser("install", help="install or update background refresh").set_defaults(func=cmd_polling_install) + admin_polling_subparsers.add_parser("uninstall", help="remove background refresh").set_defaults(func=cmd_polling_uninstall) + + admin_templates = admin_subparsers.add_parser("templates", help="template authoring tools") + admin_template_subparsers = admin_templates.add_subparsers(dest="template_command", required=True) + admin_template_subparsers.add_parser("adapters", help="list scaffoldable adapter kinds").set_defaults(func=cmd_templates_adapters) + admin_template_subparsers.add_parser("path", help="print the local template directory").set_defaults(func=cmd_templates_path) + admin_template_subparsers.add_parser("list", help="list built-in and local templates").set_defaults(func=cmd_templates_list) + admin_scaffold = admin_template_subparsers.add_parser("scaffold", help="create a draft local template") + admin_scaffold.add_argument("adapter_kind", choices=sorted(ADAPTER_KINDS)) + admin_scaffold.add_argument("template_id", metavar="template_id") + admin_scaffold.add_argument("--force", action="store_true", help="overwrite an existing draft") + admin_scaffold.set_defaults(func=cmd_templates_scaffold) + admin_test = admin_template_subparsers.add_parser("test", help="run a template once without writing state") + admin_test.add_argument("template_id", metavar="template_id") + admin_test.add_argument("parameters", nargs="*", help="template parameters as key=value") + admin_test.add_argument("--json", action="store_true", help="print machine-readable output") + admin_test.set_defaults(func=cmd_templates_test) + admin_approve = admin_template_subparsers.add_parser("approve-command", help="operator-only approval for local_command templates") + admin_approve.add_argument("template_id", metavar="template_id") + admin_approve.add_argument("parameters", nargs="*", help="template parameters as key=value") + admin_approve.add_argument("--json", action="store_true", help="print machine-readable output") + admin_approve.set_defaults(func=cmd_templates_approve_command) + admin_template_subparsers.add_parser("validate", help="validate local templates").set_defaults(func=cmd_templates_validate) + + admin_streams = admin_subparsers.add_parser("streams", help="advanced stream diagnostics") + admin_stream_subparsers = admin_streams.add_subparsers(dest="stream_command", required=True) + admin_stream_show = admin_stream_subparsers.add_parser("show", help="show active stream metadata") + admin_stream_show.add_argument("subscription_id") + admin_stream_show.add_argument("--json", action="store_true") + admin_stream_show.set_defaults(func=cmd_streams_show) + + admin_secrets = admin_subparsers.add_parser("secrets", help="manage local secret values") + admin_secret_subparsers = admin_secrets.add_subparsers(dest="secret_command", required=True) + secret_set = admin_secret_subparsers.add_parser("set", help="set a local secret value") + secret_set.add_argument("name") + secret_set.set_defaults(func=cmd_secrets_set) + secret_list = admin_secret_subparsers.add_parser("list", help="list local secret names") + secret_list.add_argument("--json", action="store_true") + secret_list.set_defaults(func=cmd_secrets_list) + + admin_macos = admin_subparsers.add_parser("macos", help="install macOS-native local templates") + admin_macos_subparsers = admin_macos.add_subparsers(dest="macos_command", required=True) + install_macos_templates = admin_macos_subparsers.add_parser("install-templates", help="install Calendar, Reminders, and Mail templates") + install_macos_templates.add_argument("--force", action="store_true", help="overwrite existing macOS local templates") + install_macos_templates.add_argument("--json", action="store_true") + install_macos_templates.set_defaults(func=cmd_macos_install_templates) + + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + args.root = args.root.expanduser() + return args.func(args) + + +if __name__ == "__main__": + raise SystemExit(main()) From 913e4646a579e3e7f40aa66b8d877e5371c89ea2 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:02 -0700 Subject: [PATCH 75/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/__main__.py --- .../scripts/lib/agentfeeds_runtime/__main__.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/__main__.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__main__.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__main__.py new file mode 100644 index 00000000..e1fae542 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/__main__.py @@ -0,0 +1,9 @@ +"""Run the Agent Feeds management CLI with `python -m agentfeeds_runtime`.""" + +from __future__ import annotations + +from agentfeeds_runtime.commands import main + + +if __name__ == "__main__": + raise SystemExit(main()) From c5d07146a1c3ed60a528e895d34585f7f390a543 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:03 -0700 Subject: [PATCH 76/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/fetcher.py --- .../scripts/lib/agentfeeds_runtime/fetcher.py | 915 ++++++++++++++++++ 1 file changed, 915 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py new file mode 100644 index 00000000..776b842d --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py @@ -0,0 +1,915 @@ +#!/usr/bin/env python3 +"""Reference fetcher for Agent Feeds v0.3.""" + +from __future__ import annotations + +import argparse +import contextlib +import hashlib +import json +import os +import re +import subprocess +import sys +from datetime import UTC, datetime, timedelta +from pathlib import Path +from urllib.parse import parse_qs, urlparse + +import jsonschema +import requests + +from agentfeeds_runtime.adapters import run_adapter as run_adapter_impl +from agentfeeds_runtime.constants import REQUEST_TIMEOUT_SECONDS + +try: + import fcntl +except ImportError: # pragma: no cover - Windows import compatibility. + fcntl = None + +try: + import yaml +except ImportError: # pragma: no cover + yaml = None + + +SPEC_VERSION = "0.3" +DEFAULT_ROOT = Path.home() / ".agentfeeds" +DEFAULT_CATALOG_BASE_URL = "https://raw.githubusercontent.com/verkyyi/agentfeeds-catalog/main" +PARAMETER_PATTERN = re.compile(r"{([A-Za-z_][A-Za-z0-9_]*)}") +SECRET_REF_PATTERN = re.compile(r"\{\{secret:([A-Za-z_][A-Za-z0-9_.-]*)\}\}") +SENSITIVE_OUTPUT_PATTERN = re.compile( + r"(?i)(authorization|token|api[-_ ]?key|secret|cookie)([\"'\s:=]+)([^\"'\s,}]+)" +) +UNSAFE_NAME_PATTERN = re.compile(r"[^A-Za-z0-9._-]+") +LOCK_FILE_NAME = "agentfeeds-fetch.lock" + + +def now_utc() -> str: + return datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + + +def parse_utc(value: str | None) -> datetime | None: + if not value: + return None + try: + return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC) + except ValueError: + return None + + +def safe_name_part(value: str, fallback: str = "part", max_chars: int = 96) -> str: + compact = UNSAFE_NAME_PATTERN.sub("-", value).strip("-._") + if not compact: + compact = fallback + if len(compact) > max_chars: + compact = compact[:max_chars].rstrip("-._") or fallback + return compact + + +def query_name_part(query: str) -> str: + compact = safe_name_part(query.replace("&", "-"), "query", 96) + digest = hashlib.sha256(query.encode("utf-8")).hexdigest()[:12] + return f"{compact}.{digest}" + + +def state_path_for_stream(stream_uri: str, root: Path = DEFAULT_ROOT) -> Path: + parsed = urlparse(stream_uri) + if parsed.scheme != "feed" or not parsed.netloc: + raise ValueError(f"invalid feed URI: {stream_uri}") + + path = ".".join(safe_name_part(part, "path") for part in parsed.path.split("/") if part) + name = path or "index" + if parsed.netloc == "local.file": + params = parse_qs(parsed.query, keep_blank_values=True) + local_path = (params.get("path") or [""])[0] + stem = safe_name_part(Path(local_path).name, "file") + digest = hashlib.sha256(parsed.query.encode("utf-8")).hexdigest()[:12] + return root / "state" / parsed.netloc / f"{name}.{stem}.{digest}.json" + if parsed.query: + name = f"{name}.{query_name_part(parsed.query)}" + return root / "state" / parsed.netloc / f"{name}.json" + + +def atomic_write_json(path: Path, payload: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = path.with_suffix(path.suffix + ".tmp") + with tmp_path.open("w", encoding="utf-8") as handle: + json.dump(payload, handle, indent=2, sort_keys=True) + handle.write("\n") + handle.flush() + os.fsync(handle.fileno()) + tmp_path.replace(path) + + +def status_path_for_subscription(root: Path, subscription_id: str) -> Path: + parts = [part for part in subscription_id.split("/") if part] + if not parts: + parts = ["unknown"] + safe_parts = [re.sub(r"[^A-Za-z0-9._-]+", "-", part).strip("-") or "unknown" for part in parts] + safe_parts[-1] = f"{safe_parts[-1]}.json" + return root / "status" / "subscriptions" / Path(*safe_parts) + + +def load_fetch_status(root: Path, subscription_id: str) -> dict: + return load_existing_state(status_path_for_subscription(root, subscription_id)) or {} + + +def redact_sensitive_text(value: str | None) -> str | None: + if value is None: + return None + value = re.sub(r"(?i)(authorization[\"'\s:=]+)[^,\n}]+", r"\1[redacted]", value) + return SENSITIVE_OUTPUT_PATTERN.sub(lambda match: f"{match.group(1)}{match.group(2)}[redacted]", value) + + +def write_fetch_status( + root: Path, + subscription: dict, + stream: dict | None, + *, + attempted_at: str, + succeeded: bool, + error: str | None = None, + state_path: Path | None = None, +) -> None: + subscription_id = str(subscription.get("id", "")) + previous = load_fetch_status(root, subscription_id) + payload = { + "subscription_id": subscription_id, + "template_id": template_id_for(subscription) if subscription.get("template") else None, + "title": subscription_title(subscription, stream or {}) if stream else str(subscription.get("title") or subscription_id), + "last_attempt_at": attempted_at, + "last_success_at": attempted_at if succeeded else previous.get("last_success_at"), + "last_error_at": None if succeeded else attempted_at, + "last_error": None if succeeded else redact_sensitive_text(error), + "consecutive_failures": 0 if succeeded else int(previous.get("consecutive_failures") or 0) + 1, + "state_path": str(state_path.relative_to(root)) if state_path and root in state_path.parents else previous.get("state_path"), + } + atomic_write_json(status_path_for_subscription(root, subscription_id), payload) + + +@contextlib.contextmanager +def fetch_lock(root: Path): + if fcntl is None: + raise RuntimeError("Agent Feeds background locking requires a POSIX platform such as macOS, Linux, or WSL") + path = root / LOCK_FILE_NAME + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a+", encoding="utf-8") as handle: + try: + fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + yield False + return + try: + handle.seek(0) + handle.truncate() + handle.write(f"pid={os.getpid()} acquired_at={now_utc()}\n") + handle.flush() + os.fsync(handle.fileno()) + yield True + finally: + handle.seek(0) + handle.truncate() + handle.flush() + fcntl.flock(handle.fileno(), fcntl.LOCK_UN) + + +def ensure_root(root: Path) -> None: + (root / "approvals" / "local-command").mkdir(parents=True, exist_ok=True) + (root / "catalog-cache").mkdir(parents=True, exist_ok=True) + (root / "secrets").mkdir(parents=True, exist_ok=True) + (root / "state").mkdir(parents=True, exist_ok=True) + (root / "templates" / "streams").mkdir(parents=True, exist_ok=True) + (root / "templates" / "schemas" / "event-types").mkdir(parents=True, exist_ok=True) + subscriptions = root / "subscriptions.yaml" + if not subscriptions.exists(): + subscriptions.write_text( + 'version: "0.3"\n' + "defaults:\n" + " poll_interval_seconds: 600\n" + " history_limit: 50\n" + "subscriptions: []\n", + encoding="utf-8", + ) + + +def load_subscriptions(root: Path) -> dict: + if yaml is None: + raise RuntimeError("PyYAML is required to read subscriptions.yaml") + path = root / "subscriptions.yaml" + if not path.exists(): + return {"version": SPEC_VERSION, "subscriptions": []} + return yaml.safe_load(path.read_text(encoding="utf-8")) or {} + + +def repo_root() -> Path: + return Path(__file__).resolve().parents[3] + + +def templates_root(root: Path) -> Path: + return root / "templates" + + +def template_streams_root(root: Path) -> Path: + return templates_root(root) / "streams" + + +def template_schemas_root(root: Path) -> Path: + return templates_root(root) / "schemas" / "event-types" + + +def catalog_cache_root(root: Path) -> Path: + return root / "catalog-cache" + + +def cached_catalog_file(root: Path, relative_path: str) -> Path: + return catalog_cache_root(root) / relative_path + + +def bundled_catalog_root() -> Path: + return repo_root() / "catalog" + + +def local_catalog_root() -> Path | None: + configured = os.environ.get("AGENTFEEDS_CATALOG_DIR") + candidates = [] + if configured: + candidates.append(Path(configured).expanduser()) + candidates.append(repo_root()) + + for candidate in candidates: + if (candidate / "catalog" / "INDEX.json").exists(): + return candidate + if candidate.name == "catalog" and (candidate / "INDEX.json").exists(): + return candidate.parent + return None + + +def catalog_base_url() -> str: + return os.environ.get("AGENTFEEDS_CATALOG_BASE_URL", DEFAULT_CATALOG_BASE_URL).rstrip("/") + + +def write_text_atomic(path: Path, content: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = path.with_suffix(path.suffix + ".tmp") + tmp_path.write_text(content, encoding="utf-8") + tmp_path.replace(path) + + +def copy_catalog_cache_from_dir(root: Path, source_root: Path) -> None: + cache_root = catalog_cache_root(root) + catalog_root = source_root / "catalog" + index_text = (catalog_root / "INDEX.json").read_text(encoding="utf-8") + write_text_atomic(cache_root / "INDEX.json", index_text) + write_text_atomic(cache_root / "catalog" / "INDEX.json", index_text) + + for path in sorted(catalog_root.glob("**/*")): + if path.is_file(): + relative_path = path.relative_to(source_root) + write_text_atomic(cache_root / relative_path, path.read_text(encoding="utf-8")) + + +def download_catalog_cache(root: Path) -> None: + cache_root = catalog_cache_root(root) + base_url = catalog_base_url() + index_response = requests.get(f"{base_url}/catalog/INDEX.json", timeout=REQUEST_TIMEOUT_SECONDS) + index_response.raise_for_status() + index_text = index_response.text + index = json.loads(index_text) + + write_text_atomic(cache_root / "INDEX.json", index_text) + write_text_atomic(cache_root / "catalog" / "INDEX.json", index_text) + + schema_names = {"stream-definition.v0.3.json", "envelope.v0.3.json"} + for stream in index.get("streams", []): + relative_path = stream["path"] + response = requests.get(f"{base_url}/{relative_path}", timeout=REQUEST_TIMEOUT_SECONDS) + response.raise_for_status() + stream_text = response.text + write_text_atomic(cache_root / relative_path, stream_text) + stream_payload = yaml.safe_load(stream_text) + schema_names.add(stream_payload["schema_url"].rstrip("/").split("/")[-1]) + + for name in sorted(schema_names): + if name in {"stream-definition.v0.3.json", "envelope.v0.3.json"}: + relative_path = f"catalog/schemas/{name}" + else: + relative_path = f"catalog/schemas/event-types/{name}" + response = requests.get(f"{base_url}/{relative_path}", timeout=REQUEST_TIMEOUT_SECONDS) + response.raise_for_status() + write_text_atomic(cache_root / relative_path, response.text) + + +def update_catalog_cache(root: Path) -> None: + source_root = local_catalog_root() + if source_root is not None: + copy_catalog_cache_from_dir(root, source_root) + return + download_catalog_cache(root) + + +def load_catalog_index(root: Path) -> dict: + cache = catalog_cache_root(root) / "INDEX.json" + if not cache.exists(): + update_catalog_cache(root) + index = json.loads(cache.read_text(encoding="utf-8")) + streams = {stream["id"]: {**stream, "source": stream.get("source") or "builtin"} for stream in index.get("streams", [])} + for path in sorted(template_streams_root(root).glob("**/*.yaml")): + stream = stream_summary(path, root) + if stream["id"] in streams: + continue + streams[stream["id"]] = stream + merged = {**index, "streams": sorted(streams.values(), key=lambda item: item["id"])} + merged["stream_count"] = len(merged["streams"]) + return merged + + +def stream_summary(path: Path, root: Path) -> dict: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + try: + rel_path = str(path.relative_to(catalog_cache_root(root))) + source = "builtin" + except ValueError: + try: + rel_path = str(path.relative_to(repo_root())) + source = "builtin" + except ValueError: + rel_path = str(path) + source = "local" + try: + path.relative_to(template_streams_root(root)) + rel_path = str(path) + source = "local" + except ValueError: + pass + return { + "id": data["id"], + "title": data["title"], + "description": data["description"], + "type": data["type"], + "mode": data["mode"], + "tags": data.get("tags", []), + "parameters": [param["name"] for param in data.get("parameters", [])], + "auth": data["auth"], + "quality_tier": data["quality_tier"], + "path": rel_path, + "source": source, + } + + +def stream_definition_schema_path(root: Path) -> Path: + candidates = [ + cached_catalog_file(root, "catalog/schemas/stream-definition.v0.3.json"), + bundled_catalog_root() / "schemas" / "stream-definition.v0.3.json", + ] + for path in candidates: + if path.exists(): + return path + update_catalog_cache(root) + cached = cached_catalog_file(root, "catalog/schemas/stream-definition.v0.3.json") + if cached.exists(): + return cached + raise FileNotFoundError("stream definition schema not found in catalog cache") + + +def builtin_stream_paths(root: Path): + yield from (catalog_cache_root(root) / "catalog" / "streams").glob("**/*.yaml") + yield from (bundled_catalog_root() / "streams").glob("**/*.yaml") + + +def cached_event_schemas_root(root: Path) -> Path: + return cached_catalog_file(root, "catalog/schemas/event-types") + + +def load_stream_definition(root: Path, stream_id: str, _refreshed: bool = False) -> dict: + index = load_catalog_index(root) + match = next((item for item in index.get("streams", []) if item.get("id") == stream_id), None) + if not match: + raise KeyError(f"stream id not found in catalog: {stream_id}") + + candidate_paths = [] + if match.get("path"): + match_path = Path(match["path"]) + if match_path.is_absolute(): + candidate_paths.append(match_path) + else: + candidate_paths.append(repo_root() / match["path"]) + candidate_paths.append(root / "catalog-cache" / match["path"]) + candidate_paths.append(templates_root(root) / match["path"]) + candidate_paths.extend(template_streams_root(root).glob("**/*.yaml")) + candidate_paths.extend(builtin_stream_paths(root)) + + for path in candidate_paths: + if not path.exists(): + continue + data = yaml.safe_load(path.read_text(encoding="utf-8")) + if data.get("id") == stream_id: + return data + if match.get("source") != "local" and not _refreshed: + update_catalog_cache(root) + return load_stream_definition(root, stream_id, _refreshed=True) + raise FileNotFoundError(f"stream definition not found for {stream_id}") + + +def schema_path_for_url(root: Path, schema_url: str) -> Path: + name = schema_url.rstrip("/").split("/")[-1] + for path in [ + template_schemas_root(root) / name, + cached_event_schemas_root(root) / name, + bundled_catalog_root() / "schemas" / "event-types" / name, + ]: + if path.exists(): + return path + update_catalog_cache(root) + cached = cached_event_schemas_root(root) / name + if cached.exists(): + return cached + raise FileNotFoundError(f"referenced schema not found locally: {schema_url}") + + +def validate_stream_file(path: Path, root: Path = DEFAULT_ROOT) -> None: + stream = yaml.safe_load(path.read_text(encoding="utf-8")) + schema = json.loads(stream_definition_schema_path(root).read_text(encoding="utf-8")) + jsonschema.validate(stream, schema) + + schema_path = schema_path_for_url(root, stream["schema_url"]) + json.loads(schema_path.read_text(encoding="utf-8")) + + adapter_kind = stream["adapter"]["kind"] + if adapter_kind in {"json_http", "paginated_json_http"}: + for required in ("url", "method", "transform"): + if required not in stream["adapter"]: + raise ValueError(f"{path}: adapter.{required} is required for {adapter_kind}") + if adapter_kind in {"rss", "ical"} and "url" not in stream["adapter"]: + raise ValueError(f"{path}: adapter.url is required for {adapter_kind}") + path_based_adapters = {"local_file", "filesystem_scan", "markdown_scan", "git_status", "plist_reading_list"} + if adapter_kind in path_based_adapters and "path" not in stream["adapter"]: + raise ValueError(f"{path}: adapter.path is required for {adapter_kind}") + if adapter_kind == "local_command": + command = stream["adapter"].get("command") + if not isinstance(command, list) or not command or not all(isinstance(item, str) for item in command): + raise ValueError(f"{path}: adapter.command must be a non-empty string array for local_command") + if stream["mode"] == "event" and stream["adapter"].get("parse") != "json": + raise ValueError(f"{path}: local_command event streams require adapter.parse: json") + if adapter_kind == "apple_automation": + for required in ("script", "columns"): + if required not in stream["adapter"]: + raise ValueError(f"{path}: adapter.{required} is required for apple_automation") + if adapter_kind == "sqlite_query": + for required in ("database", "query", "columns"): + if required not in stream["adapter"]: + raise ValueError(f"{path}: adapter.{required} is required for sqlite_query") + tcc_adapters = {"apple_automation", "sqlite_query"} + if adapter_kind in tcc_adapters and "tcc_permission" not in stream["adapter"]: + raise ValueError(f"{path}: adapter.tcc_permission is required for {adapter_kind}") + + +def validate_template_tree(root: Path) -> list[Path]: + stream_paths = sorted(template_streams_root(root).glob("**/*.yaml")) + seen: dict[str, Path] = {} + index = load_catalog_index(root) + builtin_ids = {stream["id"] for stream in index.get("streams", []) if stream.get("source") != "local"} + for path in stream_paths: + validate_stream_file(path, root) + stream_id = yaml.safe_load(path.read_text(encoding="utf-8"))["id"] + if stream_id in builtin_ids: + raise ValueError(f"{path}: local template id conflicts with built-in template: {stream_id}") + if stream_id in seen: + raise ValueError(f"{path}: duplicate local template id also defined in {seen[stream_id]}: {stream_id}") + seen[stream_id] = path + return stream_paths + + +def substitute(value: object, parameters: dict) -> object: + if isinstance(value, str): + return PARAMETER_PATTERN.sub( + lambda match: str(parameters[match.group(1)]) + if match.group(1) in parameters + else match.group(0), + value, + ) + if isinstance(value, list): + return [substitute(item, parameters) for item in value] + if isinstance(value, dict): + return {key: substitute(item, parameters) for key, item in value.items()} + return value + + +def source_uri_for(stream: dict, parameters: dict) -> str: + return substitute(stream["source_uri_template"], parameters) + + +def secret_path(root: Path, name: str) -> Path: + safe = safe_name_part(name, "secret") + return root / "secrets" / f"{safe}.txt" + + +def read_secret(root: Path, name: str) -> str: + if sys.platform == "darwin": + result = subprocess.run( + ["security", "find-generic-password", "-s", "agentfeeds", "-a", name, "-w"], + check=False, + text=True, + capture_output=True, + ) + if result.returncode == 0: + return result.stdout.rstrip("\n") + path = secret_path(root, name) + if not path.exists(): + raise KeyError(f"secret {name} not set; user can run `python3 scripts/agentfeeds.py admin secrets set {name}`") + return path.read_text(encoding="utf-8").rstrip("\n") + + +def write_secret(root: Path, name: str, value: str) -> None: + if sys.platform == "darwin": + result = subprocess.run( + ["security", "add-generic-password", "-U", "-s", "agentfeeds", "-a", name, "-w", value], + check=False, + text=True, + capture_output=True, + ) + if result.returncode == 0: + return + path = secret_path(root, name) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(value.rstrip("\n") + "\n", encoding="utf-8") + os.chmod(path, 0o600) + + +def resolve_secret_refs(root: Path, value: object) -> object: + if isinstance(value, str): + return SECRET_REF_PATTERN.sub(lambda match: read_secret(root, match.group(1)), value) + if isinstance(value, list): + return [resolve_secret_refs(root, item) for item in value] + if isinstance(value, dict): + return {key: resolve_secret_refs(root, item) for key, item in value.items()} + return value + + +def add_auth_service_secret(adapter: dict) -> dict: + service = adapter.get("auth_service") + if not service: + return adapter + resolved = {**adapter} + headers = dict(resolved.get("headers") or {}) + headers.setdefault("Authorization", f"Bearer {{{{secret:{service}_token}}}}") + resolved["headers"] = headers + return resolved + + +def validate_parameters(stream: dict, parameters: dict) -> None: + missing = [ + parameter["name"] + for parameter in stream.get("parameters", []) + if parameter.get("required") and parameter["name"] not in parameters + ] + if missing: + raise ValueError(f"{stream['id']}: missing required parameters: {', '.join(missing)}") + + +def local_command_approval_digest(stream: dict, adapter: dict) -> str: + clean_stream = {key: value for key, value in stream.items() if not key.startswith("__") and key != "pending"} + payload = { + "stream": clean_stream, + "command": adapter.get("command"), + "cwd": adapter.get("cwd"), + } + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def local_command_approval_path(root: Path, stream_id: str) -> Path: + parts = [part for part in stream_id.split("/") if part] + if not parts: + parts = ["unknown"] + safe_parts = [safe_name_part(part, "unknown") for part in parts] + safe_parts[-1] = f"{safe_parts[-1]}.json" + return root / "approvals" / "local-command" / Path(*safe_parts) + + +def write_local_command_approval(root: Path, stream: dict, adapter: dict) -> dict: + digest = local_command_approval_digest(stream, adapter) + payload = { + "version": SPEC_VERSION, + "stream_id": stream["id"], + "digest": digest, + "command": adapter.get("command"), + "cwd": adapter.get("cwd"), + "approved_at": now_utc(), + } + atomic_write_json(local_command_approval_path(root, stream["id"]), payload) + return payload + + +def require_local_command_approval(root: Path, stream: dict, adapter: dict) -> None: + approval_path = local_command_approval_path(root, stream["id"]) + approval = load_existing_state(approval_path) + digest = local_command_approval_digest(stream, adapter) + if not approval or approval.get("digest") != digest: + rel_path = approval_path.relative_to(root) + raise PermissionError( + f"{stream['id']}: local_command is not approved for this command digest; " + f"review the command and run `python3 scripts/agentfeeds.py admin templates approve-command {stream['id']}` " + f"to write {rel_path}" + ) + + +def publisher_for(stream_uri: str) -> str: + return urlparse(stream_uri).netloc + + +def run_adapter(stream: dict, parameters: dict, root: Path | None = None) -> tuple[str, list[dict]]: + validate_parameters(stream, parameters) + adapter = substitute(stream["adapter"], parameters) + adapter = add_auth_service_secret(adapter) + adapter = resolve_secret_refs(root, adapter) if root is not None else adapter + if adapter.get("kind") == "local_command": + if root is None: + raise PermissionError(f"{stream['id']}: local_command execution requires an Agent Feeds root for approval lookup") + if stream.get("pending"): + raise PermissionError(f"{stream['id']}: local_command template is pending operator approval") + require_local_command_approval(root, stream, adapter) + resolved_stream = {**stream, "adapter": adapter} + return run_adapter_impl( + resolved_stream, + parameters, + validate_parameters=validate_parameters, + source_uri_for=source_uri_for, + substitute=substitute, + ) + + +def value_at_path(payload: dict, dotted_path: str) -> object: + current: object = payload + for part in dotted_path.split("."): + if not isinstance(current, dict) or part not in current: + return None + current = current[part] + return current + + +def comparison_matches(actual: object, expected: object) -> bool: + if not isinstance(expected, dict): + return actual == expected + for operator, value in expected.items(): + if operator == "gte" and not (actual is not None and actual >= value): + return False + if operator == "gt" and not (actual is not None and actual > value): + return False + if operator == "lte" and not (actual is not None and actual <= value): + return False + if operator == "lt" and not (actual is not None and actual < value): + return False + if operator == "eq" and actual != value: + return False + return True + + +def event_matches_filter(event: dict, filters: dict | None) -> bool: + if not filters: + return True + for path, expected in filters.items(): + payload = event["data"] if path.startswith("data.") else event + lookup = path.removeprefix("data.") + if not comparison_matches(value_at_path(payload, lookup), expected): + return False + return True + + +def load_existing_state(path: Path) -> dict | None: + if not path.exists(): + return None + try: + return json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError: + return None + + +def poll_interval(subscription: dict, stream: dict, defaults: dict) -> int: + return int( + subscription.get("poll_interval_seconds") + or defaults.get("poll_interval_seconds") + or stream.get("recommended_poll_interval_seconds") + or 600 + ) + + +def template_id_for(subscription: dict) -> str: + return str(subscription["template"]) + + +def subscription_title(subscription: dict, stream: dict) -> str: + return str(subscription.get("title") or stream.get("title") or subscription["id"]) + + +def state_path_for_subscription(root: Path, subscription: dict) -> Path: + stream = load_stream_definition(root, template_id_for(subscription)) + stream_uri = source_uri_for(stream, subscription.get("parameters") or {}) + return state_path_for_stream(stream_uri, root) + + +def is_due(path: Path, interval_seconds: int, force: bool) -> bool: + if force or not path.exists(): + return True + existing = load_existing_state(path) + if not existing: + return True + updated = parse_utc(existing.get("_meta", {}).get("last_updated")) + if not updated: + return True + return datetime.now(UTC) - updated >= timedelta(seconds=interval_seconds) + + +def schema_validation_enabled() -> bool: + value = os.environ.get("AGENTFEEDS_VALIDATE") + if value is None: + return True + return value.lower() not in {"0", "false", "no", "off"} + + +def validate_events_for_stream(root: Path, stream: dict, events: list[dict]) -> None: + if not schema_validation_enabled(): + return + schema = json.loads(schema_path_for_url(root, stream["schema_url"]).read_text(encoding="utf-8")) + for index, event in enumerate(events): + try: + jsonschema.validate(event.get("data"), schema) + except jsonschema.ValidationError as exc: + raise ValueError(f"{stream['id']}: event {index} does not match {stream['schema_url']}: {exc.message}") from exc + + +def state_payload( + subscription: dict, + stream: dict, + stream_uri: str, + events: list[dict], + existing: dict | None, + interval_seconds: int, + history_limit: int, +) -> dict: + updated_at = now_utc() + next_due = ( + datetime.now(UTC).replace(microsecond=0) + timedelta(seconds=interval_seconds) + ).isoformat().replace("+00:00", "Z") + meta = { + "stream": stream_uri, + "type": stream["type"], + "mode": stream["mode"], + "last_updated": updated_at, + "next_poll_due": next_due, + "schema_url": stream["schema_url"], + "schema_version": stream["schema_version"], + "publisher": publisher_for(stream_uri), + "stale": False, + "subscription_id": subscription["id"], + "template_id": stream["id"], + "title": subscription_title(subscription, stream), + } + + if stream["mode"] == "snapshot": + if not events: + raise ValueError(f"{stream['id']}: snapshot adapter produced no events") + return {"_meta": meta, "data": events[0]["data"]} + + if stream["mode"] == "event": + old_events = existing.get("data", []) if existing else [] + merged = [] + seen = set() + for event in [*events, *old_events]: + event_id = event.get("id") + if event_id in seen: + continue + seen.add(event_id) + merged.append(event) + return {"_meta": meta, "data": merged[:history_limit]} + + if stream["mode"] == "delta": + current = existing.get("data", {}) if existing else {} + for event in events: + current.update(event["data"]) + return {"_meta": meta, "data": current} + + raise ValueError(f"unsupported mode: {stream['mode']}") + + +def regenerate_catalog(root: Path) -> None: + lines = [ + "# Agent Feeds - Active Subscriptions", + "", + "This file lists data streams currently subscribed. Prefer `python3 scripts/agentfeeds.py streams read --json` for normal agent access.", + "", + ] + state_entries = [] + subscriptions = load_subscriptions(root).get("subscriptions") or [] + for subscription in subscriptions: + stream = load_stream_definition(root, template_id_for(subscription)) + parameters = subscription.get("parameters") or {} + stream_uri = source_uri_for(stream, parameters) + path = state_path_for_stream(stream_uri, root) + payload = load_existing_state(path) if path.exists() else {} + meta = (payload or {}).get("_meta", {}) + state_entries.append((subscription, stream, stream_uri, path, meta)) + if not state_entries: + lines.extend(["No active state files found.", ""]) + + for subscription, stream, stream_uri, path, meta in state_entries: + title = subscription_title(subscription, stream) + rel_path = path.relative_to(root) + lines.extend( + [ + f"## {title}", + f"- **ID:** `{subscription.get('id', '')}`", + f"- **Template:** `{template_id_for(subscription)}`", + f"- **Stream:** `{meta.get('stream') or stream_uri}`", + f"- **Path:** `{rel_path}`", + f"- **Updated:** {meta.get('last_updated', 'never')}", + f"- **Stale:** {'yes' if meta.get('stale') else 'no'}", + f"- **Mode:** {meta.get('mode') or stream.get('mode', 'unknown')}", + "", + ] + ) + + lines.extend( + [ + "---", + f"*Last regenerated: {now_utc()}.*", + "", + ] + ) + (root / "catalog.md").write_text("\n".join(lines), encoding="utf-8") + + +def run_fetch(args: argparse.Namespace, root: Path) -> int: + subscriptions = load_subscriptions(root) + defaults = subscriptions.get("defaults") or {} + active = subscriptions.get("subscriptions") or [] + if args.once: + active = [sub for sub in active if sub.get("id") == args.once] + if args.stream: + active = [sub for sub in active if sub.get("id") == args.stream] + + if not active: + regenerate_catalog(root) + return 0 + + force = args.all or bool(args.stream) or bool(args.once) + failures = 0 + for subscription in active: + attempted_at = now_utc() + stream = None + path = None + try: + stream = load_stream_definition(root, template_id_for(subscription)) + parameters = subscription.get("parameters") or {} + stream_uri = source_uri_for(stream, parameters) + path = state_path_for_stream(stream_uri, root) + interval_seconds = poll_interval(subscription, stream, defaults) + if not is_due(path, interval_seconds, force): + continue + + stream_uri, events = run_adapter(stream, parameters, root) + events = [event for event in events if event_matches_filter(event, subscription.get("filter"))] + validate_events_for_stream(root, stream, events) + history_limit = int(subscription.get("history_limit") or defaults.get("history_limit") or 50) + existing = load_existing_state(path) + payload = state_payload(subscription, stream, stream_uri, events, existing, interval_seconds, history_limit) + atomic_write_json(path, payload) + write_fetch_status(root, subscription, stream, attempted_at=attempted_at, succeeded=True, state_path=path) + except Exception as exc: # noqa: BLE001 - keep cron-friendly failure reporting. + failures += 1 + write_fetch_status(root, subscription, stream, attempted_at=attempted_at, succeeded=False, error=str(exc), state_path=path) + print(f"{subscription.get('id', '')}: {exc}", file=sys.stderr) + + regenerate_catalog(root) + return 1 if failures else 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Fetch Agent Feeds subscriptions") + parser.add_argument("--all", action="store_true", help="refresh every subscription") + parser.add_argument("--stream", help="refresh one subscription id") + parser.add_argument("--once", help="one-shot fetch for a subscription id") + parser.add_argument("--regenerate-catalog", action="store_true", help="regenerate catalog.md without polling") + parser.add_argument("--update-catalog", action="store_true", help="refresh the local catalog cache") + parser.add_argument("--root", type=Path, default=DEFAULT_ROOT, help="agentfeeds root directory") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + root = args.root.expanduser() + ensure_root(root) + + if args.update_catalog: + update_catalog_cache(root) + if args.regenerate_catalog: + regenerate_catalog(root) + return 0 + with fetch_lock(root) as acquired: + if not acquired: + print(f"agentfeeds-fetch already running for {root}; skipping", file=sys.stderr) + return 1 + return run_fetch(args, root) + + +if __name__ == "__main__": + raise SystemExit(main()) From 50a72e9b85381bbf14ef0841724b366c934e4822 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:04 -0700 Subject: [PATCH 77/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/polling/__init__.py --- .../scripts/lib/agentfeeds_runtime/polling/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/__init__.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/__init__.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/__init__.py new file mode 100644 index 00000000..d126ee63 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/__init__.py @@ -0,0 +1 @@ +"""Polling scheduler helpers for Agent Feeds.""" From b5ad37d58a9ae1f5d5f16cffa3f7c1be41c2095d Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:05 -0700 Subject: [PATCH 78/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/polling/uninstall.py --- .../agentfeeds_runtime/polling/uninstall.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py new file mode 100644 index 00000000..1b3e514d --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Uninstall background polling for Agent Feeds v0.3.""" + +from __future__ import annotations + +import os +import argparse +import platform +import subprocess +from pathlib import Path + + +LABEL = "dev.agentfeeds.fetch" +BEGIN_MARKER = "# BEGIN Agent Feeds polling" +END_MARKER = "# END Agent Feeds polling" + + +def uninstall_launchd() -> None: + plist_path = Path.home() / "Library" / "LaunchAgents" / f"{LABEL}.plist" + domain = f"gui/{os.getuid()}" + subprocess.run(["launchctl", "bootout", domain, str(plist_path)], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + if plist_path.exists(): + plist_path.unlink() + print(f"removed launchd polling: {plist_path}") + + +def current_crontab() -> str: + result = subprocess.run(["crontab", "-l"], check=False, text=True, capture_output=True) + return "" if result.returncode else result.stdout + + +def without_existing_block(text: str) -> str: + lines = text.splitlines() + output = [] + skipping = False + for line in lines: + if line == BEGIN_MARKER: + skipping = True + continue + if line == END_MARKER: + skipping = False + continue + if not skipping: + output.append(line) + return "\n".join(output).strip() + + +def uninstall_cron() -> None: + cleaned = without_existing_block(current_crontab()) + subprocess.run(["crontab", "-"], input=(cleaned + "\n") if cleaned else "", text=True, check=True) + print("removed cron polling") + + +def build_parser() -> argparse.ArgumentParser: + return argparse.ArgumentParser(description="Uninstall Agent Feeds background polling") + + +def main(argv: list[str] | None = None) -> int: + build_parser().parse_args(argv) + system = platform.system() + if system == "Darwin": + uninstall_launchd() + return 0 + if system in {"Linux", "FreeBSD"}: + uninstall_cron() + return 0 + print(f"unsupported platform for polling uninstaller: {system}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) From 3a56ed0dada14b57d4d55ec39032232d86258f71 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:05 -0700 Subject: [PATCH 79/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/polling/install.py --- .../lib/agentfeeds_runtime/polling/install.py | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py new file mode 100644 index 00000000..0b261149 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Install background polling for Agent Feeds v0.3.""" + +from __future__ import annotations + +import os +import argparse +import platform +import plistlib +import shlex +import shutil +import subprocess +from pathlib import Path + +try: + import yaml +except ImportError: # pragma: no cover + yaml = None + + +LABEL = "dev.agentfeeds.fetch" +BEGIN_MARKER = "# BEGIN Agent Feeds polling" +END_MARKER = "# END Agent Feeds polling" +DEFAULT_ROOT = Path.home() / ".agentfeeds" +MIN_INTERVAL_SECONDS = 300 +STABLE_PATH = f"{Path.home()}/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" + + +def load_subscriptions(root: Path) -> dict: + path = root / "subscriptions.yaml" + if not path.exists() or yaml is None: + return {} + return yaml.safe_load(path.read_text(encoding="utf-8")) or {} + + +def poll_interval_seconds(root: Path) -> int: + config = load_subscriptions(root) + defaults = config.get("defaults") or {} + intervals = [] + if defaults.get("poll_interval_seconds"): + intervals.append(int(defaults["poll_interval_seconds"])) + for subscription in config.get("subscriptions") or []: + if subscription.get("poll_interval_seconds"): + intervals.append(int(subscription["poll_interval_seconds"])) + return max(min(intervals or [600]), MIN_INTERVAL_SECONDS) + + +def fetcher_path() -> str: + venv_candidate = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "agentfeeds-fetch" + if venv_candidate.exists(): + return str(venv_candidate) + candidate = Path.home() / ".local" / "bin" / "agentfeeds-fetch" + if candidate.exists(): + return str(candidate) + command = shutil.which("agentfeeds-fetch") + if command: + return command + raise FileNotFoundError("agentfeeds-fetch not found; run `python3 scripts/setup.py` first") + + +def install_launchd(root: Path, interval: int, fetcher: str) -> None: + launch_agents = Path.home() / "Library" / "LaunchAgents" + launch_agents.mkdir(parents=True, exist_ok=True) + logs = root / "logs" + logs.mkdir(parents=True, exist_ok=True) + plist_path = launch_agents / f"{LABEL}.plist" + payload = { + "Label": LABEL, + "ProgramArguments": [fetcher, "--root", str(root), "--all"], + "StartInterval": interval, + "RunAtLoad": True, + "StandardOutPath": str(logs / "poll.out.log"), + "StandardErrorPath": str(logs / "poll.err.log"), + "EnvironmentVariables": { + "PATH": STABLE_PATH, + }, + } + plist_path.write_bytes(plistlib.dumps(payload)) + + domain = f"gui/{os.getuid()}" + subprocess.run(["launchctl", "bootout", domain, str(plist_path)], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.run(["launchctl", "bootstrap", domain, str(plist_path)], check=True) + subprocess.run(["launchctl", "enable", f"{domain}/{LABEL}"], check=False) + subprocess.run(["launchctl", "kickstart", "-k", f"{domain}/{LABEL}"], check=False) + print(f"installed launchd polling: {plist_path}") + print(f"interval: {interval} seconds") + print(f"logs: {logs}") + + +def current_crontab() -> str: + result = subprocess.run(["crontab", "-l"], check=False, text=True, capture_output=True) + return "" if result.returncode else result.stdout + + +def without_existing_block(text: str) -> str: + lines = text.splitlines() + output = [] + skipping = False + for line in lines: + if line == BEGIN_MARKER: + skipping = True + continue + if line == END_MARKER: + skipping = False + continue + if not skipping: + output.append(line) + return "\n".join(output).strip() + + +def install_cron(root: Path, interval: int, fetcher: str) -> None: + minutes = max(interval // 60, 5) + logs = root / "logs" + logs.mkdir(parents=True, exist_ok=True) + command = ( + f"*/{minutes} * * * * " + f"{shlex.quote(fetcher)} --root {shlex.quote(str(root))} --all " + f">> {shlex.quote(str(logs / 'poll.out.log'))} " + f"2>> {shlex.quote(str(logs / 'poll.err.log'))}" + ) + existing = without_existing_block(current_crontab()) + new_crontab = "\n".join(line for line in [existing, BEGIN_MARKER, command, END_MARKER, ""] if line) + subprocess.run(["crontab", "-"], input=new_crontab + "\n", text=True, check=True) + print(f"installed cron polling every {minutes} minutes") + print(f"logs: {logs}") + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Install Agent Feeds background polling") + parser.add_argument("--root", type=Path, default=DEFAULT_ROOT, help="agentfeeds root directory") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + root = args.root.expanduser() + root.mkdir(parents=True, exist_ok=True) + interval = poll_interval_seconds(root) + fetcher = fetcher_path() + system = platform.system() + if system == "Darwin": + install_launchd(root, interval, fetcher) + return 0 + if system in {"Linux", "FreeBSD"}: + install_cron(root, interval, fetcher) + return 0 + print(f"unsupported platform for polling installer: {system}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) From 5dd3ef91e0a8a7ee34b4db9fb409ba1cdafbd9f0 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:06 -0700 Subject: [PATCH 80/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/local_command.py --- .../adapters/local_command.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_command.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_command.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_command.py new file mode 100644 index 00000000..79bd820b --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_command.py @@ -0,0 +1,113 @@ +"""Local command adapter.""" + +from __future__ import annotations + +import json +import os +import signal +import subprocess +from pathlib import Path + +from agentfeeds_runtime.adapters.common import envelope, jmespath_search, now_utc, stable_hash +from agentfeeds_runtime.constants import COMMAND_MAX_OUTPUT_BYTES, COMMAND_TIMEOUT_SECONDS + + +def _decode_limited(raw: bytes, limit: int) -> tuple[str, bool]: + truncated = len(raw) > limit + return raw[:limit].decode("utf-8", errors="replace"), truncated + + +def run_local_command(stream: dict, adapter: dict) -> dict: + command = adapter.get("command") + if not isinstance(command, list) or not command or not all(isinstance(item, str) for item in command): + raise ValueError(f"{stream['id']}: local_command adapter.command must be a non-empty string array") + + timeout_seconds = int(adapter.get("timeout_seconds") or COMMAND_TIMEOUT_SECONDS) + max_output_bytes = int(adapter.get("max_output_bytes") or COMMAND_MAX_OUTPUT_BYTES) + cwd = adapter.get("cwd") + if cwd is not None: + cwd = str(Path(str(cwd)).expanduser()) + + started_at = now_utc() + process = subprocess.Popen( + command, + cwd=cwd, + env={key: os.environ[key] for key in ("HOME", "PATH", "USER", "SHELL") if key in os.environ}, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + start_new_session=os.name == "posix", + ) + timed_out = False + try: + stdout_raw, stderr_raw = process.communicate(timeout=timeout_seconds) + except subprocess.TimeoutExpired: + timed_out = True + if os.name == "posix": + os.killpg(process.pid, signal.SIGKILL) + else: # pragma: no cover - native Windows is not a supported polling target yet. + process.kill() + stdout_raw, stderr_raw = process.communicate() + ran_at = now_utc() + stdout, stdout_truncated = _decode_limited(stdout_raw, max_output_bytes) + stderr, stderr_truncated = _decode_limited(stderr_raw, max_output_bytes) + + parsed_json = None + transformed = None + if adapter.get("parse") == "json" and not timed_out: + parsed_json = json.loads(stdout or "null") + expression = adapter.get("transform", {}).get("expression") + transformed = jmespath_search(expression, parsed_json) if expression else parsed_json + + return { + "command": command, + "cwd": cwd, + "exit_code": process.returncode, + "timed_out": timed_out, + "stdout": stdout, + "stderr": stderr, + "stdout_truncated": stdout_truncated, + "stderr_truncated": stderr_truncated, + "parsed_json": parsed_json, + "transformed": transformed, + "started_at": started_at, + "ran_at": ran_at, + } + + +def fetch_local_command_events(stream: dict, adapter: dict, stream_uri: str, result: dict) -> list[dict]: + if adapter.get("parse") != "json": + raise ValueError(f"{stream['id']}: local_command event streams require parse: json") + + items_expression = adapter.get("items_from") or "@" + items = jmespath_search(items_expression, result["parsed_json"]) + if not isinstance(items, list): + raise ValueError(f"{stream['id']}: local_command items_from must produce an array") + + transform_expression = adapter.get("transform", {}).get("expression") + id_expression = adapter.get("id_from") + time_expression = adapter.get("time_from") + events = [] + for item in items: + transformed = jmespath_search(transform_expression, item) if transform_expression else item + if not isinstance(transformed, dict): + raise ValueError(f"{stream['id']}: local_command event transform must produce an object") + event_id = jmespath_search(id_expression, item) if id_expression else None + if event_id is None: + event_id = stable_hash(item) + event_time = jmespath_search(time_expression, item) if time_expression else None + if event_time is None: + event_time = result["ran_at"] + events.append(envelope(stream, stream_uri, event_id, transformed, str(event_time) if event_time else None)) + return events + + +def fetch_local_command(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + result = run_local_command(stream, adapter) + + if stream["mode"] == "event": + return fetch_local_command_events(stream, adapter, stream_uri, result) + if stream["mode"] != "snapshot": + raise ValueError(f"{stream['id']}: local_command supports snapshot and event modes") + + data = result + return [envelope(stream, stream_uri, stable_hash(data), data, result["ran_at"])] From 5ca4d4b0834b3a331c57128ffc616d978774df6e Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:07 -0700 Subject: [PATCH 81/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/rss.py --- .../lib/agentfeeds_runtime/adapters/rss.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/rss.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/rss.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/rss.py new file mode 100644 index 00000000..2da2ccfc --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/rss.py @@ -0,0 +1,25 @@ +"""RSS and Atom adapter.""" + +from __future__ import annotations + +import feedparser + +from agentfeeds_runtime.adapters.common import envelope, stable_hash + + +def fetch_rss(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + parsed = feedparser.parse(adapter["url"]) + if parsed.bozo: + raise ValueError(f"{stream['id']}: failed to parse RSS feed: {parsed.bozo_exception}") + events = [] + for entry in parsed.entries: + data = { + "title": entry.get("title", ""), + "link": entry.get("link"), + "summary": entry.get("summary"), + "published": entry.get("published"), + "id": entry.get("id") or entry.get("guid") or entry.get("link"), + } + event_id = data["id"] or stable_hash(data) + events.append(envelope(stream, stream_uri, event_id, data)) + return events From 15d8fe805dfccd30308bf13bd6bc42c1d2e53113 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:08 -0700 Subject: [PATCH 82/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/ical.py --- .../lib/agentfeeds_runtime/adapters/ical.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/ical.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/ical.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/ical.py new file mode 100644 index 00000000..c6055591 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/ical.py @@ -0,0 +1,35 @@ +"""iCalendar adapter.""" + +from __future__ import annotations + +import icalendar +import requests + +from agentfeeds_runtime.adapters.common import envelope, stable_hash +from agentfeeds_runtime.constants import REQUEST_TIMEOUT_SECONDS + + +def serialize_ical_value(value: object) -> str | None: + if value is None: + return None + decoded = getattr(value, "dt", value) + if hasattr(decoded, "isoformat"): + return decoded.isoformat() + return str(decoded) + + +def fetch_ical(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + response = requests.get(adapter["url"], timeout=REQUEST_TIMEOUT_SECONDS) + response.raise_for_status() + calendar = icalendar.Calendar.from_ical(response.content) + events = [] + for component in calendar.walk("VEVENT"): + data = { + "uid": str(component.get("uid", "")), + "summary": str(component.get("summary", "")), + "starts_at": serialize_ical_value(component.get("dtstart")), + "ends_at": serialize_ical_value(component.get("dtend")), + "location": str(component.get("location")) if component.get("location") else None, + } + events.append(envelope(stream, stream_uri, data["uid"] or stable_hash(data), data)) + return events From ed93c890ea23a9cfa13b63f811d0d4f74509ca75 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:09 -0700 Subject: [PATCH 83/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/__init__.py --- .../agentfeeds_runtime/adapters/__init__.py | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/__init__.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/__init__.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/__init__.py new file mode 100644 index 00000000..01220b8b --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/__init__.py @@ -0,0 +1,41 @@ +"""Adapter registry for Agent Feeds streams.""" + +from __future__ import annotations + +from agentfeeds_runtime.adapters.http import fetch_json +from agentfeeds_runtime.adapters.ical import fetch_ical +from agentfeeds_runtime.adapters.local_sources import fetch_filesystem_scan, fetch_git_status, fetch_markdown_scan +from agentfeeds_runtime.adapters.local_command import fetch_local_command +from agentfeeds_runtime.adapters.local_file import fetch_local_file +from agentfeeds_runtime.adapters.mac_native import fetch_apple_automation, fetch_plist_reading_list, fetch_sqlite_query +from agentfeeds_runtime.adapters.rss import fetch_rss + + +def run_adapter(stream: dict, parameters: dict, *, validate_parameters, source_uri_for, substitute) -> tuple[str, list[dict]]: + validate_parameters(stream, parameters) + stream_uri = source_uri_for(stream, parameters) + adapter = substitute(stream["adapter"], parameters) + kind = adapter["kind"] + if kind in {"json_http", "paginated_json_http"}: + return stream_uri, fetch_json(stream, adapter, stream_uri) + if kind == "rss": + return stream_uri, fetch_rss(stream, adapter, stream_uri) + if kind == "ical": + return stream_uri, fetch_ical(stream, adapter, stream_uri) + if kind == "local_file": + return stream_uri, fetch_local_file(stream, adapter, stream_uri) + if kind == "filesystem_scan": + return stream_uri, fetch_filesystem_scan(stream, adapter, stream_uri) + if kind == "markdown_scan": + return stream_uri, fetch_markdown_scan(stream, adapter, stream_uri) + if kind == "git_status": + return stream_uri, fetch_git_status(stream, adapter, stream_uri) + if kind == "local_command": + return stream_uri, fetch_local_command(stream, adapter, stream_uri) + if kind == "apple_automation": + return stream_uri, fetch_apple_automation(stream, adapter, stream_uri) + if kind == "sqlite_query": + return stream_uri, fetch_sqlite_query(stream, adapter, stream_uri) + if kind == "plist_reading_list": + return stream_uri, fetch_plist_reading_list(stream, adapter, stream_uri) + raise ValueError(f"unsupported adapter kind: {kind}") From 4c53724b9d70bcda211333b8a3cdd3520ab468d9 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:10 -0700 Subject: [PATCH 84/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/common.py --- .../lib/agentfeeds_runtime/adapters/common.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/common.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/common.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/common.py new file mode 100644 index 00000000..723aa01f --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/common.py @@ -0,0 +1,40 @@ +"""Shared adapter helpers.""" + +from __future__ import annotations + +import hashlib +import json +from datetime import UTC, datetime + +import jmespath + +from agentfeeds_runtime.constants import AGENTFEEDS_VERSION + + +def now_utc() -> str: + return datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + + +def stable_hash(value: object) -> str: + encoded = json.dumps(value, sort_keys=True, default=str).encode("utf-8") + return hashlib.sha256(encoded).hexdigest()[:16] + + +def jmespath_search(expression: str | None, document: object) -> object: + if not expression: + return None + return jmespath.search(expression, document) + + +def envelope(stream: dict, stream_uri: str, event_id: object, data: dict, event_time: str | None = None) -> dict: + return { + "specversion": AGENTFEEDS_VERSION, + "id": str(event_id or stable_hash(data)), + "source": stream_uri, + "type": stream["type"], + "time": event_time or now_utc(), + "schema_url": stream["schema_url"], + "schema_version": stream["schema_version"], + "mode": stream["mode"], + "data": data, + } From 1805a35a8f5ed9b3a14a36d0e6ffc5b7a1786146 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:10 -0700 Subject: [PATCH 85/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/http.py --- .../lib/agentfeeds_runtime/adapters/http.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/http.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/http.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/http.py new file mode 100644 index 00000000..8239b50c --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/http.py @@ -0,0 +1,38 @@ +"""HTTP JSON adapters.""" + +from __future__ import annotations + +import requests + +from agentfeeds_runtime.adapters.common import envelope, jmespath_search, stable_hash +from agentfeeds_runtime.constants import REQUEST_TIMEOUT_SECONDS + + +def fetch_json(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + response = requests.request( + adapter.get("method", "GET"), + adapter["url"], + headers=adapter.get("headers") or {}, + json=adapter.get("body"), + timeout=REQUEST_TIMEOUT_SECONDS, + ) + response.raise_for_status() + raw = response.json() + expression = adapter.get("transform", {}).get("expression") + transformed = jmespath_search(expression, raw) if expression else raw + + if adapter["kind"] == "json_http" and stream["mode"] == "snapshot": + if not isinstance(transformed, dict): + raise ValueError(f"{stream['id']}: json_http transform must produce an object") + event_id = jmespath_search(adapter.get("id_from"), raw) or stable_hash(transformed) + return [envelope(stream, stream_uri, event_id, transformed)] + + if not isinstance(transformed, list): + raise ValueError(f"{stream['id']}: {adapter['kind']} event transform must produce an array") + events = [] + for item in transformed: + if not isinstance(item, dict): + raise ValueError(f"{stream['id']}: {adapter['kind']} event items must be objects") + event_id = jmespath_search(adapter.get("id_from"), item) or stable_hash(item) + events.append(envelope(stream, stream_uri, event_id, item)) + return events From 0c31f237f0ac0510aa54cf4d6154ab4b63d05c5f Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:11 -0700 Subject: [PATCH 86/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/local_sources.py --- .../adapters/local_sources.py | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_sources.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_sources.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_sources.py new file mode 100644 index 00000000..f1a7b195 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_sources.py @@ -0,0 +1,125 @@ +"""Local filesystem and repository adapters.""" + +from __future__ import annotations + +import subprocess +from datetime import UTC, datetime +from pathlib import Path + +from agentfeeds_runtime.adapters.common import envelope, stable_hash + + +def _iso_mtime(path: Path) -> str: + return datetime.fromtimestamp(path.stat().st_mtime, UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + + +def _resolve_path(value: str) -> Path: + path = Path(value).expanduser() + return path if path.is_absolute() else path.resolve() + + +def _entry_kind(path: Path) -> str: + if path.is_symlink(): + return "symlink" + if path.is_file(): + return "file" + if path.is_dir(): + return "directory" + return "other" + + +def _directory_entry(path: Path) -> dict: + stat = path.stat() + return { + "path": str(path), + "name": path.name, + "kind": _entry_kind(path), + "extension": path.suffix.lstrip(".") or None, + "size_bytes": stat.st_size if path.is_file() else None, + "modified_at": _iso_mtime(path), + } + + +def fetch_filesystem_scan(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + root = _resolve_path(adapter["path"]) + if not root.exists(): + raise FileNotFoundError(f"{stream['id']}: local directory not found: {root}") + if not root.is_dir(): + raise ValueError(f"{stream['id']}: local path is not a directory: {root}") + limit = int(adapter.get("limit") or 25) + entries = [_directory_entry(path) for path in root.iterdir()] + entries.sort(key=lambda item: item["modified_at"], reverse=True) + return [envelope(stream, stream_uri, stable_hash(item), item, item["modified_at"]) for item in entries[:limit]] + + +def _parse_frontmatter(text: str) -> tuple[dict, str]: + if not text.startswith("---\n"): + return {}, text + end = text.find("\n---\n", 4) + if end == -1: + return {}, text + raw = text[4:end] + body = text[end + 5 :] + data = {} + for line in raw.splitlines(): + if ":" not in line: + continue + key, value = line.split(":", 1) + data[key.strip()] = value.strip().strip("\"'") + return data, body + + +def fetch_markdown_scan(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + root = _resolve_path(adapter["path"]) + if not root.exists(): + raise FileNotFoundError(f"{stream['id']}: markdown vault not found: {root}") + if not root.is_dir(): + raise ValueError(f"{stream['id']}: markdown vault path is not a directory: {root}") + limit = int(adapter.get("limit") or 25) + docs = [] + for path in root.rglob("*.md"): + text = path.read_text(encoding="utf-8", errors="replace") + frontmatter, body = _parse_frontmatter(text) if adapter.get("parse_frontmatter") else ({}, text) + title = frontmatter.get("title") or next((line.lstrip("# ").strip() for line in body.splitlines() if line.strip()), path.stem) + snippet = " ".join(line.strip() for line in body.splitlines() if line.strip())[:500] + tags = frontmatter.get("tags") or "" + if isinstance(tags, str): + tags = [item.strip() for item in tags.strip("[]").split(",") if item.strip()] + item = { + "path": str(path), + "title": title, + "snippet": snippet, + "modified_at": _iso_mtime(path), + "frontmatter": frontmatter, + "tags": tags, + } + docs.append(item) + docs.sort(key=lambda item: item["modified_at"], reverse=True) + return [envelope(stream, stream_uri, stable_hash(item), item, item["modified_at"]) for item in docs[:limit]] + + +def _git(repo: Path, *args: str) -> str: + return subprocess.run(["git", "-C", str(repo), *args], check=False, text=True, capture_output=True).stdout.strip() + + +def fetch_git_status(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + repo = _resolve_path(adapter["path"]) + if not (repo / ".git").exists(): + raise ValueError(f"{stream['id']}: path is not a git repository: {repo}") + branch = _git(repo, "branch", "--show-current") or "HEAD" + dirty_files = [line[3:] for line in _git(repo, "status", "--porcelain").splitlines() if line] + ahead = behind = 0 + upstream = _git(repo, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}") + if upstream: + counts = _git(repo, "rev-list", "--left-right", "--count", f"{upstream}...HEAD").split() + if len(counts) == 2: + behind, ahead = int(counts[0]), int(counts[1]) + data = { + "path": str(repo), + "branch": branch, + "clean": not dirty_files, + "dirty_files": dirty_files, + "ahead": ahead, + "behind": behind, + } + return [envelope(stream, stream_uri, stable_hash(data), data)] From a90757da7e6636d55fe5dcaa75fd5c1ac33343e9 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:12 -0700 Subject: [PATCH 87/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/mac_native.py --- .../agentfeeds_runtime/adapters/mac_native.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/mac_native.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/mac_native.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/mac_native.py new file mode 100644 index 00000000..7a9d7263 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/mac_native.py @@ -0,0 +1,158 @@ +"""macOS native read-only adapters.""" + +from __future__ import annotations + +import platform +import plistlib +import sqlite3 +import subprocess +from datetime import UTC, datetime, timedelta +from pathlib import Path + +from agentfeeds_runtime.adapters.common import envelope, stable_hash + + +def _require_macos(stream: dict) -> None: + if platform.system() != "Darwin": + raise RuntimeError(f"{stream['id']}: this template requires macOS") + + +def _osascript(stream: dict, script: str) -> str: + _require_macos(stream) + result = subprocess.run(["osascript", "-e", script], check=False, text=True, capture_output=True, timeout=30) + if result.returncode: + raise RuntimeError(f"{stream['id']}: osascript failed: {result.stderr.strip()}") + return result.stdout + + +def _rows(output: str, min_parts: int) -> list[list[str]]: + rows = [] + for line in output.splitlines(): + parts = line.split("\t") + if len(parts) >= min_parts: + rows.append(parts) + return rows + + +def _convert(value: object, value_type: str | None) -> object: + if value == "": + return None + if value_type == "boolean": + return str(value).lower() == "true" + if value_type == "integer": + try: + return int(value) + except (TypeError, ValueError): + return None + if value_type == "number": + try: + return float(value) + except (TypeError, ValueError): + return None + return value + + +def _column_name(column: object) -> str: + return column["name"] if isinstance(column, dict) else str(column) + + +def _column_type(column: object) -> str | None: + return column.get("type") if isinstance(column, dict) else None + + +def _row_data(columns: list[object], values: list[object], static: dict | None = None) -> dict: + data = dict(static or {}) + for column, value in zip(columns, values, strict=False): + data[_column_name(column)] = _convert(value, _column_type(column)) + return data + + +def fetch_apple_automation(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + columns = adapter.get("columns") or [] + if not columns: + raise ValueError(f"{stream['id']}: adapter.columns is required for apple_automation") + script = adapter["script"] + rows = _rows(_osascript(stream, script), len(columns)) + id_column = adapter.get("id_column") + time_column = adapter.get("time_column") + static = adapter.get("static") or {} + events = [] + for row in rows: + data = _row_data(columns, row, static) + event_id = data.get(id_column) if id_column else None + event_time = data.get(time_column) if time_column else None + events.append(envelope(stream, stream_uri, event_id or stable_hash(data), data, event_time)) + return events + + +def _mac_absolute_epoch(value: float) -> str: + dt = datetime(2001, 1, 1, tzinfo=UTC) + timedelta(seconds=float(value)) + return dt.replace(microsecond=0).isoformat().replace("+00:00", "Z") + + +def _walk_reading_list(node: object) -> list[dict]: + items = [] + if isinstance(node, dict): + uri = node.get("URLString") + extra = node.get("ReadingList") or {} + if uri and extra: + items.append( + { + "title": node.get("URIDictionary", {}).get("title") or uri, + "url": uri, + "added_at": _mac_absolute_epoch(extra["DateAdded"]) if extra.get("DateAdded") else None, + "preview_text": extra.get("PreviewText"), + } + ) + for value in node.values(): + items.extend(_walk_reading_list(value)) + elif isinstance(node, list): + for value in node: + items.extend(_walk_reading_list(value)) + return items + + +def fetch_plist_reading_list(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + path = Path(adapter["path"]).expanduser() + if not path.exists(): + raise FileNotFoundError(f"{stream['id']}: Safari bookmarks file not found: {path}") + payload = plistlib.loads(path.read_bytes()) + items = _walk_reading_list(payload) + items.sort(key=lambda item: item.get("added_at") or "", reverse=True) + limit = int(adapter.get("limit") or 50) + return [envelope(stream, stream_uri, item["url"], item, item.get("added_at")) for item in items[:limit]] + + +def _convert_sqlite_row(adapter: dict, columns: list[object], row: tuple) -> dict: + data = _row_data(columns, list(row), adapter.get("static") or {}) + for column, encoding in (adapter.get("timestamp_columns") or {}).items(): + if encoding == "mac_absolute_ns" and data.get(column) is not None: + data[column] = _mac_absolute_epoch(float(data[column]) / 1_000_000_000) + elif encoding == "mac_absolute_seconds" and data.get(column) is not None: + data[column] = _mac_absolute_epoch(float(data[column])) + return data + + +def fetch_sqlite_query(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + if adapter.get("tcc_permission"): + _require_macos(stream) + path = Path(adapter["database"]).expanduser() + if not path.exists(): + raise FileNotFoundError(f"{stream['id']}: SQLite database not found: {path}") + columns = adapter.get("columns") or [] + if not columns: + raise ValueError(f"{stream['id']}: adapter.columns is required for sqlite_query") + connection = sqlite3.connect(f"file:{path}?mode=ro", uri=True) + try: + rows = connection.execute(adapter["query"], adapter.get("params") or []).fetchall() + finally: + connection.close() + id_column = adapter.get("id_column") + time_column = adapter.get("time_column") + events = [] + for row in rows: + data = _convert_sqlite_row(adapter, columns, row) + event_id = data.get(id_column) if id_column else None + event_time = data.get(time_column) if time_column else None + events.append(envelope(stream, stream_uri, event_id or stable_hash(data), data, event_time)) + return events From b0e13ca3b20cad76e506999cec98a62498538eee Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:13 -0700 Subject: [PATCH 88/92] Add agentfeeds skill: scripts/lib/agentfeeds_runtime/adapters/local_file.py --- .../agentfeeds_runtime/adapters/local_file.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_file.py diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_file.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_file.py new file mode 100644 index 00000000..fe8e8b21 --- /dev/null +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/adapters/local_file.py @@ -0,0 +1,33 @@ +"""Local file adapter.""" + +from __future__ import annotations + +import hashlib +from datetime import UTC, datetime +from pathlib import Path + +from agentfeeds_runtime.adapters.common import envelope + + +def fetch_local_file(stream: dict, adapter: dict, stream_uri: str) -> list[dict]: + path = Path(adapter["path"]).expanduser() + if not path.is_absolute(): + path = path.resolve() + if not path.exists(): + raise FileNotFoundError(f"{stream['id']}: local file not found: {path}") + if not path.is_file(): + raise ValueError(f"{stream['id']}: local path is not a file: {path}") + + raw = path.read_bytes() + stat = path.stat() + modified_at = datetime.fromtimestamp(stat.st_mtime, UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + data = { + "path": str(path), + "name": path.name, + "extension": path.suffix.lstrip("."), + "content": raw.decode("utf-8", errors="replace"), + "size_bytes": stat.st_size, + "sha256": hashlib.sha256(raw).hexdigest(), + "modified_at": modified_at, + } + return [envelope(stream, stream_uri, data["sha256"], data, modified_at)] From 046a18b96fd6d05ac35697e24a83ebc4f594fed4 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:43:14 -0700 Subject: [PATCH 89/92] Add agentfeeds skill: assets/agentfeeds-demo.gif --- skills/agentfeeds/assets/agentfeeds-demo.gif | Bin 0 -> 242403 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 skills/agentfeeds/assets/agentfeeds-demo.gif diff --git a/skills/agentfeeds/assets/agentfeeds-demo.gif b/skills/agentfeeds/assets/agentfeeds-demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..47eb7b51120d8c5770e72186c554fb69516da321 GIT binary patch literal 242403 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

!NA+??IcaHPnFG|7o1$#_J`jAY4=bjgkX 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 RGgxsJukj7%aBB$&06Vx61>XPw literal 0 HcmV?d00001 From 184fb8468ea2550145f808d0d07cd1b057530b84 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 15:45:19 -0700 Subject: [PATCH 90/92] Clean agentfeeds skill bundle --- skills/agentfeeds/SKILL.md | 8 +- .../references/macos-personal-sources.md | 56 +++--- skills/agentfeeds/references/publishing.md | 34 ---- skills/agentfeeds/scripts/agentfeeds.py | 0 skills/agentfeeds/scripts/agentfeeds_fetch.py | 0 skills/agentfeeds/scripts/evals/run_evals.py | 166 ------------------ .../scripts/lib/agentfeeds_runtime/fetcher.py | 0 .../lib/agentfeeds_runtime/polling/install.py | 0 .../agentfeeds_runtime/polling/uninstall.py | 0 skills/agentfeeds/scripts/polling/install.py | 0 .../agentfeeds/scripts/polling/uninstall.py | 0 skills/agentfeeds/scripts/setup.py | 0 12 files changed, 31 insertions(+), 233 deletions(-) delete mode 100644 skills/agentfeeds/references/publishing.md mode change 100644 => 100755 skills/agentfeeds/scripts/agentfeeds.py mode change 100644 => 100755 skills/agentfeeds/scripts/agentfeeds_fetch.py delete mode 100644 skills/agentfeeds/scripts/evals/run_evals.py mode change 100644 => 100755 skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py mode change 100644 => 100755 skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py mode change 100644 => 100755 skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py mode change 100644 => 100755 skills/agentfeeds/scripts/polling/install.py mode change 100644 => 100755 skills/agentfeeds/scripts/polling/uninstall.py mode change 100644 => 100755 skills/agentfeeds/scripts/setup.py diff --git a/skills/agentfeeds/SKILL.md b/skills/agentfeeds/SKILL.md index 107cde27..5d9ce1bb 100644 --- a/skills/agentfeeds/SKILL.md +++ b/skills/agentfeeds/SKILL.md @@ -125,13 +125,7 @@ For `local_command` templates, use argv arrays only. Only create command templat After scaffolding or installing a `local_command` template, tell the user to run `python3 scripts/agentfeeds.py admin templates approve-command [key=value ...]` themselves in an interactive terminal. Do not approve on the user's behalf, even if asked. Explain that approval is tied to the exact template and command digest, and edits revoke it. -For macOS personal context, install local templates with: - -```bash -python3 scripts/agentfeeds.py admin macos install-templates -``` - -This installs pending read-only templates for Calendar, Reminders, and Mail. The operator must approve each one before subscribing or refreshing. +For macOS personal context, prefer the built-in `mac/*` templates from the bundled catalog. They do not require local-command approval, but macOS may ask the user to grant Calendar, Reminders, Automation, or Full Disk Access permissions on first refresh. ## Safety Rules diff --git a/skills/agentfeeds/references/macos-personal-sources.md b/skills/agentfeeds/references/macos-personal-sources.md index 691ef80f..26bf71ed 100644 --- a/skills/agentfeeds/references/macos-personal-sources.md +++ b/skills/agentfeeds/references/macos-personal-sources.md @@ -1,43 +1,36 @@ # macOS Personal Sources -Use this reference when the user wants local personal context from macOS apps such as Calendar, Reminders, or Mail. +Use this reference when the user wants local personal context from macOS apps such as Calendar, Reminders, Notes, Mail, Messages, Safari, Finder, or local folders. -These sources are local `local_command` templates. They read from macOS apps through AppleScript, may trigger macOS Automation or app-data permission prompts on first refresh, and are pending until the operator approves each command. +The public catalog includes built-in `mac/*` templates. Prefer these before creating operator-local templates. -## Install Templates +## Discover -Install the local templates: +Find available Mac templates: ```bash -python3 scripts/agentfeeds.py admin macos install-templates --json +python3 scripts/agentfeeds.py templates find mac ``` -This creates: +Useful built-ins include: -- `macos/calendar-today`: today's local Calendar events -- `macos/reminders-open`: incomplete Reminders items -- `macos/mail-inbox-recent`: recent Mail inbox messages - -## Approval - -For each source the user wants, tell the user to run the approval command in an interactive terminal: - -```bash -python3 scripts/agentfeeds.py admin templates approve-command macos/calendar-today -python3 scripts/agentfeeds.py admin templates approve-command macos/reminders-open -python3 scripts/agentfeeds.py admin templates approve-command macos/mail-inbox-recent -``` - -Do not approve on the user's behalf. Approval prints the exact command and requires typing `APPROVE`. +- `mac/calendar-today`: today's Calendar.app agenda +- `mac/calendar-upcoming`: next 7 days of Calendar.app events +- `mac/reminders-pending`: pending Reminders.app items +- `mac/notes-recent`: recently modified Notes.app notes +- `mac/mail-unread`: unread Mail.app messages +- `mac/imessage-unread`: unread iMessage conversations +- `mac/safari-reading-list`: Safari Reading List items +- `mac/finder-recent-downloads`: recent items in Downloads ## Subscribe -After approval, subscribe only the sources the user chose: +Subscribe only the sources the user asks for: ```bash -python3 scripts/agentfeeds.py subscribe macos/calendar-today --title "Calendar today" -python3 scripts/agentfeeds.py subscribe macos/reminders-open --title "Open reminders" -python3 scripts/agentfeeds.py subscribe macos/mail-inbox-recent --title "Recent inbox mail" +python3 scripts/agentfeeds.py subscribe mac/calendar-today +python3 scripts/agentfeeds.py subscribe mac/reminders-pending +python3 scripts/agentfeeds.py subscribe mac/mail-unread ``` Then check health: @@ -46,7 +39,18 @@ Then check health: python3 scripts/agentfeeds.py streams health --json ``` -If a source fails with a macOS permission error, tell the user to grant the requested Automation/app permission in System Settings, then refresh that one stream: +## Permissions + +Mac templates are read-only, but the host process may need user-granted macOS permissions: + +- Calendar templates may require Calendar permission. +- Reminders templates may require Reminders permission. +- Notes and Mail templates may require Automation permission for the relevant app. +- iMessage reads `~/Library/Messages/chat.db` and may require Full Disk Access. +- Safari Reading List reads `~/Library/Safari/Bookmarks.plist`. +- Finder Downloads reads `~/Downloads`. + +If a source fails with a macOS permission error, tell the user which permission is needed, then refresh that one stream: ```bash python3 scripts/agentfeeds.py refresh --stream diff --git a/skills/agentfeeds/references/publishing.md b/skills/agentfeeds/references/publishing.md deleted file mode 100644 index 942c24c9..00000000 --- a/skills/agentfeeds/references/publishing.md +++ /dev/null @@ -1,34 +0,0 @@ -# Publishing - -This repository includes development files for tests, docs, packaging, and GitHub presentation. A Skills Hub release should publish a clean skill bundle instead of the raw repo. - -Build the publishable bundle from the repo root: - -```bash -python3 scripts/bundle/build_skill_bundle.py -``` - -The bundle should include: - -```text -SKILL.md -agents/ -assets/ -references/ -scripts/ -LICENSE -``` - -The bundle should exclude: - -```text -README.md -docs/ -tests/ -dist/ -build/ -*.egg-info/ -__pycache__/ -.pytest_cache/ -.venv/ -``` diff --git a/skills/agentfeeds/scripts/agentfeeds.py b/skills/agentfeeds/scripts/agentfeeds.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/agentfeeds_fetch.py b/skills/agentfeeds/scripts/agentfeeds_fetch.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/evals/run_evals.py b/skills/agentfeeds/scripts/evals/run_evals.py deleted file mode 100644 index f42a29a3..00000000 --- a/skills/agentfeeds/scripts/evals/run_evals.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3 -"""Run AgentSkills-style eval cases with Codex CLI.""" - -from __future__ import annotations - -import argparse -from concurrent.futures import ThreadPoolExecutor, as_completed -import json -import shutil -import subprocess -import time -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -DEFAULT_EVALS = ROOT / "evals" / "evals.json" -DEFAULT_WORKSPACE = ROOT.parent / "agentfeeds-eval-workspace" - - -def slug(value: str) -> str: - return "".join(ch if ch.isalnum() else "-" for ch in value.lower()).strip("-") - - -def load_evals(path: Path) -> dict: - data = json.loads(path.read_text(encoding="utf-8")) - if data.get("skill_name") != "agentfeeds": - raise SystemExit(f"unexpected skill_name in {path}: {data.get('skill_name')}") - for item in data.get("evals") or []: - for file in item.get("files") or []: - if not (ROOT / file).exists(): - raise SystemExit(f"missing eval input file: {file}") - return data - - -def write_json(path: Path, payload: object) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") - - -def build_prompt(case: dict, output_dir: Path, *, with_skill: bool) -> str: - files = case.get("files") or [] - skill_line = f"- Skill path: {ROOT}" if with_skill else "- Skill path: none; answer without using the Agent Feeds skill instructions." - file_lines = "\n".join(f" - {ROOT / file}" for file in files) or " - none" - assertions = "\n".join(f" - {item}" for item in case.get("assertions") or []) - return f"""Execute this eval task in a clean context. - -{skill_line} -- Task: {case["prompt"]} -- Input files: -{file_lines} -- Save any produced files to: {output_dir} - -Expected output: -{case["expected_output"]} - -Assertions to satisfy: -{assertions} - -Keep the final answer concise and include the commands you used when relevant. -Do not edit files in the skill repository. Put all artifacts, temporary Agent Feeds roots, draft templates, reports, logs, and generated files under the output directory above. -""" - - -def run_case(case: dict, target: Path, *, with_skill: bool, model: str | None) -> dict: - outputs = target / "outputs" - outputs.mkdir(parents=True, exist_ok=True) - prompt = build_prompt(case, outputs, with_skill=with_skill) - prompt_path = target / "prompt.txt" - output_path = target / "output.txt" - prompt_path.write_text(prompt, encoding="utf-8") - - command = [ - "codex", - "exec", - "--cd", - str(ROOT), - "--sandbox", - "danger-full-access", - "--dangerously-bypass-approvals-and-sandbox", - "--ephemeral", - "--output-last-message", - str(output_path), - ] - if model: - command.extend(["--model", model]) - command.append(prompt) - - started = time.monotonic() - result = subprocess.run(command, text=True, capture_output=True, check=False) - duration_ms = round((time.monotonic() - started) * 1000) - (target / "stdout.log").write_text(result.stdout, encoding="utf-8") - (target / "stderr.log").write_text(result.stderr, encoding="utf-8") - timing = {"duration_ms": duration_ms, "returncode": result.returncode} - write_json(target / "timing.json", timing) - return timing - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="Run Agent Feeds eval cases") - parser.add_argument("--evals", type=Path, default=DEFAULT_EVALS) - parser.add_argument("--workspace", type=Path, default=DEFAULT_WORKSPACE) - parser.add_argument("--iteration", default="iteration-1") - parser.add_argument("--case", action="append", help="run only this eval id; repeatable") - parser.add_argument("--with-skill-only", action="store_true", help="skip baseline runs") - parser.add_argument("--model", help="optional Codex model override") - parser.add_argument("--jobs", type=int, default=6, help="parallel model runs when --execute is set") - parser.add_argument("--execute", action="store_true", help="actually run Codex model calls") - return parser - - -def main() -> int: - args = build_parser().parse_args() - data = load_evals(args.evals) - selected = set(args.case or []) - cases = [case for case in data["evals"] if not selected or case["id"] in selected] - if selected and len(cases) != len(selected): - found = {case["id"] for case in cases} - raise SystemExit(f"unknown eval case(s): {', '.join(sorted(selected - found))}") - - iteration = args.workspace / args.iteration - plan = [] - for case in cases: - case_dir = iteration / f"eval-{slug(case['id'])}" - targets = [("with_skill", True)] - if not args.with_skill_only: - targets.append(("without_skill", False)) - for name, with_skill in targets: - target = case_dir / name - plan.append((case, target, with_skill)) - - print(f"workspace: {iteration}") - print(f"planned runs: {len(plan)}") - for case, target, with_skill in plan: - print(f"- {case['id']} -> {target.relative_to(args.workspace)}") - if not args.execute: - target.mkdir(parents=True, exist_ok=True) - (target / "prompt.txt").write_text(build_prompt(case, target / "outputs", with_skill=with_skill), encoding="utf-8") - - if not args.execute: - print("dry run only; pass --execute to spend model calls") - return 0 - - if not shutil.which("codex"): - raise SystemExit("codex CLI not found on PATH") - - jobs = max(1, args.jobs) - results = [] - with ThreadPoolExecutor(max_workers=jobs) as pool: - futures = {} - for case, target, with_skill in plan: - label = "with_skill" if with_skill else "without_skill" - print(f"starting {case['id']} ({label})") - future = pool.submit(run_case, case, target, with_skill=with_skill, model=args.model) - futures[future] = (case, target, label) - for future in as_completed(futures): - case, target, label = futures[future] - timing = future.result() - print(f"finished {case['id']} ({label}) returncode={timing['returncode']} duration_ms={timing['duration_ms']}") - results.append({"id": case["id"], "variant": label, "target": str(target), **timing}) - results.sort(key=lambda item: (item["id"], item["variant"])) - write_json(iteration / "run-summary.json", {"runs": results}) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/fetcher.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/install.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/polling/uninstall.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/polling/install.py b/skills/agentfeeds/scripts/polling/install.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/polling/uninstall.py b/skills/agentfeeds/scripts/polling/uninstall.py old mode 100644 new mode 100755 diff --git a/skills/agentfeeds/scripts/setup.py b/skills/agentfeeds/scripts/setup.py old mode 100644 new mode 100755 From a324c4bd540a35970c4097b6a3a53629cfa8a1e3 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 19:07:12 -0700 Subject: [PATCH 91/92] Remove duplicate agentfeeds macOS scripts --- .../references/template-authoring.md | 8 +- .../lib/agentfeeds_runtime/commands.py | 133 ------------------ .../scripts/macos/calendar_today.py | 68 --------- .../scripts/macos/mail_inbox_recent.py | 75 ---------- .../scripts/macos/reminders_open.py | 66 --------- 5 files changed, 1 insertion(+), 349 deletions(-) delete mode 100644 skills/agentfeeds/scripts/macos/calendar_today.py delete mode 100644 skills/agentfeeds/scripts/macos/mail_inbox_recent.py delete mode 100644 skills/agentfeeds/scripts/macos/reminders_open.py diff --git a/skills/agentfeeds/references/template-authoring.md b/skills/agentfeeds/references/template-authoring.md index 54bf8b82..727f4b2f 100644 --- a/skills/agentfeeds/references/template-authoring.md +++ b/skills/agentfeeds/references/template-authoring.md @@ -68,10 +68,4 @@ python3 scripts/agentfeeds.py admin secrets set github_token On macOS this uses Keychain when available; other platforms fall back to a local 0600 secret file under the Agent Feeds root. -macOS local templates can be installed with: - -```bash -python3 scripts/agentfeeds.py admin macos install-templates -``` - -This writes pending Calendar, Reminders, and Mail templates. Approve only the ones the user wants enabled. +macOS personal sources are built-in catalog templates under `mac/*`. Prefer those before creating operator-local templates. diff --git a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py index cccd0fd2..8b066983 100644 --- a/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py +++ b/skills/agentfeeds/scripts/lib/agentfeeds_runtime/commands.py @@ -1502,132 +1502,6 @@ def cmd_secrets_list(args: argparse.Namespace) -> int: return 0 -MACOS_EVENT_SCHEMA = { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agentfeeds.dev/schemas/macos.item.v1.json", - "title": "macOS Item", - "type": "object", - "required": ["title"], - "properties": { - "title": {"type": "string"}, - "content": {"type": ["string", "null"]}, - "source": {"type": ["string", "null"]}, - "url": {"type": ["string", "null"]}, - "starts_at": {"type": ["string", "null"]}, - "ends_at": {"type": ["string", "null"]}, - "updated_at": {"type": ["string", "null"]}, - "sender": {"type": ["string", "null"]}, - }, -} - - -def _macos_stream(template_id: str, title: str, description: str, script_name: str, poll_seconds: int) -> dict: - script_path = fetch.repo_root() / "scripts" / "macos" / script_name - return { - "id": template_id, - "title": title, - "description": description, - "type": "macos.item", - "mode": "event", - "schema_url": "https://agentfeeds.dev/schemas/macos.item.v1.json", - "schema_version": "1.0.0", - "parameters": [], - "source_uri_template": f"feed://macos.{template_id.split('/', 1)[1]}/items", - "adapter": { - "kind": "local_command", - "command": [sys.executable, str(script_path)], - "timeout_seconds": 30, - "max_output_bytes": 1048576, - "parse": "json", - "items_from": "items", - "id_from": "id", - "time_from": "updated_at", - "transform": { - "language": "jmespath", - "expression": ( - "{title: title, content: content, source: source, url: url, starts_at: starts_at, " - "ends_at: ends_at, updated_at: updated_at, sender: sender}" - ), - }, - }, - "recommended_poll_interval_seconds": poll_seconds, - "auth": "none", - "tags": ["macos", "local-command"], - "quality_tier": "experimental", - "contributed_by": "local", - "pending": True, - } - - -def macos_native_templates() -> list[dict]: - return [ - _macos_stream( - "macos/calendar-today", - "macOS Calendar today", - "Read-only events from the local macOS Calendar app for today.", - "calendar_today.py", - 300, - ), - _macos_stream( - "macos/reminders-open", - "macOS open reminders", - "Read-only incomplete reminders from the local macOS Reminders app.", - "reminders_open.py", - 600, - ), - _macos_stream( - "macos/mail-inbox-recent", - "macOS recent inbox mail", - "Read-only recent messages from the local macOS Mail inbox.", - "mail_inbox_recent.py", - 300, - ), - ] - - -def cmd_macos_install_templates(args: argparse.Namespace) -> int: - fetch.ensure_root(args.root) - schema_path = fetch.template_schemas_root(args.root) / "macos.item.v1.json" - if args.force or not schema_path.exists(): - schema_path.parent.mkdir(parents=True, exist_ok=True) - schema_path.write_text(json.dumps(MACOS_EVENT_SCHEMA, indent=2, sort_keys=True) + "\n", encoding="utf-8") - - written = [] - skipped = [] - for stream in macos_native_templates(): - category, name = stream["id"].split("/", 1) - path = fetch.template_streams_root(args.root) / category / f"{name}.yaml" - if path.exists() and not args.force: - skipped.append(str(path)) - continue - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(yaml.safe_dump(stream, sort_keys=False), encoding="utf-8") - written.append(str(path)) - - result = { - "schema": str(schema_path), - "written": written, - "skipped": skipped, - "next_actions": [ - { - "action": "approve_local_command", - "command": "python3 scripts/agentfeeds.py admin templates approve-command ", - "reason": "macOS templates are local_command templates and remain pending until the operator approves them", - } - ], - } - if args.json: - print(json.dumps(result, indent=2, sort_keys=True)) - return 0 - - for path in written: - print(f"wrote: {path}") - for path in skipped: - print(f"skipped existing: {path}") - print("Next: run `python3 scripts/agentfeeds.py admin templates approve-command ` in a terminal for each template you want to enable.") - return 0 - - def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Manage Agent Feeds subscriptions") parser.add_argument("--root", type=Path, default=fetch.DEFAULT_ROOT, help="agentfeeds root directory") @@ -1748,13 +1622,6 @@ def build_parser() -> argparse.ArgumentParser: secret_list.add_argument("--json", action="store_true") secret_list.set_defaults(func=cmd_secrets_list) - admin_macos = admin_subparsers.add_parser("macos", help="install macOS-native local templates") - admin_macos_subparsers = admin_macos.add_subparsers(dest="macos_command", required=True) - install_macos_templates = admin_macos_subparsers.add_parser("install-templates", help="install Calendar, Reminders, and Mail templates") - install_macos_templates.add_argument("--force", action="store_true", help="overwrite existing macOS local templates") - install_macos_templates.add_argument("--json", action="store_true") - install_macos_templates.set_defaults(func=cmd_macos_install_templates) - return parser diff --git a/skills/agentfeeds/scripts/macos/calendar_today.py b/skills/agentfeeds/scripts/macos/calendar_today.py deleted file mode 100644 index 2fd147ed..00000000 --- a/skills/agentfeeds/scripts/macos/calendar_today.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -"""Read today's macOS Calendar events as Agent Feeds local_command JSON.""" - -from __future__ import annotations - -import json -import platform -import subprocess -import sys - - -SCRIPT = r''' -set startDate to current date -set hours of startDate to 0 -set minutes of startDate to 0 -set seconds of startDate to 0 -set endDate to startDate + (1 * days) -set rows to {} -tell application "Calendar" - repeat with cal in calendars - set calName to name of cal - set matches to every event of cal whose start date is greater than or equal to startDate and start date is less than endDate - repeat with ev in matches - set evLocation to "" - try - set evLocation to location of ev - end try - set end of rows to (uid of ev) & tab & (summary of ev) & tab & ((start date of ev) as string) & tab & ((end date of ev) as string) & tab & evLocation & tab & calName - end repeat - end repeat -end tell -set AppleScript's text item delimiters to linefeed -return rows as text -''' - - -def main() -> int: - if platform.system() != "Darwin": - print(json.dumps({"items": []})) - return 0 - result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) - if result.returncode: - print(json.dumps({"items": []})) - print(result.stderr.strip(), file=sys.stderr) - return 0 - items = [] - for line in result.stdout.splitlines(): - parts = line.split("\t") - if len(parts) < 6: - continue - uid, title, starts_at, ends_at, location, calendar_name = parts[:6] - items.append( - { - "id": uid or f"{calendar_name}:{starts_at}:{title}", - "title": title or "(untitled event)", - "content": location or None, - "source": calendar_name, - "starts_at": starts_at, - "ends_at": ends_at, - "updated_at": starts_at, - } - ) - print(json.dumps({"items": items}, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/skills/agentfeeds/scripts/macos/mail_inbox_recent.py b/skills/agentfeeds/scripts/macos/mail_inbox_recent.py deleted file mode 100644 index 9ebb1bed..00000000 --- a/skills/agentfeeds/scripts/macos/mail_inbox_recent.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 -"""Read recent macOS Mail inbox messages as Agent Feeds local_command JSON.""" - -from __future__ import annotations - -import json -import platform -import subprocess -import sys - - -SCRIPT = r''' -set rows to {} -tell application "Mail" - set messageCount to count of messages of inbox - set maxItems to 20 - if messageCount < maxItems then set maxItems to messageCount - repeat with i from 1 to maxItems - set msg to message i of inbox - set senderText to "" - set subjectText to "" - set dateText to "" - set previewText to "" - try - set senderText to sender of msg - end try - try - set subjectText to subject of msg - end try - try - set dateText to (date received of msg) as string - end try - try - set previewText to content of msg - if length of previewText > 500 then set previewText to text 1 thru 500 of previewText - end try - set end of rows to (message id of msg) & tab & subjectText & tab & senderText & tab & dateText & tab & previewText - end repeat -end tell -set AppleScript's text item delimiters to linefeed -return rows as text -''' - - -def main() -> int: - if platform.system() != "Darwin": - print(json.dumps({"items": []})) - return 0 - result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) - if result.returncode: - print(json.dumps({"items": []})) - print(result.stderr.strip(), file=sys.stderr) - return 0 - items = [] - for line in result.stdout.splitlines(): - parts = line.split("\t") - if len(parts) < 5: - continue - message_id, subject, sender, received_at, preview = parts[:5] - items.append( - { - "id": message_id or f"{sender}:{received_at}:{subject}", - "title": subject or "(no subject)", - "content": preview or None, - "source": "Mail Inbox", - "sender": sender or None, - "updated_at": received_at or None, - } - ) - print(json.dumps({"items": items}, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/skills/agentfeeds/scripts/macos/reminders_open.py b/skills/agentfeeds/scripts/macos/reminders_open.py deleted file mode 100644 index f91ad193..00000000 --- a/skills/agentfeeds/scripts/macos/reminders_open.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -"""Read incomplete macOS Reminders as Agent Feeds local_command JSON.""" - -from __future__ import annotations - -import json -import platform -import subprocess -import sys - - -SCRIPT = r''' -set rows to {} -tell application "Reminders" - repeat with listRef in lists - set listName to name of listRef - set matches to every reminder of listRef whose completed is false - repeat with remRef in matches - set bodyText to "" - set dueText to "" - try - set bodyText to body of remRef - end try - try - set dueText to (due date of remRef) as string - end try - set end of rows to (id of remRef) & tab & (name of remRef) & tab & bodyText & tab & dueText & tab & listName - end repeat - end repeat -end tell -set AppleScript's text item delimiters to linefeed -return rows as text -''' - - -def main() -> int: - if platform.system() != "Darwin": - print(json.dumps({"items": []})) - return 0 - result = subprocess.run(["osascript", "-e", SCRIPT], check=False, text=True, capture_output=True, timeout=25) - if result.returncode: - print(json.dumps({"items": []})) - print(result.stderr.strip(), file=sys.stderr) - return 0 - items = [] - for line in result.stdout.splitlines(): - parts = line.split("\t") - if len(parts) < 5: - continue - reminder_id, title, body, due_at, list_name = parts[:5] - items.append( - { - "id": reminder_id or f"{list_name}:{title}:{due_at}", - "title": title or "(untitled reminder)", - "content": body or None, - "source": list_name, - "starts_at": due_at or None, - "updated_at": due_at or None, - } - ) - print(json.dumps({"items": items}, ensure_ascii=False)) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) From 0b788a82e31bf32ee59c929d7593f04e4334b7e1 Mon Sep 17 00:00:00 2001 From: Verky Yi Date: Sun, 3 May 2026 19:08:48 -0700 Subject: [PATCH 92/92] Reduce agentfeeds publish bundle --- .../event-types/geo.earthquake.v1.json | 14 ----------- .../schemas/event-types/hn.story.v1.json | 14 ----------- .../event-types/space.iss-location.v1.json | 12 ---------- skills/agentfeeds/scripts/polling/install.py | 23 ------------------- .../agentfeeds/scripts/polling/uninstall.py | 23 ------------------- 5 files changed, 86 deletions(-) delete mode 100644 skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json delete mode 100644 skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json delete mode 100644 skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json delete mode 100755 skills/agentfeeds/scripts/polling/install.py delete mode 100755 skills/agentfeeds/scripts/polling/uninstall.py diff --git a/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json b/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json deleted file mode 100644 index 00561dea..00000000 --- a/skills/agentfeeds/catalog/schemas/event-types/geo.earthquake.v1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agentfeeds.dev/schemas/geo.earthquake.v1.json", - "title": "Earthquake Event", - "type": "object", - "required": ["id", "time", "magnitude", "place"], - "properties": { - "id": { "type": "string" }, - "time": { "type": "string" }, - "magnitude": { "type": "number" }, - "place": { "type": "string" }, - "url": { "type": "string" } - } -} diff --git a/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json b/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json deleted file mode 100644 index 91581ca8..00000000 --- a/skills/agentfeeds/catalog/schemas/event-types/hn.story.v1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agentfeeds.dev/schemas/hn.story.v1.json", - "title": "Hacker News Story", - "type": "object", - "required": ["id", "title"], - "properties": { - "id": { "type": "integer" }, - "title": { "type": "string" }, - "url": { "type": ["string", "null"] }, - "score": { "type": ["integer", "null"] }, - "by": { "type": ["string", "null"] } - } -} diff --git a/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json b/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json deleted file mode 100644 index 4d5344b7..00000000 --- a/skills/agentfeeds/catalog/schemas/event-types/space.iss-location.v1.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agentfeeds.dev/schemas/space.iss-location.v1.json", - "title": "ISS Location", - "type": "object", - "required": ["latitude", "longitude", "timestamp"], - "properties": { - "latitude": { "type": "number" }, - "longitude": { "type": "number" }, - "timestamp": { "type": "integer" } - } -} diff --git a/skills/agentfeeds/scripts/polling/install.py b/skills/agentfeeds/scripts/polling/install.py deleted file mode 100755 index 7578e74b..00000000 --- a/skills/agentfeeds/scripts/polling/install.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -"""Install background polling from the skill checkout.""" - -from __future__ import annotations - -import sys -import os -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" -if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): - os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) -LIB = ROOT / "scripts" / "lib" -if str(LIB) not in sys.path: - sys.path.insert(0, str(LIB)) - -from agentfeeds_runtime.polling.install import main # noqa: E402 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/skills/agentfeeds/scripts/polling/uninstall.py b/skills/agentfeeds/scripts/polling/uninstall.py deleted file mode 100755 index 40e7fab7..00000000 --- a/skills/agentfeeds/scripts/polling/uninstall.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -"""Uninstall background polling from the skill checkout.""" - -from __future__ import annotations - -import sys -import os -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -VENV_PYTHON = Path.home() / ".agentfeeds" / "runtime-venv" / "bin" / "python" -if VENV_PYTHON.exists() and Path(sys.executable).resolve() != VENV_PYTHON.resolve(): - os.execv(str(VENV_PYTHON), [str(VENV_PYTHON), __file__, *sys.argv[1:]]) -LIB = ROOT / "scripts" / "lib" -if str(LIB) not in sys.path: - sys.path.insert(0, str(LIB)) - -from agentfeeds_runtime.polling.uninstall import main # noqa: E402 - - -if __name__ == "__main__": - raise SystemExit(main())