| 1 | package installsource |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "fmt" |
| 6 | ) |
| 7 | |
| 8 | // RiskLevel classifies how dangerous an action is. The install-capability skill |
| 9 | // prompt tells the model to call apply=true only when every action is low or |
| 10 | // medium, or to ask the user first when any action is high. |
| 11 | type RiskLevel string |
| 12 | |
| 13 | const ( |
| 14 | // RiskLow is read-mostly safe: copy/link of a single skill file, or |
| 15 | // connecting an MCP endpoint whose URL the user already trusts. |
| 16 | RiskLow RiskLevel = "low" |
| 17 | // RiskMedium is a write that mutates the active config (new MCP server, |
| 18 | // new skill registered into a project root the user already shares). |
| 19 | RiskMedium RiskLevel = "medium" |
| 20 | // RiskHigh is a write the user almost certainly wants to see first: a |
| 21 | // symlink target outside any expected scope, a remote URL with auth |
| 22 | // headers, a package name that triggers an out-of-process fetch, or a |
| 23 | // replace of an existing MCP server. |
| 24 | RiskHigh RiskLevel = "high" |
| 25 | ) |
| 26 | |
| 27 | // Sentinel errors. Callers use errors.Is to map a failure to a remediation |
| 28 | // hint without scraping error messages. |
| 29 | var ( |
| 30 | // ErrAuthRequired: the upstream demanded credentials that the request |
| 31 | // did not carry. Surface a hint to set the relevant env var or header. |
| 32 | ErrAuthRequired = errors.New("install_source: authentication required") |
| 33 | // ErrBinaryMissing: a stdio MCP server references a command that is not |
| 34 | // on PATH or not present at the given path. |
| 35 | ErrBinaryMissing = errors.New("install_source: command or runtime not found") |
| 36 | // ErrAlreadyExists: a target file / config entry already exists and the |
| 37 | // call did not opt into replace=true. |
| 38 | ErrAlreadyExists = errors.New("install_source: target already exists") |
| 39 | // ErrUnsafeLinkTarget: a link-mode skill install would create a symlink |
| 40 | // that escapes the expected skill roots — typically an attempt to |
| 41 | // read arbitrary host files. |
| 42 | ErrUnsafeLinkTarget = errors.New("install_source: link target escapes skill roots") |
| 43 | // ErrSourceUnreadable: a URL did not respond, returned non-2xx, or a |
| 44 | // local path was not readable. |
| 45 | ErrSourceUnreadable = errors.New("install_source: source is not readable") |
| 46 | // ErrManifestMissing: a path was reachable but contained no installable |
| 47 | // artifact (no SKILL.md, no .mcp.json, no executable, etc.). |
| 48 | ErrManifestMissing = errors.New("install_source: no installable manifest") |
| 49 | // ErrInvalidManifest: a manifest existed but did not validate (missing |
| 50 | // required fields, unknown transport, etc.). |
| 51 | ErrInvalidManifest = errors.New("install_source: manifest did not validate") |
| 52 | // ErrNoCompatibleCapabilities: a plugin manifest was valid but none of its |
| 53 | // capabilities can run in Reasonix. Preview returns a structured block. |
| 54 | ErrNoCompatibleCapabilities = errors.New("install_source: plugin has no compatible capabilities") |
| 55 | // ErrUnsupportedKind: kind was set explicitly to something the resolver |
| 56 | // cannot satisfy (e.g. kind=skill for a remote MCP endpoint). |
| 57 | ErrUnsupportedKind = errors.New("install_source: kind does not match source") |
| 58 | // ErrApprovalDenied: the host's ApprovalFunc returned a non-nil error, |
| 59 | // or the call set apply=true while the host requires explicit consent. |
| 60 | ErrApprovalDenied = errors.New("install_source: host denied the install") |
| 61 | ) |
| 62 | |
| 63 | // errKind wraps a sentinel with a human-readable detail so logs and the |
| 64 | // `next` field stay useful. |
| 65 | type errKind struct { |
| 66 | sentinel error |
| 67 | detail string |
| 68 | } |
| 69 | |
| 70 | func (e *errKind) Error() string { return fmt.Sprintf("%s: %s", e.sentinel, e.detail) } |
| 71 | func (e *errKind) Unwrap() error { return e.sentinel } |
| 72 | |
| 73 | func newErr(sentinel error, format string, args ...any) error { |
| 74 | return &errKind{sentinel: sentinel, detail: fmt.Sprintf(format, args...)} |
| 75 | } |
| 76 |