detached b13a9a6

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 (
88 "runtime"
99 "strings"
1010
11+ zotdocs "github.com/patriceckhart/zot"
1112 "github.com/patriceckhart/zot/internal/agent/tools"
1213 "github.com/patriceckhart/zot/internal/core"
1314 "github.com/patriceckhart/zot/internal/provider"
@@ -77,10 +78,11 @@ func (r *Resolved) MergeExtensionTools(mgr ExtensionToolSource) {
7778 // addendum is preserved by walking the existing append slice.
7879 append_ := r.systemAppend
7980 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"),
8486 })
8587 }
8688
@@ -318,6 +320,8 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
318320 sandbox := tools.NewSandbox(args.CWD)
319321 reg := buildToolRegistry(args, args.CWD, sandbox)
320322
323+ docsDir, _ := zotdocs.EnsureInstalled(ZotHome())
324+
321325 // Skill discovery: scan project + global locations + built-in
322326 // skills shipped with the binary. If any are found, register
323327 // the on-demand `skill` loader tool plus a system-prompt
@@ -362,10 +366,11 @@ func Resolve(args Args, requireCred bool) (Resolved, error) {
362366 }
363367
364368 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,
369374 })
370375
371376 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.systemAppend79 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-in325 // Skill discovery: scan project + global locations + built-in
322 // skills shipped with the binary. If any are found, register326 // skills shipped with the binary. If any are found, register
323 // the on-demand `skill` loader tool plus a system-prompt327 // 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 {
1919
2020 // SystemPromptOpts configures BuildSystemPrompt.
2121 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
2728 }
2829
2930 // BuildSystemPrompt constructs the system prompt.
@@ -63,6 +64,12 @@ func BuildSystemPrompt(o SystemPromptOpts) string {
6364 sb.WriteString(defaultIdentity)
6465 }
6566
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+
6673 for _, a := range o.Append {
6774 if strings.TrimSpace(a) == "" {
6875 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 string22+ CWD string
23- Tools []ToolSummary23+ Tools []ToolSummary
24- Custom string // if set, replaces the default identity entirely24+ Custom string // if set, replaces the default identity entirely
25- Append []string // extra text appended at the end25+ Append []string // extra text appended at the end
26- Now time.Time26+ 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 continue75 continue

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close