From 201d63ee1ea2a9a03df1fd51fc079f582c563d86 Mon Sep 17 00:00:00 2001 From: iRedTea Date: Tue, 13 Oct 2026 20:31:41 +0300 Subject: [PATCH] feat: open the file manager in the user's home directory Co-Authored-By: Claude Opus 5.5 --- internal/agent/fs.go | 39 ++++++++++++++++++++++++ internal/server/web/templates/files.html | 10 +++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/internal/agent/fs.go b/internal/agent/fs.go index d1b712d..753dd10 100644 --- a/internal/agent/fs.go +++ b/internal/agent/fs.go @@ -9,7 +9,9 @@ import ( "io" "os" "os/exec" + "os/user" "path/filepath" + "runtime" "sort" "strings" "unicode/utf8" @@ -79,8 +81,18 @@ func fsList(path string) (proto.FSListResult, error) { if path == "" { path = "/" } + // "~" / "~name" is where the file manager starts: the home directory, or + // the root when there is none to open. + home := strings.HasPrefix(path, "~") && !strings.Contains(path, "/") + if home { + path = homeDir(path[1:]) + } abs, _ := filepath.Abs(path) entries, err := os.ReadDir(abs) + if err != nil && home && abs != "/" { + abs = "/" + entries, err = os.ReadDir(abs) + } if err != nil { return proto.FSListResult{}, err } @@ -222,3 +234,30 @@ func fsZip(paths []string, dest string) (any, error) { } return map[string]string{"dest": dest}, nil } + +// homeDir returns the home directory of the named user ("" = the user the +// agent runs as), or "/" when it cannot be told. +func homeDir(name string) string { + if name != "" { + if validUserName(name) { + if u, err := user.Lookup(name); err == nil && u.HomeDir != "" { + return u.HomeDir + } + } + return "/" + } + if u, err := user.Current(); err == nil && u.HomeDir != "" { + return u.HomeDir + } + if h := os.Getenv("HOME"); h != "" { + return h + } + // A root service often has neither a passwd entry we can read nor $HOME. + if os.Geteuid() == 0 { + if runtime.GOOS == "darwin" { + return "/var/root" + } + return "/root" + } + return "/" +} diff --git a/internal/server/web/templates/files.html b/internal/server/web/templates/files.html index 3c419a5..7d03690 100644 --- a/internal/server/web/templates/files.html +++ b/internal/server/web/templates/files.html @@ -282,7 +282,15 @@ ['dragleave','drop'].forEach(x => drop.addEventListener(x, e => { e.preventDefault(); if(x!=='drop')drop.classList.remove('drag'); })); drop.addEventListener('drop', e => { drop.classList.remove('drag'); if (e.dataTransfer.files.length) uploadFiles(e.dataTransfer.files); }); - load('/'); + // Start in the home directory of the machine's user; an agent too old to + // know "~" fails that listing, so fall back to the root. + (async () => { + const r = await api('list', {path: '~' + {{.Agent.LoginUser}}}); + if (!r.ok) { load('/'); return; } + cwd = r.data.path; entries = r.data.entries || []; selected.clear(); + $('path').value = cwd; + render(); + })(); })();