feat(server-mode): native-like folder browser, a developer kept to their own folder, no password minimum
- Folder browser (the Browse fallback for a remote browser): back/forward/up, a clickable breadcrumb that becomes a path field (Ctrl+L), Places (Home, the user's workspaces, recent picks), a filter, Name/Modified columns, Show hidden and New folder. A click selects; a double-click or Enter opens a folder or picks a file; Backspace goes up; typing jumps to a name. - GET /api/lanes/browse: `hidden=1` (never the dot entries at the top of a non-admin's own folder), each entry's `mtime`, the answer's `home`. - POST /api/lanes/browse/mkdir: one plain folder name inside an existing folder; a non-admin only in their own folder, none in server mode without one; under S3 a member's folder is made as the member. - scope.ownFolder falls back to server.conf's USERS_ROOT under S3, so a settings file without AGENTHUB_USERS_ROOT no longer opens the host's home to every developer. - Passwords: any non-empty password is accepted (no 8-character minimum). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -226,6 +226,37 @@ describe("a developer browses and lists branches only in their own folder", () =
|
||||
assert.equal(admin.status, 200);
|
||||
});
|
||||
|
||||
it("hidden=1 shows dot entries, never the logins at the top of their home", async () => {
|
||||
fs.mkdirSync(path.join(MIA, ".claude"), { recursive: true });
|
||||
fs.mkdirSync(path.join(MIA, "proj", ".cfg"), { recursive: true });
|
||||
const top = await call("mia", "GET", "/api/lanes/browse?hidden=1");
|
||||
assert.equal(top.status, 200, JSON.stringify(top.body));
|
||||
assert.equal(top.body.home, MIA);
|
||||
assert.ok(!top.body.entries.some((e) => e.name.startsWith(".")));
|
||||
const inner = await call(
|
||||
"mia",
|
||||
"GET",
|
||||
`/api/lanes/browse?hidden=1&path=${encodeURIComponent(path.join(MIA, "proj"))}`
|
||||
);
|
||||
assert.ok(inner.body.entries.some((e) => e.name === ".cfg"));
|
||||
});
|
||||
|
||||
it("mkdir: only in their own folder, made as them", async () => {
|
||||
const mine = await call("mia", "POST", "/api/lanes/browse/mkdir", {
|
||||
parent: MIA,
|
||||
name: "fresh",
|
||||
});
|
||||
assert.equal(mine.status, 201, JSON.stringify(mine.body));
|
||||
assert.ok(fs.statSync(path.join(MIA, "fresh")).isDirectory());
|
||||
for (const parent of [BOB, path.join(MIA, "tohost")]) {
|
||||
const r = await call("mia", "POST", "/api/lanes/browse/mkdir", { parent, name: "x" });
|
||||
assert.equal(r.status, 403, `${parent}: ${JSON.stringify(r.body)}`);
|
||||
assert.equal(r.body.error.code, "EFOLDER");
|
||||
}
|
||||
assert.ok(!fs.existsSync(path.join(BOB, "x")));
|
||||
assert.ok(!fs.existsSync(path.join(HOSTDIR, "x")));
|
||||
});
|
||||
|
||||
it("branches", async () => {
|
||||
const other = await call(
|
||||
"mia",
|
||||
|
||||
Reference in New Issue
Block a user