Embed zot docs in binary
- commit
- b13a9a6a4dd6b4fea0c262561b91b53ae5bb04b1
- author
- patriceckhart · · 2026-05-20 18:25 UTC
- parent
- d46f1d5
- signature
- unsigned
3 files · +80 -13
A docs.go +55 -0
| @@ -0,0 +1,55 @@ | |||
| 1 | + | package docs | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "embed" | |
| 6 | + | "io/fs" | |
| 7 | + | "os" | |
| 8 | + | "path/filepath" | |
| 9 | + | "sort" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // embeddedDocs holds zot's user-facing documentation so installed | |
| 13 | + | // binaries do not depend on a source checkout existing on disk. | |
| 14 | + | // | |
| 15 | + | //go:embed README.md docs/*.md | |
| 16 | + | var embeddedDocs embed.FS | |
| 17 | + | ||
| 18 | + | var docFiles = map[string]string{ | |
| 19 | + | "README.md": "README.md", | |
| 20 | + | "extensions.md": "docs/extensions.md", | |
| 21 | + | "rpc.md": "docs/rpc.md", | |
| 22 | + | "skills.md": "docs/skills.md", | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | // EnsureInstalled writes the embedded docs into $ZOT_HOME/docs and | |
| 26 | + | // returns that directory. Existing files are left untouched when their | |
| 27 | + | // content already matches the embedded copy. | |
| 28 | + | func EnsureInstalled(zotHome string) (string, error) { | |
| 29 | + | dir := filepath.Join(zotHome, "docs") | |
| 30 | + | if err := os.MkdirAll(dir, 0o755); err != nil { | |
| 31 | + | return dir, err | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | names := make([]string, 0, len(docFiles)) | |
| 35 | + | for name := range docFiles { | |
| 36 | + | names = append(names, name) | |
| 37 | + | } | |
| 38 | + | sort.Strings(names) | |
| 39 | + | ||
| 40 | + | for _, name := range names { | |
| 41 | + | src := docFiles[name] | |
| 42 | + | data, err := fs.ReadFile(embeddedDocs, src) | |
| 43 | + | if err != nil { | |
| 44 | + | return dir, err | |
| 45 | + | } | |
| 46 | + | dst := filepath.Join(dir, name) | |
| 47 | + | if existing, err := os.ReadFile(dst); err == nil && bytes.Equal(existing, data) { | |
| 48 | + | continue | |
| 49 | + | } | |
| 50 | + | if err := os.WriteFile(dst, data, 0o644); err != nil { | |
| 51 | + | return dir, err | |
| 52 | + | } | |
| 53 | + | } | |
| 54 | + | return dir, nil | |
| 55 | + | } | |
| @@ -0,0 +1,55 @@ | |||||
| 1 | + | package docs | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "embed" | |||
| 6 | + | "io/fs" | |||
| 7 | + | "os" | |||
| 8 | + | "path/filepath" | |||
| 9 | + | "sort" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // embeddedDocs holds zot's user-facing documentation so installed | |||
| 13 | + | // binaries do not depend on a source checkout existing on disk. | |||
| 14 | + | // | |||
| 15 | + | //go:embed README.md docs/*.md | |||
| 16 | + | var embeddedDocs embed.FS | |||
| 17 | + | ||||
| 18 | + | var docFiles = map[string]string{ | |||
| 19 | + | "README.md": "README.md", | |||
| 20 | + | "extensions.md": "docs/extensions.md", | |||
| 21 | + | "rpc.md": "docs/rpc.md", | |||
| 22 | + | "skills.md": "docs/skills.md", | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | // EnsureInstalled writes the embedded docs into $ZOT_HOME/docs and | |||
| 26 | + | // returns that directory. Existing files are left untouched when their | |||
| 27 | + | // content already matches the embedded copy. | |||
| 28 | + | func EnsureInstalled(zotHome string) (string, error) { | |||
| 29 | + | dir := filepath.Join(zotHome, "docs") | |||
| 30 | + | if err := os.MkdirAll(dir, 0o755); err != nil { | |||
| 31 | + | return dir, err | |||
| 32 | + | } | |||
| 33 | + | ||||
| 34 | + | names := make([]string, 0, len(docFiles)) | |||
| 35 | + | for name := range docFiles { | |||
| 36 | + | names = append(names, name) | |||
| 37 | + | } | |||
| 38 | + | sort.Strings(names) | |||
| 39 | + | ||||
| 40 | + | for _, name := range names { | |||
| 41 | + | src := docFiles[name] | |||
| 42 | + | data, err := fs.ReadFile(embeddedDocs, src) | |||
| 43 | + | if err != nil { | |||
| 44 | + | return dir, err | |||
| 45 | + | } | |||
| 46 | + | dst := filepath.Join(dir, name) | |||
| 47 | + | if existing, err := os.ReadFile(dst); err == nil && bytes.Equal(existing, data) { | |||
| 48 | + | continue | |||
| 49 | + | } | |||
| 50 | + | if err := os.WriteFile(dst, data, 0o644); err != nil { | |||
| 51 | + | return dir, err | |||
| 52 | + | } | |||
| 53 | + | } | |||
| 54 | + | return dir, nil | |||
| 55 | + | } | |||
M internal/agent/build.go +13 -8
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "runtime" | |
| 9 | 9 | "strings" | |
| 10 | 10 | ||
| 11 | + | zotdocs "github.com/patriceckhart/zot" | |
| 11 | 12 | "github.com/patriceckhart/zot/internal/agent/tools" | |
| 12 | 13 | "github.com/patriceckhart/zot/internal/core" | |
| 13 | 14 | "github.com/patriceckhart/zot/internal/provider" | |
| @@ -77,10 +78,11 @@ func (r *Resolved) MergeExtensionTools(mgr ExtensionToolSource) { | |||
| 77 | 78 | // addendum is preserved by walking the existing append slice. | |
| 78 | 79 | append_ := r.systemAppend | |
| 79 | 80 | r.SystemPrompt = BuildSystemPrompt(SystemPromptOpts{ | |
| 80 | - | CWD: r.CWD, | |
| 81 | - | Tools: toolSummariesFromRegistry(r.ToolRegistry, r.toolDescriptions), | |
| 82 | - | Custom: r.systemCustom, | |
| 83 | - | Append: append_, | |
| 81 | + | CWD: r.CWD, | |
| 82 | + | Tools: toolSummariesFromRegistry(r.ToolRegistry, r.toolDescriptions), | |
| 83 | + | Custom: r.systemCustom, | |
| 84 | + | Append: append_, | |
| 85 | + | ZotDocsDir: filepath.Join(ZotHome(), "docs"), | |
| 84 | 86 | }) | |
| 85 | 87 | } | |
| 86 | 88 | ||
| @@ -318,6 +320,8 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||
| 318 | 320 | sandbox := tools.NewSandbox(args.CWD) | |
| 319 | 321 | reg := buildToolRegistry(args, args.CWD, sandbox) | |
| 320 | 322 | ||
| 323 | + | docsDir, _ := zotdocs.EnsureInstalled(ZotHome()) | |
| 324 | + | ||
| 321 | 325 | // Skill discovery: scan project + global locations + built-in | |
| 322 | 326 | // skills shipped with the binary. If any are found, register | |
| 323 | 327 | // the on-demand `skill` loader tool plus a system-prompt | |
| @@ -362,10 +366,11 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||
| 362 | 366 | } | |
| 363 | 367 | ||
| 364 | 368 | sys := BuildSystemPrompt(SystemPromptOpts{ | |
| 365 | - | CWD: args.CWD, | |
| 366 | - | Tools: summaries, | |
| 367 | - | Custom: custom, | |
| 368 | - | Append: append_, | |
| 369 | + | CWD: args.CWD, | |
| 370 | + | Tools: summaries, | |
| 371 | + | Custom: custom, | |
| 372 | + | Append: append_, | |
| 373 | + | ZotDocsDir: docsDir, | |
| 369 | 374 | }) | |
| 370 | 375 | ||
| 371 | 376 | reasoning := firstNonEmpty(args.Reasoning, cfg.Reasoning) | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "runtime" | 8 | "runtime" | ||
| 9 | "strings" | 9 | "strings" | ||
| 10 | 10 | ||||
| 11 | + | zotdocs "github.com/patriceckhart/zot" | |||
| 11 | "github.com/patriceckhart/zot/internal/agent/tools" | 12 | "github.com/patriceckhart/zot/internal/agent/tools" | ||
| 12 | "github.com/patriceckhart/zot/internal/core" | 13 | "github.com/patriceckhart/zot/internal/core" | ||
| 13 | "github.com/patriceckhart/zot/internal/provider" | 14 | "github.com/patriceckhart/zot/internal/provider" | ||
| @@ -77,10 +78,11 @@ func (r *Resolved) MergeExtensionTools(mgr ExtensionToolSource) { | |||||
| 77 | // addendum is preserved by walking the existing append slice. | 78 | // addendum is preserved by walking the existing append slice. | ||
| 78 | append_ := r.systemAppend | 79 | append_ := r.systemAppend | ||
| 79 | r.SystemPrompt = BuildSystemPrompt(SystemPromptOpts{ | 80 | r.SystemPrompt = BuildSystemPrompt(SystemPromptOpts{ | ||
| 80 | - | CWD: r.CWD, | 81 | + | CWD: r.CWD, |
| 81 | - | Tools: toolSummariesFromRegistry(r.ToolRegistry, r.toolDescriptions), | 82 | + | Tools: toolSummariesFromRegistry(r.ToolRegistry, r.toolDescriptions), |
| 82 | - | Custom: r.systemCustom, | 83 | + | Custom: r.systemCustom, |
| 83 | - | Append: append_, | 84 | + | Append: append_, |
| 85 | + | ZotDocsDir: filepath.Join(ZotHome(), "docs"), | |||
| 84 | }) | 86 | }) | ||
| 85 | } | 87 | } | ||
| 86 | 88 | ||||
| @@ -318,6 +320,8 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||||
| 318 | sandbox := tools.NewSandbox(args.CWD) | 320 | sandbox := tools.NewSandbox(args.CWD) | ||
| 319 | reg := buildToolRegistry(args, args.CWD, sandbox) | 321 | reg := buildToolRegistry(args, args.CWD, sandbox) | ||
| 320 | 322 | ||||
| 323 | + | docsDir, _ := zotdocs.EnsureInstalled(ZotHome()) | |||
| 324 | + | ||||
| 321 | // Skill discovery: scan project + global locations + built-in | 325 | // Skill discovery: scan project + global locations + built-in | ||
| 322 | // skills shipped with the binary. If any are found, register | 326 | // skills shipped with the binary. If any are found, register | ||
| 323 | // the on-demand `skill` loader tool plus a system-prompt | 327 | // the on-demand `skill` loader tool plus a system-prompt | ||
| @@ -362,10 +366,11 @@ func Resolve(args Args, requireCred bool) (Resolved, error) { | |||||
| 362 | } | 366 | } | ||
| 363 | 367 | ||||
| 364 | sys := BuildSystemPrompt(SystemPromptOpts{ | 368 | sys := BuildSystemPrompt(SystemPromptOpts{ | ||
| 365 | - | CWD: args.CWD, | 369 | + | CWD: args.CWD, |
| 366 | - | Tools: summaries, | 370 | + | Tools: summaries, |
| 367 | - | Custom: custom, | 371 | + | Custom: custom, |
| 368 | - | Append: append_, | 372 | + | Append: append_, |
| 373 | + | ZotDocsDir: docsDir, | |||
| 369 | }) | 374 | }) | ||
| 370 | 375 | ||||
| 371 | reasoning := firstNonEmpty(args.Reasoning, cfg.Reasoning) | 376 | reasoning := firstNonEmpty(args.Reasoning, cfg.Reasoning) | ||
M internal/agent/systemprompt.go +12 -5
| @@ -19,11 +19,12 @@ type ToolSummary struct { | |||
| 19 | 19 | ||
| 20 | 20 | // SystemPromptOpts configures BuildSystemPrompt. | |
| 21 | 21 | type SystemPromptOpts struct { | |
| 22 | - | CWD string | |
| 23 | - | Tools []ToolSummary | |
| 24 | - | Custom string // if set, replaces the default identity entirely | |
| 25 | - | Append []string // extra text appended at the end | |
| 26 | - | Now time.Time | |
| 22 | + | CWD string | |
| 23 | + | Tools []ToolSummary | |
| 24 | + | Custom string // if set, replaces the default identity entirely | |
| 25 | + | Append []string // extra text appended at the end | |
| 26 | + | Now time.Time | |
| 27 | + | ZotDocsDir string | |
| 27 | 28 | } | |
| 28 | 29 | ||
| 29 | 30 | // BuildSystemPrompt constructs the system prompt. | |
| @@ -63,6 +64,12 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||
| 63 | 64 | sb.WriteString(defaultIdentity) | |
| 64 | 65 | } | |
| 65 | 66 | ||
| 67 | + | if strings.TrimSpace(o.ZotDocsDir) != "" { | |
| 68 | + | sb.WriteString("\n\nZot's own docs are installed under ") | |
| 69 | + | sb.WriteString(o.ZotDocsDir) | |
| 70 | + | sb.WriteString("; use the read tool there when you need details about zot RPC, extensions, skills, or built-in behaviour.") | |
| 71 | + | } | |
| 72 | + | ||
| 66 | 73 | for _, a := range o.Append { | |
| 67 | 74 | if strings.TrimSpace(a) == "" { | |
| 68 | 75 | continue | |
| @@ -19,11 +19,12 @@ type ToolSummary struct { | |||||
| 19 | 19 | ||||
| 20 | // SystemPromptOpts configures BuildSystemPrompt. | 20 | // SystemPromptOpts configures BuildSystemPrompt. | ||
| 21 | type SystemPromptOpts struct { | 21 | type SystemPromptOpts struct { | ||
| 22 | - | CWD string | 22 | + | CWD string |
| 23 | - | Tools []ToolSummary | 23 | + | Tools []ToolSummary |
| 24 | - | Custom string // if set, replaces the default identity entirely | 24 | + | Custom string // if set, replaces the default identity entirely |
| 25 | - | Append []string // extra text appended at the end | 25 | + | Append []string // extra text appended at the end |
| 26 | - | Now time.Time | 26 | + | Now time.Time |
| 27 | + | ZotDocsDir string | |||
| 27 | } | 28 | } | ||
| 28 | 29 | ||||
| 29 | // BuildSystemPrompt constructs the system prompt. | 30 | // BuildSystemPrompt constructs the system prompt. | ||
| @@ -63,6 +64,12 @@ func BuildSystemPrompt(o SystemPromptOpts) string { | |||||
| 63 | sb.WriteString(defaultIdentity) | 64 | sb.WriteString(defaultIdentity) | ||
| 64 | } | 65 | } | ||
| 65 | 66 | ||||
| 67 | + | if strings.TrimSpace(o.ZotDocsDir) != "" { | |||
| 68 | + | sb.WriteString("\n\nZot's own docs are installed under ") | |||
| 69 | + | sb.WriteString(o.ZotDocsDir) | |||
| 70 | + | sb.WriteString("; use the read tool there when you need details about zot RPC, extensions, skills, or built-in behaviour.") | |||
| 71 | + | } | |||
| 72 | + | ||||
| 66 | for _, a := range o.Append { | 73 | for _, a := range o.Append { | ||
| 67 | if strings.TrimSpace(a) == "" { | 74 | if strings.TrimSpace(a) == "" { | ||
| 68 | continue | 75 | continue | ||