chore: rename project to clawdbot

This commit is contained in:
Peter Steinberger
2026-01-04 14:32:47 +00:00
parent d48dc71fa4
commit 246adaa119
841 changed files with 4590 additions and 4328 deletions

View File

@@ -5,7 +5,7 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-config-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-config-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
@@ -60,10 +60,10 @@ describe("config identity defaults", () => {
it("derives mentionPatterns when identity is set", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" },
@@ -89,10 +89,10 @@ describe("config identity defaults", () => {
it("does not override explicit values", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
identity: {
@@ -124,14 +124,14 @@ describe("config identity defaults", () => {
it("supports provider textChunkLimit config", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
messages: {
messagePrefix: "[clawdis]",
messagePrefix: "[clawdbot]",
responsePrefix: "🦞",
// legacy field should be ignored (moved to providers)
textChunkLimit: 9999,
@@ -167,10 +167,10 @@ describe("config identity defaults", () => {
it("respects empty responsePrefix to disable identity defaults", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" },
@@ -193,10 +193,10 @@ describe("config identity defaults", () => {
it("does not synthesize agent/session when absent", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
identity: { name: "Samantha", theme: "helpful sloth", emoji: "🦥" },
@@ -224,10 +224,10 @@ describe("config identity defaults", () => {
it("does not derive responsePrefix from identity emoji", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
identity: { name: "Clawd", theme: "space lobster", emoji: "🦞" },
@@ -262,10 +262,10 @@ describe("config discord", () => {
it("loads discord guild map + dm group settings", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify(
{
discord: {
@@ -309,29 +309,29 @@ describe("config discord", () => {
describe("Nix integration (U3, U5, U9)", () => {
describe("U3: isNixMode env var detection", () => {
it("isNixMode is false when CLAWDIS_NIX_MODE is not set", async () => {
await withEnvOverride({ CLAWDIS_NIX_MODE: undefined }, async () => {
it("isNixMode is false when CLAWDBOT_NIX_MODE is not set", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: undefined }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is false when CLAWDIS_NIX_MODE is empty", async () => {
await withEnvOverride({ CLAWDIS_NIX_MODE: "" }, async () => {
it("isNixMode is false when CLAWDBOT_NIX_MODE is empty", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is false when CLAWDIS_NIX_MODE is not '1'", async () => {
await withEnvOverride({ CLAWDIS_NIX_MODE: "true" }, async () => {
it("isNixMode is false when CLAWDBOT_NIX_MODE is not '1'", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "true" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(false);
});
});
it("isNixMode is true when CLAWDIS_NIX_MODE=1", async () => {
await withEnvOverride({ CLAWDIS_NIX_MODE: "1" }, async () => {
it("isNixMode is true when CLAWDBOT_NIX_MODE=1", async () => {
await withEnvOverride({ CLAWDBOT_NIX_MODE: "1" }, async () => {
const { isNixMode } = await import("./config.js");
expect(isNixMode).toBe(true);
});
@@ -339,52 +339,52 @@ describe("Nix integration (U3, U5, U9)", () => {
});
describe("U5: CONFIG_PATH and STATE_DIR env var overrides", () => {
it("STATE_DIR_CLAWDIS defaults to ~/.clawdis when env not set", async () => {
await withEnvOverride({ CLAWDIS_STATE_DIR: undefined }, async () => {
const { STATE_DIR_CLAWDIS } = await import("./config.js");
expect(STATE_DIR_CLAWDIS).toMatch(/\.clawdis$/);
it("STATE_DIR_CLAWDBOT defaults to ~/.clawdbot when env not set", async () => {
await withEnvOverride({ CLAWDBOT_STATE_DIR: undefined }, async () => {
const { STATE_DIR_CLAWDBOT } = await import("./config.js");
expect(STATE_DIR_CLAWDBOT).toMatch(/\.clawdbot$/);
});
});
it("STATE_DIR_CLAWDIS respects CLAWDIS_STATE_DIR override", async () => {
it("STATE_DIR_CLAWDBOT respects CLAWDBOT_STATE_DIR override", async () => {
await withEnvOverride(
{ CLAWDIS_STATE_DIR: "/custom/state/dir" },
{ CLAWDBOT_STATE_DIR: "/custom/state/dir" },
async () => {
const { STATE_DIR_CLAWDIS } = await import("./config.js");
expect(STATE_DIR_CLAWDIS).toBe("/custom/state/dir");
const { STATE_DIR_CLAWDBOT } = await import("./config.js");
expect(STATE_DIR_CLAWDBOT).toBe("/custom/state/dir");
},
);
});
it("CONFIG_PATH_CLAWDIS defaults to ~/.clawdis/clawdis.json when env not set", async () => {
it("CONFIG_PATH_CLAWDBOT defaults to ~/.clawdbot/clawdbot.json when env not set", async () => {
await withEnvOverride(
{ CLAWDIS_CONFIG_PATH: undefined, CLAWDIS_STATE_DIR: undefined },
{ CLAWDBOT_CONFIG_PATH: undefined, CLAWDBOT_STATE_DIR: undefined },
async () => {
const { CONFIG_PATH_CLAWDIS } = await import("./config.js");
expect(CONFIG_PATH_CLAWDIS).toMatch(/\.clawdis\/clawdis\.json$/);
const { CONFIG_PATH_CLAWDBOT } = await import("./config.js");
expect(CONFIG_PATH_CLAWDBOT).toMatch(/\.clawdbot\/clawdbot\.json$/);
},
);
});
it("CONFIG_PATH_CLAWDIS respects CLAWDIS_CONFIG_PATH override", async () => {
it("CONFIG_PATH_CLAWDBOT respects CLAWDBOT_CONFIG_PATH override", async () => {
await withEnvOverride(
{ CLAWDIS_CONFIG_PATH: "/nix/store/abc/clawdis.json" },
{ CLAWDBOT_CONFIG_PATH: "/nix/store/abc/clawdbot.json" },
async () => {
const { CONFIG_PATH_CLAWDIS } = await import("./config.js");
expect(CONFIG_PATH_CLAWDIS).toBe("/nix/store/abc/clawdis.json");
const { CONFIG_PATH_CLAWDBOT } = await import("./config.js");
expect(CONFIG_PATH_CLAWDBOT).toBe("/nix/store/abc/clawdbot.json");
},
);
});
it("CONFIG_PATH_CLAWDIS uses STATE_DIR_CLAWDIS when only state dir is overridden", async () => {
it("CONFIG_PATH_CLAWDBOT uses STATE_DIR_CLAWDBOT when only state dir is overridden", async () => {
await withEnvOverride(
{
CLAWDIS_CONFIG_PATH: undefined,
CLAWDIS_STATE_DIR: "/custom/state",
CLAWDBOT_CONFIG_PATH: undefined,
CLAWDBOT_STATE_DIR: "/custom/state",
},
async () => {
const { CONFIG_PATH_CLAWDIS } = await import("./config.js");
expect(CONFIG_PATH_CLAWDIS).toBe("/custom/state/clawdis.json");
const { CONFIG_PATH_CLAWDBOT } = await import("./config.js");
expect(CONFIG_PATH_CLAWDBOT).toBe("/custom/state/clawdbot.json");
},
);
});
@@ -392,7 +392,7 @@ describe("Nix integration (U3, U5, U9)", () => {
describe("U6: gateway port resolution", () => {
it("uses default when env and config are unset", async () => {
await withEnvOverride({ CLAWDIS_GATEWAY_PORT: undefined }, async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: undefined }, async () => {
const { DEFAULT_GATEWAY_PORT, resolveGatewayPort } = await import(
"./config.js"
);
@@ -400,15 +400,15 @@ describe("Nix integration (U3, U5, U9)", () => {
});
});
it("prefers CLAWDIS_GATEWAY_PORT over config", async () => {
await withEnvOverride({ CLAWDIS_GATEWAY_PORT: "19001" }, async () => {
it("prefers CLAWDBOT_GATEWAY_PORT over config", async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: "19001" }, async () => {
const { resolveGatewayPort } = await import("./config.js");
expect(resolveGatewayPort({ gateway: { port: 19002 } })).toBe(19001);
});
});
it("falls back to config when env is invalid", async () => {
await withEnvOverride({ CLAWDIS_GATEWAY_PORT: "nope" }, async () => {
await withEnvOverride({ CLAWDBOT_GATEWAY_PORT: "nope" }, async () => {
const { resolveGatewayPort } = await import("./config.js");
expect(resolveGatewayPort({ gateway: { port: 19003 } })).toBe(19003);
});
@@ -418,10 +418,10 @@ describe("Nix integration (U3, U5, U9)", () => {
describe("U9: telegram.tokenFile schema validation", () => {
it("accepts config with only botToken", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify({
telegram: { botToken: "123:ABC" },
}),
@@ -438,10 +438,10 @@ describe("Nix integration (U3, U5, U9)", () => {
it("accepts config with only tokenFile", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify({
telegram: { tokenFile: "/run/agenix/telegram-token" },
}),
@@ -458,10 +458,10 @@ describe("Nix integration (U3, U5, U9)", () => {
it("accepts config with both botToken and tokenFile", async () => {
await withTempHome(async (home) => {
const configDir = path.join(home, ".clawdis");
const configDir = path.join(home, ".clawdbot");
await fs.mkdir(configDir, { recursive: true });
await fs.writeFile(
path.join(configDir, "clawdis.json"),
path.join(configDir, "clawdbot.json"),
JSON.stringify({
telegram: {
botToken: "fallback:token",
@@ -643,7 +643,7 @@ describe("legacy config detection", () => {
it("surfaces legacy issues in snapshot", async () => {
await withTempHome(async (home) => {
const configPath = path.join(home, ".clawdis", "clawdis.json");
const configPath = path.join(home, ".clawdbot", "clawdbot.json");
await fs.mkdir(path.dirname(configPath), { recursive: true });
await fs.writeFile(
configPath,

View File

@@ -9,4 +9,4 @@ export { migrateLegacyConfig } from "./legacy-migrate.js";
export * from "./paths.js";
export * from "./types.js";
export { validateConfigObject } from "./validation.js";
export { ClawdisSchema } from "./zod-schema.js";
export { ClawdbotSchema } from "./zod-schema.js";

View File

@@ -1,5 +1,5 @@
import { resolveTalkApiKey } from "./talk.js";
import type { ClawdisConfig } from "./types.js";
import type { ClawdbotConfig } from "./types.js";
type WarnState = { warned: boolean };
@@ -14,7 +14,7 @@ function escapeRegExp(text: string): string {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function applyIdentityDefaults(cfg: ClawdisConfig): ClawdisConfig {
export function applyIdentityDefaults(cfg: ClawdbotConfig): ClawdbotConfig {
const identity = cfg.identity;
if (!identity) return cfg;
@@ -24,7 +24,7 @@ export function applyIdentityDefaults(cfg: ClawdisConfig): ClawdisConfig {
const groupChat = routing.groupChat ?? {};
let mutated = false;
const next: ClawdisConfig = { ...cfg };
const next: ClawdbotConfig = { ...cfg };
if (name && !groupChat.mentionPatterns) {
const parts = name.split(/\s+/).filter(Boolean).map(escapeRegExp);
@@ -41,9 +41,9 @@ export function applyIdentityDefaults(cfg: ClawdisConfig): ClawdisConfig {
}
export function applySessionDefaults(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
options: SessionDefaultsOptions = {},
): ClawdisConfig {
): ClawdbotConfig {
const session = cfg.session;
if (!session || session.mainKey === undefined) return cfg;
@@ -51,7 +51,7 @@ export function applySessionDefaults(
const warn = options.warn ?? console.warn;
const warnState = options.warnState ?? defaultWarnState;
const next: ClawdisConfig = {
const next: ClawdbotConfig = {
...cfg,
session: { ...session, mainKey: "main" },
};
@@ -64,7 +64,7 @@ export function applySessionDefaults(
return next;
}
export function applyTalkApiKey(config: ClawdisConfig): ClawdisConfig {
export function applyTalkApiKey(config: ClawdbotConfig): ClawdbotConfig {
const resolved = resolveTalkApiKey();
if (!resolved) return config;
const existing = config.talk?.apiKey?.trim();

View File

@@ -11,17 +11,17 @@ import {
} from "./defaults.js";
import { findLegacyConfigIssues } from "./legacy.js";
import {
CONFIG_PATH_CLAWDIS,
CONFIG_PATH_CLAWDBOT,
resolveConfigPath,
resolveStateDir,
} from "./paths.js";
import type {
ClawdisConfig,
ClawdbotConfig,
ConfigFileSnapshot,
LegacyConfigIssue,
} from "./types.js";
import { validateConfigObject } from "./validation.js";
import { ClawdisSchema } from "./zod-schema.js";
import { ClawdbotSchema } from "./zod-schema.js";
export type ParseConfigJson5Result =
| { ok: true; parsed: unknown }
@@ -67,13 +67,13 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
const deps = normalizeDeps(overrides);
const configPath = resolveConfigPathForDeps(deps);
function loadConfig(): ClawdisConfig {
function loadConfig(): ClawdbotConfig {
try {
if (!deps.fs.existsSync(configPath)) return {};
const raw = deps.fs.readFileSync(configPath, "utf-8");
const parsed = deps.json5.parse(raw);
if (typeof parsed !== "object" || parsed === null) return {};
const validated = ClawdisSchema.safeParse(parsed);
const validated = ClawdbotSchema.safeParse(parsed);
if (!validated.success) {
deps.logger.error("Invalid config:");
for (const iss of validated.error.issues) {
@@ -82,7 +82,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
return {};
}
return applySessionDefaults(
applyIdentityDefaults(validated.data as ClawdisConfig),
applyIdentityDefaults(validated.data as ClawdbotConfig),
);
} catch (err) {
deps.logger.error(`Failed to read config at ${configPath}`, err);
@@ -165,7 +165,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
}
}
async function writeConfigFile(cfg: ClawdisConfig) {
async function writeConfigFile(cfg: ClawdbotConfig) {
await deps.fs.promises.mkdir(path.dirname(configPath), {
recursive: true,
});
@@ -181,7 +181,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
};
}
const defaultIO = createConfigIO({ configPath: CONFIG_PATH_CLAWDIS });
const defaultIO = createConfigIO({ configPath: CONFIG_PATH_CLAWDBOT });
export const loadConfig = defaultIO.loadConfig;
export const readConfigFileSnapshot = defaultIO.readConfigFileSnapshot;

View File

@@ -1,9 +1,9 @@
import { applyLegacyMigrations } from "./legacy.js";
import type { ClawdisConfig } from "./types.js";
import type { ClawdbotConfig } from "./types.js";
import { validateConfigObject } from "./validation.js";
export function migrateLegacyConfig(raw: unknown): {
config: ClawdisConfig | null;
config: ClawdbotConfig | null;
changes: string[];
} {
const { next, changes } = applyLegacyMigrations(raw);

View File

@@ -15,17 +15,17 @@ const LEGACY_CONFIG_RULES: LegacyConfigRule[] = [
{
path: ["routing", "allowFrom"],
message:
"routing.allowFrom was removed; use whatsapp.allowFrom instead (run `clawdis doctor` to migrate).",
"routing.allowFrom was removed; use whatsapp.allowFrom instead (run `clawdbot doctor` to migrate).",
},
{
path: ["routing", "groupChat", "requireMention"],
message:
'routing.groupChat.requireMention was removed; use whatsapp/telegram/imessage groups defaults (e.g. whatsapp.groups."*".requireMention) instead (run `clawdis doctor` to migrate).',
'routing.groupChat.requireMention was removed; use whatsapp/telegram/imessage groups defaults (e.g. whatsapp.groups."*".requireMention) instead (run `clawdbot doctor` to migrate).',
},
{
path: ["telegram", "requireMention"],
message:
'telegram.requireMention was removed; use telegram.groups."*".requireMention instead (run `clawdis doctor` to migrate).',
'telegram.requireMention was removed; use telegram.groups."*".requireMention instead (run `clawdbot doctor` to migrate).',
},
];

View File

@@ -1,10 +1,10 @@
import os from "node:os";
import path from "node:path";
import type { ClawdisConfig } from "./types.js";
import type { ClawdbotConfig } from "./types.js";
/**
* Nix mode detection: When CLAWDIS_NIX_MODE=1, the gateway is running under Nix.
* Nix mode detection: When CLAWDBOT_NIX_MODE=1, the gateway is running under Nix.
* In this mode:
* - No auto-install flows should be attempted
* - Missing dependencies should produce actionable Nix-specific error messages
@@ -13,50 +13,50 @@ import type { ClawdisConfig } from "./types.js";
export function resolveIsNixMode(
env: NodeJS.ProcessEnv = process.env,
): boolean {
return env.CLAWDIS_NIX_MODE === "1";
return env.CLAWDBOT_NIX_MODE === "1";
}
export const isNixMode = resolveIsNixMode();
/**
* State directory for mutable data (sessions, logs, caches).
* Can be overridden via CLAWDIS_STATE_DIR environment variable.
* Default: ~/.clawdis
* Can be overridden via CLAWDBOT_STATE_DIR environment variable.
* Default: ~/.clawdbot
*/
export function resolveStateDir(
env: NodeJS.ProcessEnv = process.env,
homedir: () => string = os.homedir,
): string {
const override = env.CLAWDIS_STATE_DIR?.trim();
const override = env.CLAWDBOT_STATE_DIR?.trim();
if (override) return override;
return path.join(homedir(), ".clawdis");
return path.join(homedir(), ".clawdbot");
}
export const STATE_DIR_CLAWDIS = resolveStateDir();
export const STATE_DIR_CLAWDBOT = resolveStateDir();
/**
* Config file path (JSON5).
* Can be overridden via CLAWDIS_CONFIG_PATH environment variable.
* Default: ~/.clawdis/clawdis.json (or $CLAWDIS_STATE_DIR/clawdis.json)
* Can be overridden via CLAWDBOT_CONFIG_PATH environment variable.
* Default: ~/.clawdbot/clawdbot.json (or $CLAWDBOT_STATE_DIR/clawdbot.json)
*/
export function resolveConfigPath(
env: NodeJS.ProcessEnv = process.env,
stateDir: string = resolveStateDir(env, os.homedir),
): string {
const override = env.CLAWDIS_CONFIG_PATH?.trim();
const override = env.CLAWDBOT_CONFIG_PATH?.trim();
if (override) return override;
return path.join(stateDir, "clawdis.json");
return path.join(stateDir, "clawdbot.json");
}
export const CONFIG_PATH_CLAWDIS = resolveConfigPath();
export const CONFIG_PATH_CLAWDBOT = resolveConfigPath();
export const DEFAULT_GATEWAY_PORT = 18789;
export function resolveGatewayPort(
cfg?: ClawdisConfig,
cfg?: ClawdbotConfig,
env: NodeJS.ProcessEnv = process.env,
): number {
const envRaw = env.CLAWDIS_GATEWAY_PORT?.trim();
const envRaw = env.CLAWDBOT_GATEWAY_PORT?.trim();
if (envRaw) {
const parsed = Number.parseInt(envRaw, 10);
if (Number.isFinite(parsed) && parsed > 0) return parsed;

View File

@@ -1,5 +1,5 @@
import { VERSION } from "../version.js";
import { ClawdisSchema } from "./zod-schema.js";
import { ClawdbotSchema } from "./zod-schema.js";
export type ConfigUiHint = {
label?: string;
@@ -14,7 +14,7 @@ export type ConfigUiHint = {
export type ConfigUiHints = Record<string, ConfigUiHint>;
export type ConfigSchema = ReturnType<typeof ClawdisSchema.toJSONSchema>;
export type ConfigSchema = ReturnType<typeof ClawdbotSchema.toJSONSchema>;
export type ConfigSchemaResponse = {
schema: ConfigSchema;
@@ -106,7 +106,7 @@ const FIELD_HELP: Record<string, string> = {
"Required for multi-machine access or non-loopback binds.",
"gateway.auth.password": "Required for Tailscale funnel.",
"gateway.controlUi.basePath":
"Optional URL prefix where the Control UI is served (e.g. /clawdis).",
"Optional URL prefix where the Control UI is served (e.g. /clawdbot).",
"gateway.reload.mode":
'Hot reload strategy for config changes ("hybrid" recommended).',
"gateway.reload.debounceMs":
@@ -117,7 +117,7 @@ const FIELD_HELP: Record<string, string> = {
const FIELD_PLACEHOLDERS: Record<string, string> = {
"gateway.remote.url": "ws://host:18789",
"gateway.controlUi.basePath": "/clawdis",
"gateway.controlUi.basePath": "/clawdbot",
};
const SENSITIVE_PATTERNS = [/token/i, /password/i, /secret/i, /api.?key/i];
@@ -164,11 +164,11 @@ let cached: ConfigSchemaResponse | null = null;
export function buildConfigSchema(): ConfigSchemaResponse {
if (cached) return cached;
const schema = ClawdisSchema.toJSONSchema({
const schema = ClawdbotSchema.toJSONSchema({
target: "draft-07",
unrepresentable: "any",
});
schema.title = "ClawdisConfig";
schema.title = "ClawdbotConfig";
const hints = applySensitiveHints(buildBaseHints());
const next = {
schema,

View File

@@ -95,7 +95,7 @@ describe("sessions", () => {
});
it("updateLastRoute persists channel and target", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-sessions-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-sessions-"));
const storePath = path.join(dir, "sessions.json");
await fs.writeFile(
storePath,

View File

@@ -84,7 +84,7 @@ export type SessionSkillSnapshot = {
};
export function resolveSessionTranscriptsDir(): string {
return path.join(os.homedir(), ".clawdis", "sessions");
return path.join(os.homedir(), ".clawdbot", "sessions");
}
export function resolveDefaultSessionStorePath(): string {

View File

@@ -443,7 +443,7 @@ export type RoutingConfig = {
};
export type MessagesConfig = {
messagePrefix?: string; // Prefix added to all inbound messages (default: "[clawdis]" if no allowFrom, else "")
messagePrefix?: string; // Prefix added to all inbound messages (default: "[clawdbot]" if no allowFrom, else "")
responsePrefix?: string; // Prefix auto-added to all outbound replies (e.g., "🦞")
timestampPrefix?: boolean | string; // true/false or IANA timezone string (default: true with UTC)
};
@@ -499,7 +499,7 @@ export type TalkConfig = {
export type GatewayControlUiConfig = {
/** If false, the Gateway will not serve the Control UI (default /). */
enabled?: boolean;
/** Optional base path prefix for the Control UI (e.g. "/clawdis"). */
/** Optional base path prefix for the Control UI (e.g. "/clawdbot"). */
basePath?: string;
};
@@ -636,7 +636,7 @@ export type ModelsConfig = {
providers?: Record<string, ModelProviderConfig>;
};
export type ClawdisConfig = {
export type ClawdbotConfig = {
identity?: {
name?: string;
theme?: string;
@@ -652,7 +652,7 @@ export type ClawdisConfig = {
logging?: LoggingConfig;
browser?: BrowserConfig;
ui?: {
/** Accent color for Clawdis UI chrome (hex). */
/** Accent color for Clawdbot UI chrome (hex). */
seamColor?: string;
};
skills?: SkillsConfig;
@@ -761,6 +761,30 @@ export type ClawdisConfig = {
env?: Record<string, string>;
/** Optional setup command run once after container creation. */
setupCommand?: string;
/** Limit container PIDs (0 = Docker default). */
pidsLimit?: number;
/** Limit container memory (e.g. 512m, 2g, or bytes as number). */
memory?: string | number;
/** Limit container memory swap (same format as memory). */
memorySwap?: string | number;
/** Limit container CPU shares (e.g. 0.5, 1, 2). */
cpus?: number;
/**
* Set ulimit values by name (e.g. nofile, nproc).
* Use "soft:hard" string, a number, or { soft, hard }.
*/
ulimits?: Record<
string,
string | number | { soft?: number; hard?: number }
>;
/** Seccomp profile (path or profile name). */
seccompProfile?: string;
/** AppArmor profile name. */
apparmorProfile?: string;
/** DNS servers (e.g. ["1.1.1.1", "8.8.8.8"]). */
dns?: string[];
/** Extra host mappings (e.g. ["api.local:10.0.0.2"]). */
extraHosts?: string[];
};
/** Optional sandboxed browser settings. */
browser?: {
@@ -822,7 +846,7 @@ export type ConfigFileSnapshot = {
raw: string | null;
parsed: unknown;
valid: boolean;
config: ClawdisConfig;
config: ClawdbotConfig;
issues: ConfigValidationIssue[];
legacyIssues: LegacyConfigIssue[];
};

View File

@@ -1,12 +1,12 @@
import { applyIdentityDefaults, applySessionDefaults } from "./defaults.js";
import { findLegacyConfigIssues } from "./legacy.js";
import type { ClawdisConfig, ConfigValidationIssue } from "./types.js";
import { ClawdisSchema } from "./zod-schema.js";
import type { ClawdbotConfig, ConfigValidationIssue } from "./types.js";
import { ClawdbotSchema } from "./zod-schema.js";
export function validateConfigObject(
raw: unknown,
):
| { ok: true; config: ClawdisConfig }
| { ok: true; config: ClawdbotConfig }
| { ok: false; issues: ConfigValidationIssue[] } {
const legacyIssues = findLegacyConfigIssues(raw);
if (legacyIssues.length > 0) {
@@ -18,7 +18,7 @@ export function validateConfigObject(
})),
};
}
const validated = ClawdisSchema.safeParse(raw);
const validated = ClawdbotSchema.safeParse(raw);
if (!validated.success) {
return {
ok: false,
@@ -31,7 +31,7 @@ export function validateConfigObject(
return {
ok: true,
config: applySessionDefaults(
applyIdentityDefaults(validated.data as ClawdisConfig),
applyIdentityDefaults(validated.data as ClawdbotConfig),
),
};
}

View File

@@ -273,7 +273,7 @@ const HooksGmailSchema = z
})
.optional();
export const ClawdisSchema = z.object({
export const ClawdbotSchema = z.object({
identity: z
.object({
name: z.string().optional(),
@@ -444,6 +444,27 @@ export const ClawdisSchema = z.object({
capDrop: z.array(z.string()).optional(),
env: z.record(z.string(), z.string()).optional(),
setupCommand: z.string().optional(),
pidsLimit: z.number().int().positive().optional(),
memory: z.union([z.string(), z.number()]).optional(),
memorySwap: z.union([z.string(), z.number()]).optional(),
cpus: z.number().positive().optional(),
ulimits: z
.record(
z.string(),
z.union([
z.string(),
z.number(),
z.object({
soft: z.number().int().nonnegative().optional(),
hard: z.number().int().nonnegative().optional(),
}),
]),
)
.optional(),
seccompProfile: z.string().optional(),
apparmorProfile: z.string().optional(),
dns: z.array(z.string()).optional(),
extraHosts: z.array(z.string()).optional(),
})
.optional(),
browser: z