skills

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package skills installs the agent-facing documentation that ships inside the binary (§14) into an agent's skill directory.

It does not own the skill's bytes. The canonical — and only — copy of the skill lives at the repository root under skills/pay/, which is both the layout `npx skills add .../skills --skill pay` expects and a Go package that owns the //go:embed directive. This package imports it, so the tree that ships inside `pay` is byte-for-byte the tree in the repository: one copy, no sync step, nothing to drift (§1 conflict 18).

Index

Constants

View Source
const (
	// Name is the skill's directory name and the name agents refer to it by.
	// It is the root package's Dir, so the directory `pay skills install`
	// creates and the directory the skill is stored in cannot disagree.
	Name = root.Dir
	// SkillFile is the entry point every agent reads first.
	SkillFile = "SKILL.md"
	// ReferenceDir holds the deep-dive documents.
	ReferenceDir = "references"
	// ProjectFile is the optional, discovery-generated project snapshot.
	ProjectFile = "references/PROJECT.md"
	// DirPerm is the mode for created skill directories (§14).
	DirPerm fs.FileMode = 0o755
	// FilePerm is the mode for installed skill files (§14).
	FilePerm fs.FileMode = 0o644
)
View Source
const (
	// StatusWritten — the file was created or overwritten.
	StatusWritten = "written"
	// StatusUnchanged — the file already had the embedded content.
	StatusUnchanged = "unchanged"
	// StatusSkipped — the file (or agent) was deliberately left alone.
	StatusSkipped = "skipped"
	// StatusRemoved — the file was deleted by `pay skills uninstall`.
	StatusRemoved = "removed"
	// StatusMissing — nothing was installed there.
	StatusMissing = "missing"
)

File and target statuses.

View Source
const (
	// WarnFileModified — a user-edited file was left alone.
	WarnFileModified = "skill_file_modified"
	// WarnNotIgnored — the project's .gitignore does not cover the skill dir.
	WarnNotIgnored = "skill_dir_not_ignored"
	// WarnAgentSkipped — an agent was not installed into, and why.
	WarnAgentSkipped = "skill_agent_skipped"
	// WarnProjectContextUnavailable — discovery failed, so PROJECT.md was not
	// written (§14: it degrades gracefully).
	WarnProjectContextUnavailable = "skill_project_context_unavailable"
)

Warning codes this package emits.

View Source
const ManifestName = ".pay-skill.json"

ManifestName is the install record written next to the skill files (§14).

Variables

View Source
var Agents = []Agent{
	{Name: "claude", Home: ".claude", Skills: "skills"},
	{Name: "codex", Home: ".codex", Skills: "skills"},
	{Name: "cursor", Home: ".cursor", Skills: "skills"},
	{Name: "gemini", Home: ".gemini", Skills: "skills"},
	{Name: "gemini/antigravity", Home: ".antigravity", Skills: "skills"},
	{Name: "opencode", Home: ".opencode", Skills: "skills"},
	{Name: "windsurf", Home: ".windsurf", Skills: "skills"},
	{Name: "continue", Home: ".continue", Skills: "skills"},
	{Name: "crush", Home: ".crush", Skills: "skills"},
	{Name: "kiro", Home: ".kiro", Skills: "skills"},
	{Name: "qwen", Home: ".qwen", Skills: "skills"},
	{Name: "qoder", Home: ".qoder", Skills: "skills"},
}

Agents is the §14 target list, in install order.

View Source
var MandatoryStatements = []string{

	"A read without `--draft` does **not** filter out unpublished documents — documents that were never published are returned with `_status:\"draft\"`. To get only published content, always pass `--published-only` (`_status = published`). `--draft` additionally swaps in the newest draft for documents that **do** have a published version.",

	"Payload validates the **whole document** on update: a `PATCH` of one field re-validates every field of the stored document, so the error can name fields you never sent. `error.fields[].sent` is `true` only for paths that were leaves of the body PayCLI actually sent; when every entry is `sent: false`, the stored document was already invalid and your change was rejected by pre-existing state, not by your input.",

	"`--locale de` on an untranslated field returns the **default locale's** text unless `fallback-locale=none`, which PayCLI sends by default and reports in `meta.locale`.",

	"**Never branch on `error.message` — it is translated.** Payload runs its error strings through i18n, so the same failure reads differently on a German project, and a proxy can change the language by injecting `Accept-Language`. `error.code`, `error.exit` and `ok` are the stable signals.",
}

MandatoryStatements are the four sentences §14 requires verbatim in both SKILL.md and references/gotchas.md. Each one is a place where the obvious agent behaviour produces a confidently wrong answer, so they are asserted by a unit test rather than left to review.

View Source
var ProjectMarkers = []string{
	"payload.config.ts", "payload.config.js", "payload.config.mjs", "payload.config.mts",
	".git", "package.json",
}

ProjectMarkers identify a project root when walking upward from the working directory (§14).

Functions

func AgentNames

func AgentNames() []string

AgentNames lists the known agents, for help text and completions.

func DetectProjectRoot

func DetectProjectRoot(start string) (string, bool)

DetectProjectRoot walks upward from start looking for a project marker. The deepest match wins, so a nested package.json beats the repository root.

func FS

func FS() fs.FS

FS returns the embedded skill tree rooted at the skill directory, so fs.ReadFile(skills.FS(), "SKILL.md") works. It is the root skills package's embedded copy of skills/pay/ — this package never embeds its own.

func GitignoreCovers

func GitignoreCovers(root, rel string) (covered, known bool)

GitignoreCovers reports whether the project's .gitignore covers rel (a slash-separated path relative to the project root). The second return value is false when there is no .gitignore to consult, in which case no warning is warranted.

This is a deliberately simple prefix/segment matcher, not a gitignore engine: it decides whether to print an informational warning, and a false negative there costs nothing.

func ManifestPath

func ManifestPath(dir string) string

ManifestPath is the manifest's location inside an installed skill directory.

func Names

func Names() []string

Names lists the embedded document paths, sorted. `pay skills list` prints it.

func NormalizeProse

func NormalizeProse(s string) string

NormalizeProse strips Markdown blockquote markers and collapses whitespace so a required statement can be compared regardless of how it was wrapped or indented. It is how the mandatory-statement test compares text.

func Read

func Read(name string) ([]byte, error)

Read returns one embedded document by its skill-relative path. `pay skills print [NAME]` uses it; NAME defaults to SKILL.md and may be given without the .md suffix or the references/ prefix.

func RenderProject

func RenderProject(ctx ProjectContext) []byte

RenderProject renders references/PROJECT.md.

§14 requires the header comment, the snapshot disclaimer, the collection table, the globals, the auth collection slug with the exact header form, and three examples written against real slugs.

func Resolve

func Resolve(name string) string

Resolve maps a user-supplied document name onto an embedded path. It accepts "", "SKILL.md", "skill", "errors", "references/errors.md" and "errors.md".

func Sum

func Sum(data []byte) string

Sum is the digest recorded in .pay-skill.json and compared on reinstall.

Types

type Agent

type Agent struct {
	// Name is what --agent matches, case-insensitively.
	Name string `json:"name"`
	// Home is the agent's directory relative to the scope root (".claude").
	// It is the directory whose EXISTENCE decides whether the default install
	// touches this agent at all.
	Home string `json:"home"`
	// Skills is the skill container inside Home ("skills").
	Skills string `json:"skills"`
}

Agent is one coding agent's on-disk skill layout. The table is data in the binary rather than code so a new agent does not require a release; --dir PATH and the skills.extra_dirs config key extend it at runtime.

func FindAgent

func FindAgent(name string) (Agent, bool)

FindAgent looks an agent up by name, case-insensitively.

func (Agent) Dir

func (a Agent) Dir(root string) string

Dir is this skill's directory for the agent: <root>/<home>/skills/pay.

func (Agent) HomeDir

func (a Agent) HomeDir(root string) string

HomeDir is the agent's own directory under root. Its existence is the signal "this agent is used here".

func (Agent) SkillsDir

func (a Agent) SkillsDir(root string) string

SkillsDir is where all of this agent's skills live.

type CollectionInfo

type CollectionInfo struct {
	Slug      string   `json:"slug"`
	Label     string   `json:"label,omitempty"`
	Singular  string   `json:"singular,omitempty"`
	IDType    string   `json:"id_type,omitempty"`
	Ops       []string `json:"ops,omitempty"`      // create read update delete
	Features  []string `json:"features,omitempty"` // drafts versions upload trash auth folders
	KeyFields []string `json:"key_fields,omitempty"`
	Internal  bool     `json:"internal,omitempty"`
	// TotalDocs is negative when unknown.
	TotalDocs int `json:"total_docs,omitempty"`
}

CollectionInfo is one collection's row in PROJECT.md.

func (CollectionInfo) HasFeature

func (c CollectionInfo) HasFeature(name string) bool

HasFeature reports whether the collection advertises a capability.

type File

type File struct {
	// Path is relative to the skill root, slash-separated ("SKILL.md",
	// "references/errors.md").
	Path string `json:"path"`
	// Data is the file's content.
	Data []byte `json:"-"`
	// SHA256 is the hex digest recorded in the install manifest.
	SHA256 string `json:"sha256"`
}

File is one embedded skill document.

func Files

func Files() []File

Files returns every embedded document, sorted by path, with its digest.

type FileResult

type FileResult struct {
	Path   string `json:"path"`
	Status string `json:"status"`
	Reason string `json:"reason,omitempty"`
}

FileResult is what happened to one file.

type FileStatus

type FileStatus struct {
	Path string `json:"path"`
	// State is "current" | "outdated" | "modified" | "missing" | "unknown".
	State string `json:"state"`
}

FileStatus is one file's drift state in `pay skills status`.

type GlobalInfo

type GlobalInfo struct {
	Slug     string   `json:"slug"`
	Label    string   `json:"label,omitempty"`
	Features []string `json:"features,omitempty"`
}

GlobalInfo is one global's row in PROJECT.md.

type Manifest

type Manifest struct {
	CLIVersion        string            `json:"cli_version"`
	InstalledAt       string            `json:"installed_at"`
	Scope             string            `json:"scope"`
	Agents            []string          `json:"agents"`
	Profile           string            `json:"profile"`
	BaseURL           string            `json:"base_url"`
	DiscoveryRevision string            `json:"discovery_revision,omitempty"`
	Files             map[string]string `json:"files"`
}

Manifest records what was installed, by which binary, against which project. Its Files map is what makes "the user edited this file" detectable: a hash that differs from the recorded one was not written by PayCLI.

func LoadManifest

func LoadManifest(dir string) (*Manifest, error)

LoadManifest reads the manifest from an installed skill directory. A missing manifest is (nil, nil): the directory may predate PayCLI or have been created by hand, and that is not an error. An unparseable one is cache_corrupt (exit 1) so the user is told to delete it rather than silently losing the user-edit protection it provides.

func (*Manifest) Recorded

func (m *Manifest) Recorded(rel string) (string, bool)

Recorded returns the hash this manifest has for a skill-relative path, and whether it has one at all.

func (*Manifest) Save

func (m *Manifest) Save(dir string) error

Save writes the manifest atomically. base_url passes through redact.URL because a project skill directory is typically committed (§14 Privacy).

type Options

type Options struct {
	// Scope is "" for the §14 default (project when a marker is found by
	// walking up from StartDir, else user).
	Scope Scope
	// StartDir is the working directory used for project detection.
	StartDir string
	// ProjectRoot overrides detection.
	ProjectRoot string
	// Home is the user's home directory. It is injected rather than looked up
	// so the whole package is testable and env-free.
	Home string
	// Agents selects agents by name. Empty means the §14 default: every agent
	// whose Home directory already exists.
	Agents []string
	// AllAgents is --agent all: install into every known agent, creating the
	// directories.
	AllAgents bool
	// ExtraDirs are --dir PATH and skills.extra_dirs. Each is an agent's
	// skills directory; the skill lands in <dir>/pay.
	ExtraDirs []string
	// Force overwrites files the user edited.
	Force bool
	// DryRun computes the plan without touching the filesystem.
	DryRun bool

	// Project is references/PROJECT.md's content from --with-project-context.
	// Nil leaves any existing PROJECT.md alone.
	Project []byte

	// Manifest metadata.
	CLIVersion        string
	Now               time.Time
	Profile           string
	BaseURL           string
	DiscoveryRevision string
}

Options drives Install, Uninstall and Status.

type ProjectContext

type ProjectContext struct {
	CLIVersion        string
	GeneratedAt       time.Time
	Profile           string
	BaseURL           string
	APIPath           string
	DiscoveryRevision string
	PayloadVersion    string
	AuthCollection    string
	AuthMode          string
	Locales           []string
	DefaultLocale     string
	Collections       []CollectionInfo
	Globals           []GlobalInfo
	// Examples overrides the three generated example commands.
	Examples []string
}

ProjectContext is everything PROJECT.md is rendered from. It carries no credential of any kind: the auth header is documented as a form, never with a value, and BaseURL is redacted on render (§14 Privacy).

type Result

type Result struct {
	Scope    Scope            `json:"scope"`
	Root     string           `json:"root"`
	Targets  []TargetResult   `json:"targets"`
	Skipped  []TargetResult   `json:"skipped"`
	Warnings []output.Warning `json:"-"`
	DryRun   bool             `json:"dry_run,omitempty"`
}

Result is the envelope payload of `pay skills install` / `uninstall`.

func Install

func Install(opts Options) (*Result, error)

Install copies the skill into every selected target. Files are copied, never symlinked: a symlink into a binary-relative path breaks on the next update.

func Uninstall

func Uninstall(opts Options) (*Result, error)

Uninstall removes the skill from every selected target. Only files PayCLI recorded are removed, and only when they still match what it wrote — an edited file is left behind unless --force, exactly like install.

type Scope

type Scope string

Scope is where the skill is installed (§14).

const (
	// ScopeProject installs into the detected project root.
	ScopeProject Scope = "project"
	// ScopeUser installs into the user's home directory.
	ScopeUser Scope = "user"
)

type StatusReport

type StatusReport struct {
	Scope   Scope          `json:"scope"`
	Root    string         `json:"root"`
	Targets []TargetStatus `json:"targets"`
}

StatusReport is the payload of `pay skills status`. `pay doctor` uses the same data to say whether the skill is installed and stale.

func Status

func Status(opts Options) (*StatusReport, error)

Status reports every install location, its recorded CLI version against the running binary, per-file drift, and whether PROJECT.md's discovery revision is older than the current one.

type TargetResult

type TargetResult struct {
	Agent  string       `json:"agent"`
	Dir    string       `json:"dir"`
	Status string       `json:"status"`
	Reason string       `json:"reason,omitempty"`
	Files  []FileResult `json:"files,omitempty"`
}

TargetResult is what happened at one install location.

type TargetStatus

type TargetStatus struct {
	Agent             string       `json:"agent"`
	Dir               string       `json:"dir"`
	Installed         bool         `json:"installed"`
	CLIVersion        string       `json:"cli_version,omitempty"`
	Stale             bool         `json:"stale"`
	Profile           string       `json:"profile,omitempty"`
	BaseURL           string       `json:"base_url,omitempty"`
	DiscoveryRevision string       `json:"discovery_revision,omitempty"`
	ProjectDocStale   bool         `json:"project_doc_stale"`
	Files             []FileStatus `json:"files,omitempty"`
}

TargetStatus reports one install location.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL