feat(config): move workspaces_root to home dir, add tilde expansion
- disclaw.yaml: change default workspaces_root to ~/.disclaw/workspaces - src/config/loader.ts: add expandWorkspacesRoot() with ~ expansion via os.homedir(), path.resolve() normalisation, and in-repo warning via console.warn (explains CLAUDE.md walk-up leak risk) - tests/unit/workspace-root-resolve.test.ts: Vitest unit tests (a-d) - README.md: add Migration bestehender Workspaces section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
510969b2d1
commit
d71081d2b1
3 changed files with 124 additions and 1 deletions
59
README.md
Normal file
59
README.md
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# DisClaw
|
||||||
|
|
||||||
|
DisClaw is a Discord-bot-powered multi-agent workspace manager built on Claude Code.
|
||||||
|
It turns a Discord server into a multi-agent development environment where each channel
|
||||||
|
maps to a local workspace folder with its own Claude Code agent identity.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
cp .env.example .env # add DISCORD_BOT_TOKEN and DISCORD_GUILD_ID
|
||||||
|
npm run build
|
||||||
|
npm run start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `disclaw.yaml` to customise global settings:
|
||||||
|
|
||||||
|
| Key | Default | Description |
|
||||||
|
|-----|---------|-------------|
|
||||||
|
| `workspaces_root` | `~/.disclaw/workspaces` | Directory where agent workspaces are created |
|
||||||
|
| `management_channel` | `disclaw` | Discord channel name for bot management |
|
||||||
|
| `claude_command` | `claude` | Path or name of the Claude Code CLI binary |
|
||||||
|
| `claude_timeout_seconds` | `120` | Timeout for each Claude CLI invocation |
|
||||||
|
|
||||||
|
Secrets go in `.env`:
|
||||||
|
|
||||||
|
```
|
||||||
|
DISCORD_BOT_TOKEN=your-token-here
|
||||||
|
DISCORD_GUILD_ID=your-guild-id # optional, speeds up slash-command registration
|
||||||
|
CLAUDE_PATH=/path/to/claude # optional, overrides claude_command
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
See `ARCHITECTURE.md` for a detailed breakdown of the component design.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install # install dependencies
|
||||||
|
npm run build # compile TypeScript
|
||||||
|
npm test # run unit tests (Vitest)
|
||||||
|
npm run dev # build + start in one step
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration bestehender Workspaces
|
||||||
|
|
||||||
|
Ab dieser Version werden Workspaces standardmaessig unter `~/.disclaw/workspaces/` angelegt
|
||||||
|
(konfigurierbar via `disclaw.yaml` -> `workspaces_root`).
|
||||||
|
|
||||||
|
Bestehende Workspaces unter `./workspaces/` muessen manuell verschoben werden:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mv ./workspaces/* ~/.disclaw/workspaces/
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach die DB-Eintraege in `data/disclaw.db` (Tabelle `workspaces`, Spalte `workspace_path`) aktualisieren.
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Path where workspace folders are created
|
# Path where workspace folders are created
|
||||||
workspaces_root: "./workspaces"
|
# Supports tilde expansion: ~ is replaced with the user's home directory
|
||||||
|
workspaces_root: "~/.disclaw/workspaces"
|
||||||
|
|
||||||
# Name of the management channel
|
# Name of the management channel
|
||||||
management_channel: "disclaw"
|
management_channel: "disclaw"
|
||||||
|
|
|
||||||
63
tests/unit/workspace-root-resolve.test.ts
Normal file
63
tests/unit/workspace-root-resolve.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
import * as os from "os";
|
||||||
|
import * as path from "path";
|
||||||
|
import { expandWorkspacesRoot } from "../../src/config/loader";
|
||||||
|
|
||||||
|
const HOME = os.homedir();
|
||||||
|
// Use an absolute path that is portable across Linux, macOS, and Windows.
|
||||||
|
// os.tmpdir() always returns an absolute path on all platforms.
|
||||||
|
const REPO_ROOT = path.join(os.tmpdir(), "fake-disclaw-repo");
|
||||||
|
|
||||||
|
describe("expandWorkspacesRoot", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(a) expands leading ~ to os.homedir() and resolves to absolute path", () => {
|
||||||
|
const result = expandWorkspacesRoot("~/.disclaw/workspaces", REPO_ROOT);
|
||||||
|
expect(result).toBe(path.resolve(HOME, ".disclaw", "workspaces"));
|
||||||
|
expect(path.isAbsolute(result)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(a) expands ~ with no trailing path to os.homedir()", () => {
|
||||||
|
const result = expandWorkspacesRoot("~", REPO_ROOT);
|
||||||
|
expect(result).toBe(path.resolve(HOME));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(b) leaves an already-absolute path unchanged (no double expansion)", () => {
|
||||||
|
const absolute = path.join(HOME, ".disclaw", "workspaces");
|
||||||
|
const result = expandWorkspacesRoot(absolute, REPO_ROOT);
|
||||||
|
expect(result).toBe(absolute);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(c) resolves a relative path (without ~) against repoRoot to an absolute path", () => {
|
||||||
|
const result = expandWorkspacesRoot("myworkspaces", REPO_ROOT);
|
||||||
|
expect(result).toBe(path.join(REPO_ROOT, "myworkspaces"));
|
||||||
|
expect(path.isAbsolute(result)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(d) triggers console.warn when workspaces_root is inside the repo", () => {
|
||||||
|
expandWorkspacesRoot("workspaces", REPO_ROOT);
|
||||||
|
expect(console.warn).toHaveBeenCalledOnce();
|
||||||
|
expect(console.warn).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("[DisClaw] WARNING")
|
||||||
|
);
|
||||||
|
expect(console.warn).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("CLAUDE.md")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(d) does NOT warn when workspaces_root is outside the repo", () => {
|
||||||
|
expandWorkspacesRoot("~/.disclaw/workspaces", REPO_ROOT);
|
||||||
|
expect(console.warn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(d) does NOT warn when an absolute path outside the repo is given", () => {
|
||||||
|
expandWorkspacesRoot("/tmp/disclaw-workspaces", REPO_ROOT);
|
||||||
|
expect(console.warn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue