Documentation
¶
Overview ¶
Package skillsync projects active registry skills into native client skill directories (Claude Code's ~/.claude/skills, the vendor-neutral ~/.agents/skills interop dir, Antigravity's ~/.gemini/config/skills) so gridctl-managed skills are usable in clients that never fetch MCP prompts and auto-trigger in clients that read skills from disk. It is the directory-projection sibling of pkg/contexts: a per-client target table, a machine-global lockfile with ownership tracking, and sync/status/unsync operations. Every operation is a pure file operation; no running gateway is required. The MCP prompt channel is untouched: projection and prompts are complementary per-client delivery channels.
Index ¶
- Constants
- Variables
- func HasFailures(results []SyncResult) bool
- func NeedsAttention(statuses []ProjectionStatus) bool
- func SupportedSlugs() []string
- type Channel
- type Entry
- type LockFile
- type Manager
- func (m *Manager) HasProjections() (bool, error)
- func (m *Manager) LockPath() string
- func (m *Manager) Reconcile(ctx context.Context) ([]SyncResult, error)
- func (m *Manager) Statuses(ctx context.Context) ([]ProjectionStatus, error)
- func (m *Manager) Sync(ctx context.Context, names []string, opts SyncOptions) ([]SyncResult, error)
- func (m *Manager) Unsync(ctx context.Context, names []string, opts UnsyncOptions) ([]UnsyncResult, error)
- type ProjectionStatus
- type SkillSource
- type SyncOptions
- type SyncResult
- type Target
- type UnsyncOptions
- type UnsyncResult
Constants ¶
const ( StateInSync = "in-sync" StateStale = "stale" StateDrifted = "drifted" StateTargetMissing = "target-missing" )
Projection states, sharing the pkg/contexts vocabulary. Symlink projections of active skills are never content-stale (the link references the registry directly), but any projection goes stale when its skill leaves the active set: the pending action is removal.
const ( ActionLinked = "linked" ActionCopied = "copied" ActionUpdated = "updated" ActionUnchanged = "unchanged" ActionRemoved = "removed" ActionSkippedDrift = "skipped-drift" ActionSkippedUnmanaged = "skipped-unmanaged" ActionWouldLink = "would-link" ActionWouldCopy = "would-copy" ActionWouldUpdate = "would-update" ActionWouldRemove = "would-remove" ActionAlreadyGone = "already-gone" ActionError = "error" )
Sync result actions.
Variables ¶
var ( ErrUnknownClient = errors.New("unknown client") ErrNotAvailable = errors.New("client not initialized on this machine") ErrNotProjected = errors.New("skill is not projected") )
Sentinel errors callers branch on.
var ErrNewerLockVersion = errors.New("skill projection lockfile was written by a newer gridctl version")
ErrNewerLockVersion signals a lockfile written by a newer gridctl.
Functions ¶
func HasFailures ¶
func HasFailures(results []SyncResult) bool
HasFailures reports whether any result needs the caller's attention.
func NeedsAttention ¶
func NeedsAttention(statuses []ProjectionStatus) bool
NeedsAttention reports whether any projection requires action: drifted, stale, or a missing target. Backs the status exit code.
func SupportedSlugs ¶
func SupportedSlugs() []string
SupportedSlugs lists the target slugs, derived from the table so error messages never go stale.
Types ¶
type Channel ¶
type Channel string
Channel is how one skill reaches one client: a symlink into the registry (edits propagate instantly, no drift class) or a full copy (needed where the client does not follow symlinked skill dirs).
type Entry ¶
type Entry struct {
// Channel is "symlink" or "copy".
Channel Channel `yaml:"channel"`
// Target is the absolute path gridctl created (the symlink itself or
// the copied directory).
Target string `yaml:"target"`
// CreatedByGridctl marks the path as gridctl-owned. Always true for
// recorded entries; present in the schema so a future adopt flow can
// track foreign paths without a format break.
CreatedByGridctl bool `yaml:"created_by_gridctl"`
// TreeHash is the copied directory's tree hash at sync time (empty
// for symlinks, whose content lives in the registry).
TreeHash string `yaml:"tree_hash,omitempty"`
SyncedAt time.Time `yaml:"synced_at"`
}
Entry is one (skill, client) projection record.
type LockFile ¶
type LockFile struct {
Version int `yaml:"version"`
// Projections maps skill name → client slug → entry.
Projections map[string]map[string]*Entry `yaml:"projections"`
}
LockFile records, per skill and client, what gridctl last projected. Ownership (CreatedByGridctl) is what lets sync refuse to clobber foreign paths and lets unsync remove only gridctl's own artifacts.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager owns the projection lockfile under <home>/.gridctl and every write into client skill directories. All target paths resolve against home, so tests point it at a temp dir. Mutating operations serialize on mu in-process and on a flock file across processes (the CLI and the daemon reconcile can race).
func NewManager ¶
func NewManager(store SkillSource) (*Manager, error)
NewManager builds a Manager rooted at the user's home directory.
func NewManagerWithHome ¶
func NewManagerWithHome(home string, store SkillSource) *Manager
NewManagerWithHome builds a Manager rooted at an explicit home directory. Tests use this to stay isolated from $HOME.
func (*Manager) HasProjections ¶
HasProjections reports whether any skill is currently projected. The daemon reconcile uses it as a cheap no-op guard.
func (*Manager) LockPath ¶
LockPath returns the projection lockfile path (<home>/.gridctl/skillsync.lock.yaml, a sibling of the registry).
func (*Manager) Reconcile ¶
func (m *Manager) Reconcile(ctx context.Context) ([]SyncResult, error)
Reconcile re-syncs the recorded projection set. The daemon calls it after every registry refresh; it is a fast no-op when nothing is projected.
func (*Manager) Statuses ¶
func (m *Manager) Statuses(ctx context.Context) ([]ProjectionStatus, error)
Statuses computes the per-projection state for everything in the projection set, sorted by skill then client. Reads are lock-free: the lockfile is written atomically.
func (*Manager) Sync ¶
func (m *Manager) Sync(ctx context.Context, names []string, opts SyncOptions) ([]SyncResult, error)
Sync projects skills into client skill directories. With names, the named active skills are added to the projection set for the resolved targets and materialized. With no names, the recorded projection set is reconciled: dangling or missing artifacts are repaired, stale copies refreshed, and projections whose skill was deactivated or deleted are removed. Nothing is ever projected without an explicit prior request (the deliberate divergence from ctx sync's all-available default: ~90 active skills would bloat client context).
func (*Manager) Unsync ¶
func (m *Manager) Unsync(ctx context.Context, names []string, opts UnsyncOptions) ([]UnsyncResult, error)
Unsync removes projections: named skills, or the whole set with All. Only gridctl-created artifacts are touched; copies are backed up before removal.
type ProjectionStatus ¶
type ProjectionStatus struct {
Skill string `json:"skill"`
Client string `json:"client"`
Channel string `json:"channel"`
Target string `json:"target"`
State string `json:"state"`
Detail string `json:"detail,omitempty"`
Experimental bool `json:"experimental,omitempty"`
SyncedAt *time.Time `json:"synced_at,omitempty"`
}
ProjectionStatus is one (skill, client) row in `skill project status`.
type SkillSource ¶
type SkillSource interface {
// GetSkill returns a skill by name (a copy).
GetSkill(name string) (*registry.AgentSkill, error)
// ActiveSkills returns skills with state "active" (copies).
ActiveSkills() []*registry.AgentSkill
// Dir returns the registry base directory (skills live under
// Dir()/skills).
Dir() string
}
SkillSource is the slice of the registry store projection reads. The concrete *registry.Store satisfies it; tests can substitute a fake.
type SyncOptions ¶
type SyncOptions struct {
// Clients restricts the pass to these target slugs. Empty means every
// available target.
Clients []string
// Copy projects copies instead of symlinks (copy-forced targets copy
// regardless).
Copy bool
// Force overwrites drifted copies and unmanaged destination paths
// (after a timestamped backup).
Force bool
// DryRun reports the plan without writing anything.
DryRun bool
}
SyncOptions configure a sync pass.
type SyncResult ¶
type SyncResult struct {
Skill string `json:"skill"`
Client string `json:"client"`
Channel string `json:"channel,omitempty"`
Target string `json:"target,omitempty"`
Action string `json:"action"`
BackupPath string `json:"backup_path,omitempty"`
Error string `json:"error,omitempty"`
}
SyncResult describes what happened (or would happen) for one (skill, client) projection.
type Target ¶
type Target struct {
Slug string
Name string
// SkillsPath is the directory skills are projected into; each skill
// becomes SkillsPath/<name> (a symlink or a copied directory).
SkillsPath string
// DetectDirs mark the client as initialized on this machine when any
// of them exists. Sync refuses to create client config trees
// wholesale, so these gate every write unless AlwaysAvailable is set.
DetectDirs []string
// AlwaysAvailable targets skip detection: the vendor-neutral
// ~/.agents/skills interop dir is created on first projection because
// gating on its existence would silently skip clients (Grok Build)
// that read it without ever creating it.
AlwaysAvailable bool
// DefaultChannel is used when the user does not pass --copy.
DefaultChannel Channel
// ForcedChannel, when set, overrides both the default and --copy.
// Antigravity is copy-forced until symlink discovery is verified on
// its exact path (symlinks went undiscovered under a related Gemini
// skills path; vercel-labs/skills#633).
ForcedChannel Channel
// Experimental marks targets whose documented path rests on
// unofficial sourcing; surfaced in status output.
Experimental bool
}
Target describes one client's native skills directory. SkillsPath is a ~-template expanded against the Manager's home directory.
func FindTarget ¶
FindTarget returns the projection target for slug.
type UnsyncOptions ¶
type UnsyncOptions struct {
// All removes every projection instead of named skills.
All bool
// Clients restricts removal to these target slugs.
Clients []string
// DryRun reports what would be removed without writing.
DryRun bool
}
UnsyncOptions configure an unsync pass.