feat(skills): native Anthropic Skills upload + sync (closes #35)#36
feat(skills): native Anthropic Skills upload + sync (closes #35)#36teslashibe wants to merge 5 commits into
Conversation
closes #35 (backend slice) - internal/skills: Service wraps Anthropic Beta.Skills (List, Upload, Delete) and persists skill_id/name/description/version in Postgres. UploadDir parses SKILL.md frontmatter, walks the folder, preserves relative paths via a namedReader so the multipart upload matches what the API expects. - internal/skills/zip.go: rejects path-traversal entries and zips without exactly one top-level folder + SKILL.md. - cmd/skills-sync: CLI driven by SKILLS_SOURCES that bulk-uploads every SKILL.md folder it finds; idempotent via UNIQUE(name) + UPSERT. - internal/agent/provision.go: SkillIDsFn opt; per-user agents now attach the agent toolset (bash/code-execution) alongside their MCP toolset only when at least one skill is configured. - cmd/provision/main.go: optional SKILLS_AGENT_IDS env to attach skills at bootstrap. - 00005_skills.sql migration with UNIQUE(name) + index on anthropic_skill_id. - /api/skills CRUD wired in main.go; Makefile target skills-sync; SKILLS_SOURCES + SKILLS_AGENT_IDS in .env.example. Co-authored-by: Cursor <cursoragent@cursor.com>
closes #35 (mobile slice) - mobile/services/skills.ts: typed client for /api/skills CRUD; uses expo/fetch for the multipart upload so we can build FormData. - mobile/app/(app)/skills.tsx: list + upload (zip via DocumentPicker) + delete (with confirm) + sync button. Empty state CTAs upload. - settings.tsx: link to Skills card. - _layout.tsx: register hidden 'skills' route so deep links work. - docs/SKILLS.md: SKILL.md format, folder layout, sandbox constraints (no network, bundle wheels), make skills-sync workflow, REST surface. - mobile/package.json: expo-document-picker dependency. Co-authored-by: Cursor <cursoragent@cursor.com>
Forensic Audit — Skills MVP vs #35Per 1. Findings (severity-ordered, evidence first)HIGHH1. Re-uploading a changed skill orphans the old Anthropic skill. H2. Newly uploaded skills don't reach already-provisioned per-user agents. H3. Upload endpoint inherits Fiber's 4 MiB default body limit. MEDIUMM1. M2. Brittle 404 detection on Anthropic delete. M3. LOWL1. Sync handler logs nothing. L2. Upload handler reads entire file into memory. L3. Custom YAML frontmatter parser is reinventing a wheel. L4. 2. Gaps vs issue intent
The two real gaps are H1 and H2. Everything else either passed or has a low-severity caveat documented above. 3. User stories (audited)
4. Acceptance criteria (re-stated, testable)Sync from filesystem
Upload via API
Provisioner attaches skills
Mobile UI
Negative paths
5. Risks and follow-up actions
Recommended follow-up tickets
Bottom line: the MVP meets 8/10 ACs as written. The two failing ACs (1.2 partial, 2.1 and 3.2) are real and high-severity but each is a targeted ~20-line fix using SDK methods we already pull in. None of them require redesign — they're bugs introduced by writing the simplest version first. |
…ome pattern)
Demonstrates how to build a skill whose compute can't fit in Anthropic's
sandbox (native deps, data files, side effects). Splits the skill in two:
- backend/internal/astrology + internal/mcp/platforms/astrology.go:
pure-Go sun-sign computation registered as the astrology_birth_summary
MCP tool. Stub for moon/ascendant/houses with comment pointing at
mshafiee/swephgo for full Swiss Ephemeris accuracy.
- skills/astrology/{SKILL.md, reference.md}: tiny skill that tells
Claude to gather birth data, call the MCP tool, read reference.md
for interpretation tables, and compose a tight reading.
docs/SKILLS.md gains an 'Authoring skills for this template' section
that documents the pattern, with the astrology skill as the worked
example. Also calls out the constraints surfaced during live testing:
no nested archives (.whl/.zip/.tar/.tgz/.gz are silently skipped) and
display_title uniqueness on re-upload (issue #35 follow-up to fix
via Versions.New).
Co-authored-by: Cursor <cursoragent@cursor.com>
Surfaced during live testing of the astrology skill upload: - ~/.claude/skills/foo is canonically a symlink at the real repo. The sync's e.IsDir() check returned false on symlink entries; switch to os.Stat (follows symlinks) and resolve via filepath.EvalSymlinks before walking, since filepath.Walk does not follow symlinks itself. Preserve the visible folder name in the uploaded paths so the skill still appears as 'foo' to Anthropic even when the symlink target is named differently. - Anthropic's API rejects 'Skill cannot contain nested zip files'. Skip .whl, .zip, .tar, .tgz, .gz extensions in openSkillFiles so the rest of the skill still uploads. The astrology skill ships pyswisseph wheels under scripts/wheels/ that triggered this. Co-authored-by: Cursor <cursoragent@cursor.com>
closes the H1/H2/H3 + M1/M2/M3 findings from the forensic audit on PR #36 and three more constraints surfaced during live testing. H1 — display_title uniqueness on re-upload (audit + live) Anthropic rejects Skills.New when a skill with that display_title already exists. Service.UploadDir now looks up the prior anthropic skill ID for that name and uses Beta.Skills.Versions.New on collision; only first uploads call Beta.Skills.New. H2 — newly-uploaded skills don't reach cached per-user agents refreshAgentsAsync fires after every successful upload/delete: it enumerates every users.anthropic_agent_id and pushes the current skill list via Beta.Agents.Update. Fired-and-forgotten so upload latency stays low; failures are logged. H3 — Fiber's 4 MiB default body limit rejects wheel-bundled skills Bumped fiber.Config.BodyLimit to 64 MiB (matches Anthropic's per-skill cap). Verified live with a 6 MiB upload that previously 413'd. NEW: Cannot delete skill while versions exist deleteAllVersions enumerates every version via the Versions service and deletes each before calling Skills.Delete. 404s on individual versions are swallowed. NEW: SDK helper BetaManagedAgentsSkillParamsOfCustom omits required Type Returns 400 'skills[0].type: Field required'. Wrapped in skills.SkillParams which sets Type to BetaManagedAgentsCustomSkillParamsTypeCustom. Provisioner refactored to use it. NEW: Anthropic's cloud cannot reach loopback MCP URLs createAgent now fails fast with an actionable hint pointing at ngrok/cloudflared instead of waiting for Anthropic to return a generic 400. M1 — silent truncation at 20-skill cap AnthropicIDs now logs the names of skills it dropped. M2 — brittle 404 detection Added is404 helper using errors.As(*anthropic.Error{}); replaces the strings.Contains heuristic in Delete + deleteAllVersions. M3 — bash-source brittleness in Makefile Dropped the 'source backend/.env' incantation; the CLI uses godotenv.Load() so the Makefile target is one line now. Bonus: Astrology binding sets NoCredentials: true so the demonstration tool doesn't trigger the credentials guard for credentialless plugins. skills-sync waits 2s before exiting so the async refresh goroutine can drain (was logging 'closed pool' in CLI output). Co-authored-by: Cursor <cursoragent@cursor.com>
Live Test Results — All Audit Findings ResolvedTested every API + Web route on the live stack. All audit findings (H1/H2/H3 + M1/M2/M3) plus three additional Anthropic API constraints surfaced during testing are now fixed and verified. Audit fixes
Additional Anthropic API constraints surfaced (live testing)
Live test resultsSkills-specific E2E
What was NOT tested live (with reason)
Files changed in this push |
Closes #35.
Wires the template into Anthropic's Beta Skills API so any fork can drop a
SKILL.mdfolder (e.g. `~/.claude/skills/astrology-skill/`) and have it uploaded, persisted, and attached to per-user agents.Summary
Constraints worth re-flagging in review
Test plan
Made with Cursor