A Swift library for working with Agent Skills — the portable skill format used by Claude Code, OpenAI Codex, and other AI coding tools.
SwiftSkill provides parsing, writing, validation, file-system discovery, and drag & drop support (Transferable) for the SKILL.md format.
- Swift 6.2+
- macOS 15+ / iOS 18+ / tvOS 18+ / watchOS 11+ / visionOS 2+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/1amageek/swift-skills.git", from: "0.1.0")
]Then add "SwiftSkill" to your target's dependencies.
A skill is a directory bundle containing a SKILL.md file (YAML frontmatter + Markdown body) and optional supporting files. In Swift, the entire bundle is represented as a single struct Skill — no filesystem awareness is needed when working with skills in code.
my-skill/
├── SKILL.md # Frontmatter + instructions (required)
├── scripts/
│ └── deploy.sh # Executable script
├── references/
│ └── API.md # Reference documentation
└── agents/
└── openai.yaml # Codex configuration
SwiftSkill supports the full Agent Skills standard plus provider-specific extensions:
| Provider | Skills Directory | Extensions |
|---|---|---|
| Standard | skills/ |
— |
| Claude Code | .claude/skills/ |
disable-model-invocation, model, context, agent, hooks, etc. |
| OpenAI Codex | .agents/skills/ |
agents/openai.yaml (interface, policy, dependencies) |
Skill is a self-contained bundle. Assemble it in code without thinking about the filesystem:
import SwiftSkill
var skill = Skill(
name: "deploy-staging",
description: "Deploy the current branch to the staging environment",
license: "MIT",
allowedTools: ["Bash(git:*)", "Read"],
body: """
## Steps
1. Run the test suite
2. Build the project
3. Deploy to staging using `scripts/deploy.sh`
""",
supportingFiles: [
SupportingFile(relativePath: "scripts/deploy.sh", text: "#!/bin/bash\necho deploy"),
]
)
// Claude Code extensions (stored in frontmatter)
skill.disableModelInvocation = true
skill.skillContext = "fork"
skill.agent = "Explore"
skill.model = "opus"
// Codex configuration (stored as bundled data)
try skill.setConfiguration(CodexConfiguration(
interface: CodexConfiguration.Interface(displayName: "Deploy Staging"),
policy: CodexConfiguration.Policy(allowImplicitInvocation: false)
))Filesystem paths are only relevant when reading from or writing to disk:
let parser = SkillParser()
let writer = SkillWriter()
// Parse from a string
let skill = try parser.parse(markdownString)
// Parse from a file
let skill = try parser.parse(at: fileURL)
// Parse a full directory bundle
let skill = try parser.parseDirectory(at: directoryURL)
// Write to a string
let markdown = try writer.write(skill)
// Write to a file
try writer.write(skill, to: fileURL)
// Write a full directory bundle
try writer.writeDirectory(skill, to: directoryURL)let store = SkillStore(rootURL: skillsDirectoryURL)
// Discover all skills
let skills = try store.discover()
// Read a specific skill
let skill = try store.skill(named: "deploy-staging")
// Save (creates directory structure)
try store.save(skill)
// Delete
try store.delete(named: "deploy-staging")let provider = SkillProvider.claudeCode
// ~/.claude/skills/
let personal = provider.personalSkillsURL
// <project>/.claude/skills/
let project = provider.projectSkillsURL(in: projectRootURL)
// Both paths, priority-ordered
let candidates = provider.discoveryURLs(projectRoot: projectRootURL)ConfigurationRepresentable defines how provider-specific configurations serialize to and from Data. The Skill bundle stores them by type name — no file paths involved:
// Write
try skill.setConfiguration(CodexConfiguration(
interface: CodexConfiguration.Interface(
displayName: "My Skill",
brandColor: "#3B82F6"
),
policy: CodexConfiguration.Policy(allowImplicitInvocation: false),
dependencies: CodexConfiguration.Dependencies(
tools: [
CodexConfiguration.ToolDependency(
type: "mcp",
value: "my-server",
transport: "streamable_http",
url: "https://example.com"
)
]
)
))
// Read
let codex = try skill.configuration(CodexConfiguration.self)
print(codex?.interface?.displayName)
// Check
skill.hasConfiguration(CodexConfiguration.self)
// Convenience accessors
skill.codexDisplayName = "My Skill"
skill.allowImplicitInvocation = falseDefine your own provider configuration by conforming to ConfigurationRepresentable:
struct MyConfig: ConfigurationRepresentable {
var endpoint: String
init(configurationData data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
func configurationData() throws -> Data {
try JSONEncoder().encode(self)
}
}
// Use in a Skill bundle (no path needed)
try skill.setConfiguration(MyConfig(endpoint: "https://api.example.com"))
// For I/O, tell the parser/writer where to find the file
let mappings = ConfigurationFileMapping.defaults + [
ConfigurationFileMapping(MyConfig.self, relativePath: "agents/myprovider.json")
]
let store = SkillStore(rootURL: url, configurationMappings: mappings)let validator = SkillValidator()
let errors = validator.validate(skill)
if errors.isEmpty {
// Skill is valid
} else {
for error in errors {
print(error) // e.g. .nameInvalidCharacters, .descriptionEmpty
}
}Name rules:
- 1–64 characters
- Lowercase letters, digits, and hyphens only
- No leading, trailing, or consecutive hyphens
All files in the skill directory (except SKILL.md and configuration files) are bundled as SupportingFile:
skill.supportingFiles = [
SupportingFile(relativePath: "scripts/run.sh", text: "#!/bin/bash\necho hello"),
SupportingFile(relativePath: "references/API.md", text: "# API Reference"),
SupportingFile(relativePath: "assets/config.json", content: jsonData),
]
// Access content
if let text = skill.supportingFiles.first?.textContent {
print(text)
}Non-standard frontmatter fields are stored in the extensions dictionary as SkillValue:
skill.extensions["retry-count"] = .int(3)
skill.extensions["timeout"] = .double(30.5)
skill.extensions["tags"] = .array([.string("deploy"), .string("ci")])
let retries = skill.extensions["retry-count"]?.intValue // 3Skill conforms to Transferable with the UTType io.agentskills.skill:
.draggable(skill)
.dropDestination(for: Skill.self) { skills, _ in
// Handle dropped skills
}| Type | Description |
|---|---|
Skill |
Core bundle model — Sendable, Hashable, Identifiable, Codable, Transferable |
SkillValue |
Dynamic YAML value enum with typed accessors and literal conformances |
SupportingFile |
Bundled file with relative path and content |
ConfigurationRepresentable |
Protocol for serializable configurations |
ConfigurationFileMapping |
Maps configuration types to file paths for I/O |
CodexConfiguration |
OpenAI Codex configuration (interface, policy, dependencies) |
SkillProvider |
Provider enum (standard, claudeCode, codex) with path resolution |
SkillParser |
Parses SKILL.md files and directory bundles |
SkillWriter |
Serializes skills to SKILL.md format and directory bundles |
SkillValidator |
Validates skills against the Agent Skills spec |
SkillStore |
File-system CRUD operations |
| Error | Cases |
|---|---|
SkillParserError |
fileNotFound, invalidEncoding, missingFrontmatter, invalidFrontmatter, missingRequiredField |
SkillWriterError |
serializationFailed, directoryCreationFailed, fileWriteFailed |
SkillValidationError |
nameEmpty, nameTooLong, nameInvalidCharacters, nameStartsOrEndsWithHyphen, nameConsecutiveHyphens, descriptionEmpty, descriptionTooLong, compatibilityTooLong |
MIT