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
+5 -5
View File
@@ -4,17 +4,17 @@ import { CONFIG_DIR, resolveUserPath } from "../utils.js";
const DEFAULT_AGENT_DIR = path.join(CONFIG_DIR, "agent");
export function resolveClawdisAgentDir(): string {
export function resolveClawdbotAgentDir(): string {
const override =
process.env.CLAWDIS_AGENT_DIR?.trim() ||
process.env.CLAWDBOT_AGENT_DIR?.trim() ||
process.env.PI_CODING_AGENT_DIR?.trim() ||
DEFAULT_AGENT_DIR;
return resolveUserPath(override);
}
export function ensureClawdisAgentEnv(): string {
const dir = resolveClawdisAgentDir();
if (!process.env.CLAWDIS_AGENT_DIR) process.env.CLAWDIS_AGENT_DIR = dir;
export function ensureClawdbotAgentEnv(): string {
const dir = resolveClawdbotAgentDir();
if (!process.env.CLAWDBOT_AGENT_DIR) process.env.CLAWDBOT_AGENT_DIR = dir;
if (!process.env.PI_CODING_AGENT_DIR) process.env.PI_CODING_AGENT_DIR = dir;
return dir;
}
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("gateway tool", () => {
it("schedules SIGUSR1 restart", async () => {
@@ -8,7 +8,7 @@ describe("gateway tool", () => {
const kill = vi.spyOn(process, "kill").mockImplementation(() => true);
try {
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "gateway",
);
expect(tool).toBeDefined();
@@ -10,7 +10,7 @@ vi.mock("../media/image-ops.js", () => ({
resizeToJpeg: vi.fn(async () => Buffer.from("jpeg")),
}));
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("nodes camera_snap", () => {
beforeEach(() => {
@@ -35,7 +35,7 @@ describe("nodes camera_snap", () => {
throw new Error(`unexpected method: ${String(method)}`);
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "nodes",
);
if (!tool) throw new Error("missing nodes tool");
@@ -75,7 +75,7 @@ describe("nodes camera_snap", () => {
throw new Error(`unexpected method: ${String(method)}`);
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "nodes",
);
if (!tool) throw new Error("missing nodes tool");
@@ -16,7 +16,7 @@ vi.mock("../config/config.js", () => ({
resolveGatewayPort: () => 18789,
}));
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
describe("sessions tools", () => {
it("sessions_list filters kinds and includes messages", async () => {
@@ -67,7 +67,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "sessions_list",
);
expect(tool).toBeDefined();
@@ -106,7 +106,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools().find(
const tool = createClawdbotTools().find(
(candidate) => candidate.name === "sessions_history",
);
expect(tool).toBeDefined();
@@ -190,7 +190,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools({
const tool = createClawdbotTools({
agentSessionKey: requesterKey,
agentSurface: "discord",
}).find((candidate) => candidate.name === "sessions_send");
@@ -340,7 +340,7 @@ describe("sessions tools", () => {
return {};
});
const tool = createClawdisTools({
const tool = createClawdbotTools({
agentSessionKey: requesterKey,
agentSurface: "discord",
}).find((candidate) => candidate.name === "sessions_send");
@@ -10,7 +10,7 @@ import { createSessionsListTool } from "./tools/sessions-list-tool.js";
import { createSessionsSendTool } from "./tools/sessions-send-tool.js";
import { createSlackTool } from "./tools/slack-tool.js";
export function createClawdisTools(options?: {
export function createClawdbotTools(options?: {
browserControlUrl?: string;
agentSessionKey?: string;
agentSurface?: string;
+4 -4
View File
@@ -2,8 +2,8 @@
// the agent reports a model id. This includes custom models.json entries.
import { loadConfig } from "../config/config.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
type ModelEntry = { id: string; contextWindow?: number };
@@ -14,8 +14,8 @@ const loadPromise = (async () => {
"@mariozechner/pi-coding-agent"
);
const cfg = loadConfig();
await ensureClawdisModelsJson(cfg);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(cfg);
const agentDir = resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(agentDir);
const modelRegistry = discoverModels(authStorage, agentDir);
const models = modelRegistry.getAll() as ModelEntry[];
+6 -6
View File
@@ -1,6 +1,6 @@
import { type ClawdisConfig, loadConfig } from "../config/config.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
export type ModelCatalogEntry = {
id: string;
@@ -25,7 +25,7 @@ export function resetModelCatalogCacheForTest() {
}
export async function loadModelCatalog(params?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
useCache?: boolean;
}): Promise<ModelCatalogEntry[]> {
if (params?.useCache === false) {
@@ -39,8 +39,8 @@ export async function loadModelCatalog(params?: {
const models: ModelCatalogEntry[] = [];
try {
const cfg = params?.config ?? loadConfig();
await ensureClawdisModelsJson(cfg);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(cfg);
const agentDir = resolveClawdbotAgentDir();
const authStorage = piSdk.discoverAuthStorage(agentDir);
const registry = piSdk.discoverModels(authStorage, agentDir) as
| {
+5 -5
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import { resolveConfiguredModelRef } from "./model-selection.js";
@@ -8,7 +8,7 @@ describe("resolveConfiguredModelRef", () => {
it("parses provider/model from agent.model", () => {
const cfg = {
agent: { model: "openai/gpt-4.1-mini" },
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -22,7 +22,7 @@ describe("resolveConfiguredModelRef", () => {
it("falls back to anthropic when agent.model omits provider", () => {
const cfg = {
agent: { model: "claude-opus-4-5" },
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -37,7 +37,7 @@ describe("resolveConfiguredModelRef", () => {
});
it("falls back to defaults when agent.model is missing", () => {
const cfg = {} satisfies ClawdisConfig;
const cfg = {} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
@@ -59,7 +59,7 @@ describe("resolveConfiguredModelRef", () => {
Opus: "anthropic/claude-opus-4-5",
},
},
} satisfies ClawdisConfig;
} satisfies ClawdbotConfig;
const resolved = resolveConfiguredModelRef({
cfg,
+5 -5
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { ModelCatalogEntry } from "./model-catalog.js";
export type ModelRef = {
@@ -38,7 +38,7 @@ export function parseModelRef(
}
export function buildModelAliasIndex(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
defaultProvider: string;
}): ModelAliasIndex {
const rawAliases = params.cfg.agent?.modelAliases ?? {};
@@ -84,7 +84,7 @@ export function resolveModelRefFromString(params: {
}
export function resolveConfiguredModelRef(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
defaultProvider: string;
defaultModel: string;
}): ModelRef {
@@ -108,7 +108,7 @@ export function resolveConfiguredModelRef(params: {
}
export function buildAllowedModelSet(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
catalog: ModelCatalogEntry[];
defaultProvider: string;
}): {
@@ -156,7 +156,7 @@ export function buildAllowedModelSet(params: {
}
export function resolveThinkingDefault(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
provider: string;
model: string;
catalog?: ModelCatalogEntry[];
+11 -11
View File
@@ -3,10 +3,10 @@ import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-models-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-models-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
@@ -17,7 +17,7 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
}
}
const MODELS_CONFIG: ClawdisConfig = {
const MODELS_CONFIG: ClawdbotConfig = {
models: {
providers: {
"custom-proxy": {
@@ -55,12 +55,12 @@ describe("models config", () => {
it("writes models.json for configured providers", async () => {
await withTempHome(async () => {
vi.resetModules();
const { ensureClawdisModelsJson } = await import("./models-config.js");
const { resolveClawdisAgentDir } = await import("./agent-paths.js");
const { ensureClawdbotModelsJson } = await import("./models-config.js");
const { resolveClawdbotAgentDir } = await import("./agent-paths.js");
await ensureClawdisModelsJson(MODELS_CONFIG);
await ensureClawdbotModelsJson(MODELS_CONFIG);
const modelPath = path.join(resolveClawdisAgentDir(), "models.json");
const modelPath = path.join(resolveClawdbotAgentDir(), "models.json");
const raw = await fs.readFile(modelPath, "utf8");
const parsed = JSON.parse(raw) as {
providers: Record<string, { baseUrl?: string }>;
@@ -75,10 +75,10 @@ describe("models config", () => {
it("merges providers by default", async () => {
await withTempHome(async () => {
vi.resetModules();
const { ensureClawdisModelsJson } = await import("./models-config.js");
const { resolveClawdisAgentDir } = await import("./agent-paths.js");
const { ensureClawdbotModelsJson } = await import("./models-config.js");
const { resolveClawdbotAgentDir } = await import("./agent-paths.js");
const agentDir = resolveClawdisAgentDir();
const agentDir = resolveClawdbotAgentDir();
await fs.mkdir(agentDir, { recursive: true });
await fs.writeFile(
path.join(agentDir, "models.json"),
@@ -110,7 +110,7 @@ describe("models config", () => {
"utf8",
);
await ensureClawdisModelsJson(MODELS_CONFIG);
await ensureClawdbotModelsJson(MODELS_CONFIG);
const raw = await fs.readFile(path.join(agentDir, "models.json"), "utf8");
const parsed = JSON.parse(raw) as {
+8 -8
View File
@@ -1,13 +1,13 @@
import fs from "node:fs/promises";
import path from "node:path";
import { type ClawdisConfig, loadConfig } from "../config/config.js";
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import {
ensureClawdisAgentEnv,
resolveClawdisAgentDir,
ensureClawdbotAgentEnv,
resolveClawdbotAgentDir,
} from "./agent-paths.js";
type ModelsConfig = NonNullable<ClawdisConfig["models"]>;
type ModelsConfig = NonNullable<ClawdbotConfig["models"]>;
const DEFAULT_MODE: NonNullable<ModelsConfig["mode"]> = "merge";
@@ -24,17 +24,17 @@ async function readJson(pathname: string): Promise<unknown> {
}
}
export async function ensureClawdisModelsJson(
config?: ClawdisConfig,
export async function ensureClawdbotModelsJson(
config?: ClawdbotConfig,
): Promise<{ agentDir: string; wrote: boolean }> {
const cfg = config ?? loadConfig();
const providers = cfg.models?.providers;
if (!providers || Object.keys(providers).length === 0) {
return { agentDir: resolveClawdisAgentDir(), wrote: false };
return { agentDir: resolveClawdbotAgentDir(), wrote: false };
}
const mode = cfg.models?.mode ?? DEFAULT_MODE;
const agentDir = ensureClawdisAgentEnv();
const agentDir = ensureClawdbotAgentEnv();
const targetPath = path.join(agentDir, "models.json");
let mergedProviders = providers;
+6 -6
View File
@@ -12,12 +12,12 @@ describe("buildEmbeddedSandboxInfo", () => {
const sandbox = {
enabled: true,
sessionKey: "session:test",
workspaceDir: "/tmp/clawdis-sandbox",
containerName: "clawdis-sbx-test",
workspaceDir: "/tmp/clawdbot-sandbox",
containerName: "clawdbot-sbx-test",
containerWorkdir: "/workspace",
docker: {
image: "clawdis-sandbox:bookworm-slim",
containerPrefix: "clawdis-sbx-",
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp"],
@@ -33,13 +33,13 @@ describe("buildEmbeddedSandboxInfo", () => {
browser: {
controlUrl: "http://localhost:9222",
noVncUrl: "http://localhost:6080",
containerName: "clawdis-sbx-browser-test",
containerName: "clawdbot-sbx-browser-test",
},
} satisfies SandboxContext;
expect(buildEmbeddedSandboxInfo(sandbox)).toEqual({
enabled: true,
workspaceDir: "/tmp/clawdis-sandbox",
workspaceDir: "/tmp/clawdbot-sandbox",
browserControlUrl: "http://localhost:9222",
browserNoVncUrl: "http://localhost:6080",
});
+14 -14
View File
@@ -24,7 +24,7 @@ import {
} from "@mariozechner/pi-coding-agent";
import type { ThinkLevel, VerboseLevel } from "../auto-reply/thinking.js";
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { getMachineDisplayName } from "../infra/machine-name.js";
import { createSubsystemLogger } from "../logging.js";
import { splitMediaFromOutput } from "../media/parse.js";
@@ -33,10 +33,10 @@ import {
enqueueCommandInLane,
} from "../process/command-queue.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { resolveClawdisAgentDir } from "./agent-paths.js";
import { resolveClawdbotAgentDir } from "./agent-paths.js";
import type { BashElevatedDefaults } from "./bash-tools.js";
import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "./defaults.js";
import { ensureClawdisModelsJson } from "./models-config.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
import {
buildBootstrapContextFiles,
ensureSessionHeader,
@@ -48,7 +48,7 @@ import {
subscribeEmbeddedPiSession,
} from "./pi-embedded-subscribe.js";
import { extractAssistantText } from "./pi-embedded-utils.js";
import { createClawdisCodingTools } from "./pi-tools.js";
import { createClawdbotCodingTools } from "./pi-tools.js";
import { resolveSandboxContext } from "./sandbox.js";
import {
applySkillEnvOverrides,
@@ -139,9 +139,9 @@ export function buildEmbeddedSandboxInfo(
};
}
function resolveClawdisOAuthPath(): string {
function resolveClawdbotOAuthPath(): string {
const overrideDir =
process.env.CLAWDIS_OAUTH_DIR?.trim() || DEFAULT_OAUTH_DIR;
process.env.CLAWDBOT_OAUTH_DIR?.trim() || DEFAULT_OAUTH_DIR;
return path.join(resolveUserPath(overrideDir), OAUTH_FILENAME);
}
@@ -212,7 +212,7 @@ function importLegacyOAuthIfNeeded(destPath: string): void {
function ensureOAuthStorage(): void {
if (oauthStorageConfigured) return;
oauthStorageConfigured = true;
const oauthPath = resolveClawdisOAuthPath();
const oauthPath = resolveClawdbotOAuthPath();
importLegacyOAuthIfNeeded(oauthPath);
}
@@ -298,7 +298,7 @@ export function resolveEmbeddedSessionLane(key: string) {
}
function mapThinkingLevel(level?: ThinkLevel): ThinkingLevel {
// pi-agent-core supports "xhigh" too; Clawdis doesn't surface it for now.
// pi-agent-core supports "xhigh" too; Clawdbot doesn't surface it for now.
if (!level) return "off";
return level;
}
@@ -313,7 +313,7 @@ function resolveModel(
authStorage: ReturnType<typeof discoverAuthStorage>;
modelRegistry: ReturnType<typeof discoverModels>;
} {
const resolvedAgentDir = agentDir ?? resolveClawdisAgentDir();
const resolvedAgentDir = agentDir ?? resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(resolvedAgentDir);
const modelRegistry = discoverModels(authStorage, resolvedAgentDir);
const model = modelRegistry.find(provider, modelId) as Model<Api> | null;
@@ -341,7 +341,7 @@ async function getApiKeyForModel(
const envKey = getEnvApiKey(model.provider);
if (envKey) return envKey;
if (isOAuthProvider(model.provider)) {
const oauthPath = resolveClawdisOAuthPath();
const oauthPath = resolveClawdbotOAuthPath();
const storage = loadOAuthStorageAt(oauthPath);
if (storage) {
try {
@@ -384,7 +384,7 @@ export async function runEmbeddedPiAgent(params: {
surface?: string;
sessionFile: string;
workspaceDir: string;
config?: ClawdisConfig;
config?: ClawdbotConfig;
skillsSnapshot?: SkillSnapshot;
prompt: string;
provider?: string;
@@ -436,8 +436,8 @@ export async function runEmbeddedPiAgent(params: {
const provider =
(params.provider ?? DEFAULT_PROVIDER).trim() || DEFAULT_PROVIDER;
const modelId = (params.model ?? DEFAULT_MODEL).trim() || DEFAULT_MODEL;
await ensureClawdisModelsJson(params.config);
const agentDir = resolveClawdisAgentDir();
await ensureClawdbotModelsJson(params.config);
const agentDir = resolveClawdbotAgentDir();
const { model, error, authStorage, modelRegistry } = resolveModel(
provider,
modelId,
@@ -496,7 +496,7 @@ export async function runEmbeddedPiAgent(params: {
await loadWorkspaceBootstrapFiles(resolvedWorkspace);
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
const promptSkills = resolvePromptSkills(skillsSnapshot, skillEntries);
const tools = createClawdisCodingTools({
const tools = createClawdbotCodingTools({
bash: {
...params.config?.agent?.bash,
elevated: params.bashElevated,
+19 -19
View File
@@ -4,11 +4,11 @@ import path from "node:path";
import sharp from "sharp";
import { describe, expect, it } from "vitest";
import { createClawdisCodingTools } from "./pi-tools.js";
import { createClawdbotCodingTools } from "./pi-tools.js";
describe("createClawdisCodingTools", () => {
describe("createClawdbotCodingTools", () => {
it("merges properties for union tool schemas", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const browser = tools.find((tool) => tool.name === "browser");
expect(browser).toBeDefined();
const parameters = browser?.parameters as {
@@ -24,7 +24,7 @@ describe("createClawdisCodingTools", () => {
});
it("preserves union action values in merged schema", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"];
for (const name of toolNames) {
@@ -75,33 +75,33 @@ describe("createClawdisCodingTools", () => {
});
it("includes bash and process tools", () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
expect(tools.some((tool) => tool.name === "bash")).toBe(true);
expect(tools.some((tool) => tool.name === "process")).toBe(true);
});
it("scopes discord tool to discord surface", () => {
const other = createClawdisCodingTools({ surface: "whatsapp" });
const other = createClawdbotCodingTools({ surface: "whatsapp" });
expect(other.some((tool) => tool.name === "discord")).toBe(false);
const discord = createClawdisCodingTools({ surface: "discord" });
const discord = createClawdbotCodingTools({ surface: "discord" });
expect(discord.some((tool) => tool.name === "discord")).toBe(true);
});
it("scopes slack tool to slack surface", () => {
const other = createClawdisCodingTools({ surface: "whatsapp" });
const other = createClawdbotCodingTools({ surface: "whatsapp" });
expect(other.some((tool) => tool.name === "slack")).toBe(false);
const slack = createClawdisCodingTools({ surface: "slack" });
const slack = createClawdbotCodingTools({ surface: "slack" });
expect(slack.some((tool) => tool.name === "slack")).toBe(true);
});
it("keeps read tool image metadata intact", async () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const readTool = tools.find((tool) => tool.name === "read");
expect(readTool).toBeDefined();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-read-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-read-"));
try {
const imagePath = path.join(tmpDir, "sample.png");
const png = await sharp({
@@ -137,14 +137,14 @@ describe("createClawdisCodingTools", () => {
});
it("returns text content without image blocks for text files", async () => {
const tools = createClawdisCodingTools();
const tools = createClawdbotCodingTools();
const readTool = tools.find((tool) => tool.name === "read");
expect(readTool).toBeDefined();
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-read-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-read-"));
try {
const textPath = path.join(tmpDir, "sample.txt");
const contents = "Hello from clawdis read tool.";
const contents = "Hello from clawdbot read tool.";
await fs.writeFile(textPath, contents, "utf8");
const result = await readTool?.execute("tool-2", {
@@ -171,12 +171,12 @@ describe("createClawdisCodingTools", () => {
const sandbox = {
enabled: true,
sessionKey: "sandbox:test",
workspaceDir: path.join(os.tmpdir(), "clawdis-sandbox"),
containerName: "clawdis-sbx-test",
workspaceDir: path.join(os.tmpdir(), "clawdbot-sandbox"),
containerName: "clawdbot-sbx-test",
containerWorkdir: "/workspace",
docker: {
image: "clawdis-sandbox:bookworm-slim",
containerPrefix: "clawdis-sbx-",
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: [],
@@ -190,7 +190,7 @@ describe("createClawdisCodingTools", () => {
deny: ["browser"],
},
};
const tools = createClawdisCodingTools({ sandbox });
const tools = createClawdbotCodingTools({ sandbox });
expect(tools.some((tool) => tool.name === "bash")).toBe(true);
expect(tools.some((tool) => tool.name === "read")).toBe(false);
expect(tools.some((tool) => tool.name === "browser")).toBe(false);
+6 -6
View File
@@ -16,7 +16,7 @@ import {
createProcessTool,
type ProcessToolDefaults,
} from "./bash-tools.js";
import { createClawdisTools } from "./clawdis-tools.js";
import { createClawdbotTools } from "./clawdbot-tools.js";
import type { SandboxContext, SandboxToolPolicy } from "./sandbox.js";
import { assertSandboxPath } from "./sandbox-paths.js";
import { sanitizeToolResultImages } from "./tool-images.js";
@@ -332,7 +332,7 @@ function wrapSandboxPathGuard(tool: AnyAgentTool, root: string): AnyAgentTool {
function createSandboxedReadTool(root: string) {
const base = createReadTool(root);
return wrapSandboxPathGuard(createClawdisReadTool(base), root);
return wrapSandboxPathGuard(createClawdbotReadTool(base), root);
}
function createSandboxedWriteTool(root: string) {
@@ -409,7 +409,7 @@ function createWhatsAppLoginTool(): AnyAgentTool {
};
}
function createClawdisReadTool(base: AnyAgentTool): AnyAgentTool {
function createClawdbotReadTool(base: AnyAgentTool): AnyAgentTool {
return {
...base,
execute: async (toolCallId, params, signal) => {
@@ -447,7 +447,7 @@ function shouldIncludeSlackTool(surface?: string): boolean {
return normalized === "slack" || normalized.startsWith("slack:");
}
export function createClawdisCodingTools(options?: {
export function createClawdbotCodingTools(options?: {
bash?: BashToolDefaults & ProcessToolDefaults;
surface?: string;
sandbox?: SandboxContext | null;
@@ -460,7 +460,7 @@ export function createClawdisCodingTools(options?: {
if (tool.name === readTool.name) {
return sandboxRoot
? [createSandboxedReadTool(sandboxRoot)]
: [createClawdisReadTool(tool)];
: [createClawdbotReadTool(tool)];
}
if (tool.name === bashToolName) return [];
if (sandboxRoot && (tool.name === "write" || tool.name === "edit")) {
@@ -493,7 +493,7 @@ export function createClawdisCodingTools(options?: {
bashTool as unknown as AnyAgentTool,
processTool as unknown as AnyAgentTool,
createWhatsAppLoginTool(),
...createClawdisTools({
...createClawdbotTools({
browserControlUrl: sandbox?.browser?.controlUrl,
agentSessionKey: options?.sessionKey,
agentSurface: options?.surface,
+97
View File
@@ -0,0 +1,97 @@
import { describe, expect, it } from "vitest";
import {
buildSandboxCreateArgs,
type SandboxDockerConfig,
} from "./sandbox.js";
describe("buildSandboxCreateArgs", () => {
it("includes hardening and resource flags", () => {
const cfg: SandboxDockerConfig = {
image: "clawdbot-sandbox:bookworm-slim",
containerPrefix: "clawdbot-sbx-",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp"],
network: "none",
user: "1000:1000",
capDrop: ["ALL"],
env: { LANG: "C.UTF-8" },
pidsLimit: 256,
memory: "512m",
memorySwap: 1024,
cpus: 1.5,
ulimits: {
nofile: { soft: 1024, hard: 2048 },
nproc: 128,
core: "0",
},
seccompProfile: "/tmp/seccomp.json",
apparmorProfile: "clawdbot-sandbox",
dns: ["1.1.1.1"],
extraHosts: ["internal.service:10.0.0.5"],
};
const args = buildSandboxCreateArgs({
name: "clawdbot-sbx-test",
cfg,
sessionKey: "main",
createdAtMs: 1700000000000,
labels: { "clawdbot.sandboxBrowser": "1" },
});
expect(args).toEqual(
expect.arrayContaining([
"create",
"--name",
"clawdbot-sbx-test",
"--label",
"clawdbot.sandbox=1",
"--label",
"clawdbot.sessionKey=main",
"--label",
"clawdbot.createdAtMs=1700000000000",
"--label",
"clawdbot.sandboxBrowser=1",
"--read-only",
"--tmpfs",
"/tmp",
"--network",
"none",
"--user",
"1000:1000",
"--cap-drop",
"ALL",
"--security-opt",
"no-new-privileges",
"--security-opt",
"seccomp=/tmp/seccomp.json",
"--security-opt",
"apparmor=clawdbot-sandbox",
"--dns",
"1.1.1.1",
"--add-host",
"internal.service:10.0.0.5",
"--pids-limit",
"256",
"--memory",
"512m",
"--memory-swap",
"1024",
"--cpus",
"1.5",
]),
);
const ulimitValues: string[] = [];
for (let i = 0; i < args.length; i += 1) {
if (args[i] === "--ulimit") {
const value = args[i + 1];
if (value) ulimitValues.push(value);
}
}
expect(ulimitValues).toEqual(
expect.arrayContaining(["nofile=1024:2048", "nproc=128", "core=0"]),
);
});
});
+130 -46
View File
@@ -14,8 +14,8 @@ import {
resolveProfile,
} from "../browser/config.js";
import { DEFAULT_CLAWD_BROWSER_COLOR } from "../browser/constants.js";
import type { ClawdisConfig } from "../config/config.js";
import { STATE_DIR_CLAWDIS } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { STATE_DIR_CLAWDBOT } from "../config/config.js";
import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js";
import {
@@ -56,6 +56,18 @@ export type SandboxDockerConfig = {
capDrop: string[];
env?: Record<string, string>;
setupCommand?: string;
pidsLimit?: number;
memory?: string | number;
memorySwap?: string | number;
cpus?: number;
ulimits?: Record<
string,
string | number | { soft?: number; hard?: number }
>;
seccompProfile?: string;
apparmorProfile?: string;
dns?: string[];
extraHosts?: string[];
};
export type SandboxPruneConfig = {
@@ -92,11 +104,11 @@ export type SandboxContext = {
const DEFAULT_SANDBOX_WORKSPACE_ROOT = path.join(
os.homedir(),
".clawdis",
".clawdbot",
"sandboxes",
);
const DEFAULT_SANDBOX_IMAGE = "clawdis-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdis-sbx-";
const DEFAULT_SANDBOX_IMAGE = "clawdbot-sandbox:bookworm-slim";
const DEFAULT_SANDBOX_CONTAINER_PREFIX = "clawdbot-sbx-";
const DEFAULT_SANDBOX_WORKDIR = "/workspace";
const DEFAULT_SANDBOX_IDLE_HOURS = 24;
const DEFAULT_SANDBOX_MAX_AGE_DAYS = 7;
@@ -109,13 +121,13 @@ const DEFAULT_TOOL_DENY = [
"discord",
"gateway",
];
const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdis-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdis-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_IMAGE = "clawdbot-sandbox-browser:bookworm-slim";
const DEFAULT_SANDBOX_BROWSER_PREFIX = "clawdbot-sbx-browser-";
const DEFAULT_SANDBOX_BROWSER_CDP_PORT = 9222;
const DEFAULT_SANDBOX_BROWSER_VNC_PORT = 5900;
const DEFAULT_SANDBOX_BROWSER_NOVNC_PORT = 6080;
const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDIS, "sandbox");
const SANDBOX_STATE_DIR = path.join(STATE_DIR_CLAWDBOT, "sandbox");
const SANDBOX_REGISTRY_PATH = path.join(SANDBOX_STATE_DIR, "containers.json");
const SANDBOX_BROWSER_REGISTRY_PATH = path.join(
SANDBOX_STATE_DIR,
@@ -170,7 +182,7 @@ function isToolAllowed(policy: SandboxToolPolicy, name: string) {
return allow.includes(name.toLowerCase());
}
function defaultSandboxConfig(cfg?: ClawdisConfig): SandboxConfig {
function defaultSandboxConfig(cfg?: ClawdbotConfig): SandboxConfig {
const agent = cfg?.agent?.sandbox;
return {
mode: agent?.mode ?? "off",
@@ -183,11 +195,20 @@ function defaultSandboxConfig(cfg?: ClawdisConfig): SandboxConfig {
workdir: agent?.docker?.workdir ?? DEFAULT_SANDBOX_WORKDIR,
readOnlyRoot: agent?.docker?.readOnlyRoot ?? true,
tmpfs: agent?.docker?.tmpfs ?? ["/tmp", "/var/tmp", "/run"],
network: agent?.docker?.network ?? "bridge",
network: agent?.docker?.network ?? "none",
user: agent?.docker?.user,
capDrop: agent?.docker?.capDrop ?? ["ALL"],
env: agent?.docker?.env ?? { LANG: "C.UTF-8" },
setupCommand: agent?.docker?.setupCommand,
pidsLimit: agent?.docker?.pidsLimit,
memory: agent?.docker?.memory,
memorySwap: agent?.docker?.memorySwap,
cpus: agent?.docker?.cpus,
ulimits: agent?.docker?.ulimits,
seccompProfile: agent?.docker?.seccompProfile,
apparmorProfile: agent?.docker?.apparmorProfile,
dns: agent?.docker?.dns,
extraHosts: agent?.docker?.extraHosts,
},
browser: {
enabled: agent?.browser?.enabled ?? false,
@@ -428,6 +449,88 @@ async function ensureSandboxWorkspace(workspaceDir: string, seedFrom?: string) {
await ensureAgentWorkspace({ dir: workspaceDir, ensureBootstrapFiles: true });
}
function normalizeDockerLimit(value?: string | number) {
if (value === undefined || value === null) return undefined;
if (typeof value === "number") {
return Number.isFinite(value) ? String(value) : undefined;
}
const trimmed = value.trim();
return trimmed ? trimmed : undefined;
}
function formatUlimitValue(
name: string,
value: string | number | { soft?: number; hard?: number },
) {
if (!name.trim()) return null;
if (typeof value === "number" || typeof value === "string") {
const raw = String(value).trim();
return raw ? `${name}=${raw}` : null;
}
const soft =
typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
const hard =
typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
if (soft === undefined && hard === undefined) return null;
if (soft === undefined) return `${name}=${hard}`;
if (hard === undefined) return `${name}=${soft}`;
return `${name}=${soft}:${hard}`;
}
export function buildSandboxCreateArgs(params: {
name: string;
cfg: SandboxDockerConfig;
sessionKey: string;
createdAtMs?: number;
labels?: Record<string, string>;
}) {
const createdAtMs = params.createdAtMs ?? Date.now();
const args = ["create", "--name", params.name];
args.push("--label", "clawdbot.sandbox=1");
args.push("--label", `clawdbot.sessionKey=${params.sessionKey}`);
args.push("--label", `clawdbot.createdAtMs=${createdAtMs}`);
for (const [key, value] of Object.entries(params.labels ?? {})) {
if (key && value) args.push("--label", `${key}=${value}`);
}
if (params.cfg.readOnlyRoot) args.push("--read-only");
for (const entry of params.cfg.tmpfs) {
args.push("--tmpfs", entry);
}
if (params.cfg.network) args.push("--network", params.cfg.network);
if (params.cfg.user) args.push("--user", params.cfg.user);
for (const cap of params.cfg.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
if (params.cfg.seccompProfile) {
args.push("--security-opt", `seccomp=${params.cfg.seccompProfile}`);
}
if (params.cfg.apparmorProfile) {
args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
}
for (const entry of params.cfg.dns ?? []) {
if (entry.trim()) args.push("--dns", entry);
}
for (const entry of params.cfg.extraHosts ?? []) {
if (entry.trim()) args.push("--add-host", entry);
}
if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) {
args.push("--pids-limit", String(params.cfg.pidsLimit));
}
const memory = normalizeDockerLimit(params.cfg.memory);
if (memory) args.push("--memory", memory);
const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
if (memorySwap) args.push("--memory-swap", memorySwap);
if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) {
args.push("--cpus", String(params.cfg.cpus));
}
for (const [name, value] of Object.entries(params.cfg.ulimits ?? {})) {
const formatted = formatUlimitValue(name, value);
if (formatted) args.push("--ulimit", formatted);
}
return args;
}
async function createSandboxContainer(params: {
name: string;
cfg: SandboxDockerConfig;
@@ -437,20 +540,11 @@ async function createSandboxContainer(params: {
const { name, cfg, workspaceDir, sessionKey } = params;
await ensureDockerImage(cfg.image);
const args = ["create", "--name", name];
args.push("--label", "clawdis.sandbox=1");
args.push("--label", `clawdis.sessionKey=${sessionKey}`);
args.push("--label", `clawdis.createdAtMs=${Date.now()}`);
if (cfg.readOnlyRoot) args.push("--read-only");
for (const entry of cfg.tmpfs) {
args.push("--tmpfs", entry);
}
if (cfg.network) args.push("--network", cfg.network);
if (cfg.user) args.push("--user", cfg.user);
for (const cap of cfg.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
const args = buildSandboxCreateArgs({
name,
cfg,
sessionKey,
});
args.push("--workdir", cfg.workdir);
args.push("-v", `${workspaceDir}:${cfg.workdir}`);
args.push(cfg.image, "sleep", "infinity");
@@ -547,22 +641,12 @@ async function ensureSandboxBrowser(params: {
const state = await dockerContainerState(containerName);
if (!state.exists) {
await ensureSandboxBrowserImage(params.cfg.browser.image);
const args = ["create", "--name", containerName];
args.push("--label", "clawdis.sandbox=1");
args.push("--label", "clawdis.sandboxBrowser=1");
args.push("--label", `clawdis.sessionKey=${params.sessionKey}`);
args.push("--label", `clawdis.createdAtMs=${Date.now()}`);
if (params.cfg.docker.readOnlyRoot) args.push("--read-only");
for (const entry of params.cfg.docker.tmpfs) {
args.push("--tmpfs", entry);
}
if (params.cfg.docker.network)
args.push("--network", params.cfg.docker.network);
if (params.cfg.docker.user) args.push("--user", params.cfg.docker.user);
for (const cap of params.cfg.docker.capDrop) {
args.push("--cap-drop", cap);
}
args.push("--security-opt", "no-new-privileges");
const args = buildSandboxCreateArgs({
name: containerName,
cfg: params.cfg.docker,
sessionKey: params.sessionKey,
labels: { "clawdbot.sandboxBrowser": "1" },
});
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}`);
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
if (params.cfg.browser.enableNoVnc && !params.cfg.browser.headless) {
@@ -570,19 +654,19 @@ async function ensureSandboxBrowser(params: {
}
args.push(
"-e",
`CLAWDIS_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`,
`CLAWDBOT_BROWSER_HEADLESS=${params.cfg.browser.headless ? "1" : "0"}`,
);
args.push(
"-e",
`CLAWDIS_BROWSER_ENABLE_NOVNC=${
`CLAWDBOT_BROWSER_ENABLE_NOVNC=${
params.cfg.browser.enableNoVnc ? "1" : "0"
}`,
);
args.push("-e", `CLAWDIS_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
args.push("-e", `CLAWDIS_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
args.push("-e", `CLAWDBOT_BROWSER_CDP_PORT=${params.cfg.browser.cdpPort}`);
args.push("-e", `CLAWDBOT_BROWSER_VNC_PORT=${params.cfg.browser.vncPort}`);
args.push(
"-e",
`CLAWDIS_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`,
`CLAWDBOT_BROWSER_NOVNC_PORT=${params.cfg.browser.noVncPort}`,
);
args.push(params.cfg.browser.image);
await execDocker(args);
@@ -739,7 +823,7 @@ async function maybePruneSandboxes(cfg: SandboxConfig) {
}
export async function resolveSandboxContext(params: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
sessionKey?: string;
workspaceDir?: string;
}): Promise<SandboxContext | null> {
+3 -3
View File
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { resolveUserPath } from "../utils.js";
import {
@@ -18,7 +18,7 @@ export type SkillInstallRequest = {
skillName: string;
installId: string;
timeoutMs?: number;
config?: ClawdisConfig;
config?: ClawdbotConfig;
};
export type SkillInstallResult = {
@@ -73,7 +73,7 @@ function findInstallSpec(
entry: SkillEntry,
installId: string,
): SkillInstallSpec | undefined {
const specs = entry.clawdis?.install ?? [];
const specs = entry.clawdbot?.install ?? [];
for (const [index, spec] of specs.entries()) {
if (resolveInstallId(spec, index) === installId) return spec;
}
+14 -14
View File
@@ -1,6 +1,6 @@
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { CONFIG_DIR } from "../utils.js";
import {
hasBinary,
@@ -66,7 +66,7 @@ export type SkillStatusReport = {
};
function resolveSkillKey(entry: SkillEntry): string {
return entry.clawdis?.skillKey ?? entry.skill.name;
return entry.clawdbot?.skillKey ?? entry.skill.name;
}
function selectPreferredInstallSpec(
@@ -95,7 +95,7 @@ function normalizeInstallOptions(
entry: SkillEntry,
prefs: SkillsInstallPreferences,
): SkillInstallOption[] {
const install = entry.clawdis?.install ?? [];
const install = entry.clawdbot?.install ?? [];
if (install.length === 0) return [];
const preferred = selectPreferredInstallSpec(install, prefs);
if (!preferred) return [];
@@ -131,7 +131,7 @@ function normalizeInstallOptions(
function buildSkillStatus(
entry: SkillEntry,
config?: ClawdisConfig,
config?: ClawdbotConfig,
prefs?: SkillsInstallPreferences,
): SkillStatusEntry {
const skillKey = resolveSkillKey(entry);
@@ -139,19 +139,19 @@ function buildSkillStatus(
const disabled = skillConfig?.enabled === false;
const allowBundled = resolveBundledAllowlist(config);
const blockedByAllowlist = !isBundledSkillAllowed(entry, allowBundled);
const always = entry.clawdis?.always === true;
const emoji = entry.clawdis?.emoji ?? entry.frontmatter.emoji;
const always = entry.clawdbot?.always === true;
const emoji = entry.clawdbot?.emoji ?? entry.frontmatter.emoji;
const homepageRaw =
entry.clawdis?.homepage ??
entry.clawdbot?.homepage ??
entry.frontmatter.homepage ??
entry.frontmatter.website ??
entry.frontmatter.url;
const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;
const requiredBins = entry.clawdis?.requires?.bins ?? [];
const requiredEnv = entry.clawdis?.requires?.env ?? [];
const requiredConfig = entry.clawdis?.requires?.config ?? [];
const requiredOs = entry.clawdis?.os ?? [];
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
const requiredOs = entry.clawdbot?.os ?? [];
const missingBins = requiredBins.filter((bin) => !hasBinary(bin));
const missingOs =
@@ -163,7 +163,7 @@ function buildSkillStatus(
for (const envName of requiredEnv) {
if (process.env[envName]) continue;
if (skillConfig?.env?.[envName]) continue;
if (skillConfig?.apiKey && entry.clawdis?.primaryEnv === envName) {
if (skillConfig?.apiKey && entry.clawdbot?.primaryEnv === envName) {
continue;
}
missingEnv.push(envName);
@@ -204,7 +204,7 @@ function buildSkillStatus(
filePath: entry.skill.filePath,
baseDir: entry.skill.baseDir,
skillKey,
primaryEnv: entry.clawdis?.primaryEnv,
primaryEnv: entry.clawdbot?.primaryEnv,
emoji,
homepage,
always,
@@ -229,7 +229,7 @@ function buildSkillStatus(
export function buildWorkspaceSkillStatus(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
entries?: SkillEntry[];
},
+31 -31
View File
@@ -37,7 +37,7 @@ ${body ?? `# ${name}\n`}
describe("buildWorkspaceSkillsPrompt", () => {
it("returns empty prompt when skills dirs are missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
@@ -48,7 +48,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads bundled skills when present", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
@@ -69,7 +69,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads extra skill folders from config (lowest precedence)", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const extraDir = path.join(workspaceDir, ".extra");
const bundledDir = path.join(workspaceDir, ".bundled");
const managedDir = path.join(workspaceDir, ".managed");
@@ -112,7 +112,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("loads skills from workspace skills/", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "demo-skill");
await writeSkill({
@@ -131,7 +131,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("filters skills based on env/config gates", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "nano-banana-pro");
const originalEnv = process.env.GEMINI_API_KEY;
delete process.env.GEMINI_API_KEY;
@@ -142,7 +142,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
name: "nano-banana-pro",
description: "Generates images",
metadata:
'{"clawdis":{"requires":{"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY"}}',
'{"clawdbot":{"requires":{"env":["GEMINI_API_KEY"]},"primaryEnv":"GEMINI_API_KEY"}}',
body: "# Nano Banana\n",
});
@@ -166,7 +166,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("prefers workspace skills over managed skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const managedDir = path.join(workspaceDir, ".managed");
const bundledDir = path.join(workspaceDir, ".bundled");
const managedSkillDir = path.join(managedDir, "demo-skill");
@@ -204,7 +204,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("gates by bins, config, and always", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillsDir = path.join(workspaceDir, "skills");
const binDir = path.join(workspaceDir, "bin");
const originalPath = process.env.PATH;
@@ -213,32 +213,32 @@ describe("buildWorkspaceSkillsPrompt", () => {
dir: path.join(skillsDir, "bin-skill"),
name: "bin-skill",
description: "Needs a bin",
metadata: '{"clawdis":{"requires":{"bins":["fakebin"]}}}',
metadata: '{"clawdbot":{"requires":{"bins":["fakebin"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "anybin-skill"),
name: "anybin-skill",
description: "Needs any bin",
metadata: '{"clawdis":{"requires":{"anyBins":["missingbin","fakebin"]}}}',
metadata: '{"clawdbot":{"requires":{"anyBins":["missingbin","fakebin"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "config-skill"),
name: "config-skill",
description: "Needs config",
metadata: '{"clawdis":{"requires":{"config":["browser.enabled"]}}}',
metadata: '{"clawdbot":{"requires":{"config":["browser.enabled"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "always-skill"),
name: "always-skill",
description: "Always on",
metadata: '{"clawdis":{"always":true,"requires":{"env":["MISSING"]}}}',
metadata: '{"clawdbot":{"always":true,"requires":{"env":["MISSING"]}}}',
});
await writeSkill({
dir: path.join(skillsDir, "env-skill"),
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
try {
@@ -275,13 +275,13 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("uses skillKey for config lookups", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "alias-skill");
await writeSkill({
dir: skillDir,
name: "alias-skill",
description: "Uses skillKey",
metadata: '{"clawdis":{"skillKey":"alias"}}',
metadata: '{"clawdbot":{"skillKey":"alias"}}',
});
const prompt = buildWorkspaceSkillsPrompt(workspaceDir, {
@@ -292,7 +292,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
it("applies bundled allowlist without affecting workspace skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
const workspaceSkillDir = path.join(workspaceDir, "skills", "demo-skill");
@@ -323,7 +323,7 @@ describe("buildWorkspaceSkillsPrompt", () => {
describe("loadWorkspaceSkillEntries", () => {
it("handles an empty managed skills dir without throwing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const managedDir = path.join(workspaceDir, ".managed");
await fs.mkdir(managedDir, { recursive: true });
@@ -338,7 +338,7 @@ describe("loadWorkspaceSkillEntries", () => {
describe("buildWorkspaceSkillSnapshot", () => {
it("returns an empty snapshot when skills dirs are missing", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
@@ -352,7 +352,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
describe("buildWorkspaceSkillStatus", () => {
it("reports missing requirements and install options", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "status-skill");
await writeSkill({
@@ -360,7 +360,7 @@ describe("buildWorkspaceSkillStatus", () => {
name: "status-skill",
description: "Needs setup",
metadata:
'{"clawdis":{"requires":{"bins":["fakebin"],"env":["ENV_KEY"],"config":["browser.enabled"]},"install":[{"id":"brew","kind":"brew","formula":"fakebin","bins":["fakebin"],"label":"Install fakebin"}]}}',
'{"clawdbot":{"requires":{"bins":["fakebin"],"env":["ENV_KEY"],"config":["browser.enabled"]},"install":[{"id":"brew","kind":"brew","formula":"fakebin","bins":["fakebin"],"label":"Install fakebin"}]}}',
});
const report = buildWorkspaceSkillStatus(workspaceDir, {
@@ -378,14 +378,14 @@ describe("buildWorkspaceSkillStatus", () => {
});
it("respects OS-gated skills", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "os-skill");
await writeSkill({
dir: skillDir,
name: "os-skill",
description: "Darwin only",
metadata: '{"clawdis":{"os":["darwin"]}}',
metadata: '{"clawdbot":{"os":["darwin"]}}',
});
const report = buildWorkspaceSkillStatus(workspaceDir, {
@@ -404,10 +404,10 @@ describe("buildWorkspaceSkillStatus", () => {
});
it("marks bundled skills blocked by allowlist", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const bundledDir = path.join(workspaceDir, ".bundled");
const bundledSkillDir = path.join(bundledDir, "peekaboo");
const originalBundled = process.env.CLAWDIS_BUNDLED_SKILLS_DIR;
const originalBundled = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
await writeSkill({
dir: bundledSkillDir,
@@ -417,7 +417,7 @@ describe("buildWorkspaceSkillStatus", () => {
});
try {
process.env.CLAWDIS_BUNDLED_SKILLS_DIR = bundledDir;
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = bundledDir;
const report = buildWorkspaceSkillStatus(workspaceDir, {
managedSkillsDir: path.join(workspaceDir, ".managed"),
config: { skills: { allowBundled: ["other-skill"] } },
@@ -429,9 +429,9 @@ describe("buildWorkspaceSkillStatus", () => {
expect(skill?.eligible).toBe(false);
} finally {
if (originalBundled === undefined) {
delete process.env.CLAWDIS_BUNDLED_SKILLS_DIR;
delete process.env.CLAWDBOT_BUNDLED_SKILLS_DIR;
} else {
process.env.CLAWDIS_BUNDLED_SKILLS_DIR = originalBundled;
process.env.CLAWDBOT_BUNDLED_SKILLS_DIR = originalBundled;
}
}
});
@@ -439,14 +439,14 @@ describe("buildWorkspaceSkillStatus", () => {
describe("applySkillEnvOverrides", () => {
it("sets and restores env vars", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "env-skill");
await writeSkill({
dir: skillDir,
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
const entries = loadWorkspaceSkillEntries(workspaceDir, {
@@ -474,14 +474,14 @@ describe("applySkillEnvOverrides", () => {
});
it("applies env overrides from snapshots", async () => {
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-"));
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-"));
const skillDir = path.join(workspaceDir, "skills", "env-skill");
await writeSkill({
dir: skillDir,
name: "env-skill",
description: "Needs env",
metadata:
'{"clawdis":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
'{"clawdbot":{"requires":{"env":["ENV_KEY"]},"primaryEnv":"ENV_KEY"}}',
});
const snapshot = buildWorkspaceSkillSnapshot(workspaceDir, {
+53 -53
View File
@@ -8,7 +8,7 @@ import {
type Skill,
} from "@mariozechner/pi-coding-agent";
import type { ClawdisConfig, SkillConfig } from "../config/config.js";
import type { ClawdbotConfig, SkillConfig } from "../config/config.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
export type SkillInstallSpec = {
@@ -21,7 +21,7 @@ export type SkillInstallSpec = {
module?: string;
};
export type ClawdisSkillMetadata = {
export type ClawdbotSkillMetadata = {
always?: boolean;
skillKey?: string;
primaryEnv?: string;
@@ -47,7 +47,7 @@ type ParsedSkillFrontmatter = Record<string, string>;
export type SkillEntry = {
skill: Skill;
frontmatter: ParsedSkillFrontmatter;
clawdis?: ClawdisSkillMetadata;
clawdbot?: ClawdbotSkillMetadata;
};
export type SkillSnapshot = {
@@ -57,7 +57,7 @@ export type SkillSnapshot = {
};
function resolveBundledSkillsDir(): string | undefined {
const override = process.env.CLAWDIS_BUNDLED_SKILLS_DIR?.trim();
const override = process.env.CLAWDBOT_BUNDLED_SKILLS_DIR?.trim();
if (override) return override;
// bun --compile: ship a sibling `skills/` next to the executable.
@@ -173,7 +173,7 @@ const DEFAULT_CONFIG_VALUES: Record<string, boolean> = {
};
export function resolveSkillsInstallPreferences(
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillsInstallPreferences {
const raw = config?.skills?.install;
const preferBrew = raw?.preferBrew ?? true;
@@ -195,7 +195,7 @@ export function resolveRuntimePlatform(): string {
}
export function resolveConfigPath(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
pathStr: string,
) {
const parts = pathStr.split(".").filter(Boolean);
@@ -208,7 +208,7 @@ export function resolveConfigPath(
}
export function isConfigPathTruthy(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
pathStr: string,
): boolean {
const value = resolveConfigPath(config, pathStr);
@@ -219,7 +219,7 @@ export function isConfigPathTruthy(
}
export function resolveSkillConfig(
config: ClawdisConfig | undefined,
config: ClawdbotConfig | undefined,
skillKey: string,
): SkillConfig | undefined {
const skills = config?.skills?.entries;
@@ -237,7 +237,7 @@ function normalizeAllowlist(input: unknown): string[] | undefined {
}
function isBundledSkill(entry: SkillEntry): boolean {
return entry.skill.source === "clawdis-bundled";
return entry.skill.source === "clawdbot-bundled";
}
export function isBundledSkillAllowed(
@@ -265,44 +265,44 @@ export function hasBinary(bin: string): boolean {
return false;
}
function resolveClawdisMetadata(
function resolveClawdbotMetadata(
frontmatter: ParsedSkillFrontmatter,
): ClawdisSkillMetadata | undefined {
): ClawdbotSkillMetadata | undefined {
const raw = getFrontmatterValue(frontmatter, "metadata");
if (!raw) return undefined;
try {
const parsed = JSON.parse(raw) as { clawdis?: unknown };
const parsed = JSON.parse(raw) as { clawdbot?: unknown };
if (!parsed || typeof parsed !== "object") return undefined;
const clawdis = (parsed as { clawdis?: unknown }).clawdis;
if (!clawdis || typeof clawdis !== "object") return undefined;
const clawdisObj = clawdis as Record<string, unknown>;
const clawdbot = (parsed as { clawdbot?: unknown }).clawdbot;
if (!clawdbot || typeof clawdbot !== "object") return undefined;
const clawdbotObj = clawdbot as Record<string, unknown>;
const requiresRaw =
typeof clawdisObj.requires === "object" && clawdisObj.requires !== null
? (clawdisObj.requires as Record<string, unknown>)
typeof clawdbotObj.requires === "object" && clawdbotObj.requires !== null
? (clawdbotObj.requires as Record<string, unknown>)
: undefined;
const installRaw = Array.isArray(clawdisObj.install)
? (clawdisObj.install as unknown[])
const installRaw = Array.isArray(clawdbotObj.install)
? (clawdbotObj.install as unknown[])
: [];
const install = installRaw
.map((entry) => parseInstallSpec(entry))
.filter((entry): entry is SkillInstallSpec => Boolean(entry));
const osRaw = normalizeStringList(clawdisObj.os);
const osRaw = normalizeStringList(clawdbotObj.os);
return {
always:
typeof clawdisObj.always === "boolean" ? clawdisObj.always : undefined,
typeof clawdbotObj.always === "boolean" ? clawdbotObj.always : undefined,
emoji:
typeof clawdisObj.emoji === "string" ? clawdisObj.emoji : undefined,
typeof clawdbotObj.emoji === "string" ? clawdbotObj.emoji : undefined,
homepage:
typeof clawdisObj.homepage === "string"
? clawdisObj.homepage
typeof clawdbotObj.homepage === "string"
? clawdbotObj.homepage
: undefined,
skillKey:
typeof clawdisObj.skillKey === "string"
? clawdisObj.skillKey
typeof clawdbotObj.skillKey === "string"
? clawdbotObj.skillKey
: undefined,
primaryEnv:
typeof clawdisObj.primaryEnv === "string"
? clawdisObj.primaryEnv
typeof clawdbotObj.primaryEnv === "string"
? clawdbotObj.primaryEnv
: undefined,
os: osRaw.length > 0 ? osRaw : undefined,
requires: requiresRaw
@@ -321,53 +321,53 @@ function resolveClawdisMetadata(
}
function resolveSkillKey(skill: Skill, entry?: SkillEntry): string {
return entry?.clawdis?.skillKey ?? skill.name;
return entry?.clawdbot?.skillKey ?? skill.name;
}
function shouldIncludeSkill(params: {
entry: SkillEntry;
config?: ClawdisConfig;
config?: ClawdbotConfig;
}): boolean {
const { entry, config } = params;
const skillKey = resolveSkillKey(entry.skill, entry);
const skillConfig = resolveSkillConfig(config, skillKey);
const allowBundled = normalizeAllowlist(config?.skills?.allowBundled);
const osList = entry.clawdis?.os ?? [];
const osList = entry.clawdbot?.os ?? [];
if (skillConfig?.enabled === false) return false;
if (!isBundledSkillAllowed(entry, allowBundled)) return false;
if (osList.length > 0 && !osList.includes(resolveRuntimePlatform())) {
return false;
}
if (entry.clawdis?.always === true) {
if (entry.clawdbot?.always === true) {
return true;
}
const requiredBins = entry.clawdis?.requires?.bins ?? [];
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
if (requiredBins.length > 0) {
for (const bin of requiredBins) {
if (!hasBinary(bin)) return false;
}
}
const requiredAnyBins = entry.clawdis?.requires?.anyBins ?? [];
const requiredAnyBins = entry.clawdbot?.requires?.anyBins ?? [];
if (requiredAnyBins.length > 0) {
const anyFound = requiredAnyBins.some((bin) => hasBinary(bin));
if (!anyFound) return false;
}
const requiredEnv = entry.clawdis?.requires?.env ?? [];
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
if (requiredEnv.length > 0) {
for (const envName of requiredEnv) {
if (process.env[envName]) continue;
if (skillConfig?.env?.[envName]) continue;
if (skillConfig?.apiKey && entry.clawdis?.primaryEnv === envName) {
if (skillConfig?.apiKey && entry.clawdbot?.primaryEnv === envName) {
continue;
}
return false;
}
}
const requiredConfig = entry.clawdis?.requires?.config ?? [];
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
if (requiredConfig.length > 0) {
for (const configPath of requiredConfig) {
if (!isConfigPathTruthy(config, configPath)) return false;
@@ -379,14 +379,14 @@ function shouldIncludeSkill(params: {
function filterSkillEntries(
entries: SkillEntry[],
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillEntry[] {
return entries.filter((entry) => shouldIncludeSkill({ entry, config }));
}
export function applySkillEnvOverrides(params: {
skills: SkillEntry[];
config?: ClawdisConfig;
config?: ClawdbotConfig;
}) {
const { skills, config } = params;
const updates: Array<{ key: string; prev: string | undefined }> = [];
@@ -404,7 +404,7 @@ export function applySkillEnvOverrides(params: {
}
}
const primaryEnv = entry.clawdis?.primaryEnv;
const primaryEnv = entry.clawdbot?.primaryEnv;
if (primaryEnv && skillConfig.apiKey && !process.env[primaryEnv]) {
updates.push({ key: primaryEnv, prev: process.env[primaryEnv] });
process.env[primaryEnv] = skillConfig.apiKey;
@@ -421,7 +421,7 @@ export function applySkillEnvOverrides(params: {
export function applySkillEnvOverridesFromSnapshot(params: {
snapshot?: SkillSnapshot;
config?: ClawdisConfig;
config?: ClawdbotConfig;
}) {
const { snapshot, config } = params;
if (!snapshot) return () => {};
@@ -463,7 +463,7 @@ export function applySkillEnvOverridesFromSnapshot(params: {
function loadSkillEntries(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
},
@@ -494,23 +494,23 @@ function loadSkillEntries(
const bundledSkills = bundledSkillsDir
? loadSkills({
dir: bundledSkillsDir,
source: "clawdis-bundled",
source: "clawdbot-bundled",
})
: [];
const extraSkills = extraDirs.flatMap((dir) => {
const resolved = resolveUserPath(dir);
return loadSkills({
dir: resolved,
source: "clawdis-extra",
source: "clawdbot-extra",
});
});
const managedSkills = loadSkills({
dir: managedSkillsDir,
source: "clawdis-managed",
source: "clawdbot-managed",
});
const workspaceSkills = loadSkills({
dir: workspaceSkillsDir,
source: "clawdis-workspace",
source: "clawdbot-workspace",
});
const merged = new Map<string, Skill>();
@@ -532,7 +532,7 @@ function loadSkillEntries(
return {
skill,
frontmatter,
clawdis: resolveClawdisMetadata(frontmatter),
clawdbot: resolveClawdbotMetadata(frontmatter),
};
},
);
@@ -542,7 +542,7 @@ function loadSkillEntries(
export function buildWorkspaceSkillSnapshot(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
entries?: SkillEntry[];
@@ -555,7 +555,7 @@ export function buildWorkspaceSkillSnapshot(
prompt: formatSkillsForPrompt(resolvedSkills),
skills: eligible.map((entry) => ({
name: entry.skill.name,
primaryEnv: entry.clawdis?.primaryEnv,
primaryEnv: entry.clawdbot?.primaryEnv,
})),
resolvedSkills,
};
@@ -564,7 +564,7 @@ export function buildWorkspaceSkillSnapshot(
export function buildWorkspaceSkillsPrompt(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
entries?: SkillEntry[];
@@ -578,7 +578,7 @@ export function buildWorkspaceSkillsPrompt(
export function loadWorkspaceSkillEntries(
workspaceDir: string,
opts?: {
config?: ClawdisConfig;
config?: ClawdbotConfig;
managedSkillsDir?: string;
bundledSkillsDir?: string;
},
@@ -588,12 +588,12 @@ export function loadWorkspaceSkillEntries(
export function filterWorkspaceSkillEntries(
entries: SkillEntry[],
config?: ClawdisConfig,
config?: ClawdbotConfig,
): SkillEntry[] {
return filterSkillEntries(entries, config);
}
export function resolveBundledAllowlist(
config?: ClawdisConfig,
config?: ClawdbotConfig,
): string[] | undefined {
return normalizeAllowlist(config?.skills?.allowBundled);
}
+4 -4
View File
@@ -58,7 +58,7 @@ export function buildAgentSystemPromptAppend(params: {
if (runtimeInfo?.model) runtimeLines.push(`Model: ${runtimeInfo.model}`);
const lines = [
"You are Clawd, a personal assistant running inside Clawdis.",
"You are Clawd, a personal assistant running inside Clawdbot.",
"",
"## Tooling",
"Pi lists the standard tools above. This runtime enables:",
@@ -101,11 +101,11 @@ export function buildAgentSystemPromptAppend(params: {
ownerLine ?? "",
ownerLine ? "" : "",
"## Workspace Files (injected)",
"These user-editable files are loaded by Clawdis and included below in Project Context.",
"These user-editable files are loaded by Clawdbot and included below in Project Context.",
"",
"## Messaging Safety",
"Never send streaming/partial replies to external messaging surfaces; only final replies should be delivered there.",
"Clawdis handles message transport automatically; respond normally and your reply will be delivered to the current chat.",
"Clawdbot handles message transport automatically; respond normally and your reply will be delivered to the current chat.",
"",
"## Reply Tags",
"To request a native reply/quote on supported surfaces, include one tag in your reply:",
@@ -126,7 +126,7 @@ export function buildAgentSystemPromptAppend(params: {
"## Heartbeats",
'If you receive a heartbeat poll (a user message containing just "HEARTBEAT"), and there is nothing that needs attention, reply exactly:',
"HEARTBEAT_OK",
'Clawdis treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'Clawdbot treats a leading/trailing "HEARTBEAT_OK" as a heartbeat ack (and may discard it).',
'If something needs attention, do NOT include "HEARTBEAT_OK"; reply with the alert text instead.',
"",
"## Runtime",
+1 -1
View File
@@ -6,7 +6,7 @@ type ToolContentBlock = AgentToolResult<unknown>["content"][number];
type ImageContentBlock = Extract<ToolContentBlock, { type: "image" }>;
type TextContentBlock = Extract<ToolContentBlock, { type: "text" }>;
// Anthropic Messages API limitations (observed in Clawdis sessions):
// Anthropic Messages API limitations (observed in Clawdbot sessions):
// - Images over ~2000px per side can fail in multi-image requests.
// - Images over 5MB are rejected by the API.
//
+1 -1
View File
@@ -196,7 +196,7 @@ function resolveBrowserBaseUrl(controlUrl?: string) {
const resolved = resolveBrowserConfig(cfg.browser);
if (!resolved.enabled && !controlUrl?.trim()) {
throw new Error(
"Browser control is disabled. Set browser.enabled=true in ~/.clawdis/clawdis.json.",
"Browser control is disabled. Set browser.enabled=true in ~/.clawdbot/clawdbot.json.",
);
}
const url = controlUrl?.trim() ? controlUrl.trim() : resolved.controlUrl;
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type {
ClawdisConfig,
ClawdbotConfig,
DiscordActionConfig,
} from "../../config/config.js";
import { readStringParam } from "./common.js";
@@ -51,7 +51,7 @@ type ActionGate = (
export async function handleDiscordAction(
params: Record<string, unknown>,
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const isActionEnabled: ActionGate = (key, defaultValue = true) => {
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
export type SessionKind = "main" | "group" | "cron" | "hook" | "node" | "other";
@@ -7,7 +7,7 @@ function normalizeKey(value?: string) {
return trimmed ? trimmed : undefined;
}
export function resolveMainSessionAlias(cfg: ClawdisConfig) {
export function resolveMainSessionAlias(cfg: ClawdbotConfig) {
const mainKey = normalizeKey(cfg.session?.mainKey) ?? "main";
const scope = cfg.session?.scope ?? "per-sender";
const alias = scope === "global" ? "global" : mainKey;
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
const ANNOUNCE_SKIP_TOKEN = "ANNOUNCE_SKIP";
const REPLY_SKIP_TOKEN = "REPLY_SKIP";
@@ -123,7 +123,7 @@ export function isReplySkip(text?: string) {
return (text ?? "").trim() === REPLY_SKIP_TOKEN;
}
export function resolvePingPongTurns(cfg?: ClawdisConfig) {
export function resolvePingPongTurns(cfg?: ClawdbotConfig) {
const raw = cfg?.session?.agentToAgent?.maxPingPongTurns;
const fallback = DEFAULT_PING_PONG_TURNS;
if (typeof raw !== "number" || !Number.isFinite(raw)) return fallback;
+2 -2
View File
@@ -1,6 +1,6 @@
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
import type { ClawdisConfig, SlackActionConfig } from "../../config/config.js";
import type { ClawdbotConfig, SlackActionConfig } from "../../config/config.js";
import {
deleteSlackMessage,
editSlackMessage,
@@ -33,7 +33,7 @@ type ActionGate = (
export async function handleSlackAction(
params: Record<string, unknown>,
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
): Promise<AgentToolResult<unknown>> {
const action = readStringParam(params, "action", { required: true });
const isActionEnabled: ActionGate = (key, defaultValue = true) => {
+2 -2
View File
@@ -6,7 +6,7 @@ import { ensureAgentWorkspace } from "./workspace.js";
describe("ensureAgentWorkspace", () => {
it("creates directory and bootstrap files when missing", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-ws-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
const nested = path.join(dir, "nested");
const result = await ensureAgentWorkspace({
dir: nested,
@@ -30,7 +30,7 @@ describe("ensureAgentWorkspace", () => {
});
it("does not overwrite existing AGENTS.md", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-ws-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-ws-"));
const agentsPath = path.join(dir, "AGENTS.md");
await fs.writeFile(agentsPath, "custom", "utf-8");
await ensureAgentWorkspace({ dir, ensureBootstrapFiles: true });
+3 -3
View File
@@ -13,7 +13,7 @@ export const DEFAULT_IDENTITY_FILENAME = "IDENTITY.md";
export const DEFAULT_USER_FILENAME = "USER.md";
export const DEFAULT_BOOTSTRAP_FILENAME = "BOOTSTRAP.md";
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdis Workspace
const DEFAULT_AGENTS_TEMPLATE = `# AGENTS.md - Clawdbot Workspace
This folder is the assistant's working directory.
@@ -58,7 +58,7 @@ Describe who the assistant is, tone, and boundaries.
const DEFAULT_TOOLS_TEMPLATE = `# TOOLS.md - User Tool Notes (editable)
This file is for *your* notes about external tools and conventions.
It does not define which tools exist; Clawdis provides built-in tools internally.
It does not define which tools exist; Clawdbot provides built-in tools internally.
## Examples
@@ -108,7 +108,7 @@ After the user chooses, update:
- Timezone (optional)
- Notes
3) ~/.clawdis/clawdis.json
3) ~/.clawdbot/clawdbot.json
Set identity.name, identity.theme, identity.emoji to match IDENTITY.md.
## Cleanup
+2 -2
View File
@@ -2,7 +2,7 @@
// unintentionally breaking on newlines. Using [\s\S] keeps newlines inside
// the chunk so messages are only split when they truly exceed the limit.
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
export type TextChunkSurface =
| "whatsapp"
@@ -24,7 +24,7 @@ const DEFAULT_CHUNK_LIMIT_BY_SURFACE: Record<TextChunkSurface, number> = {
};
export function resolveTextChunkLimit(
cfg: ClawdisConfig | undefined,
cfg: ClawdbotConfig | undefined,
surface?: TextChunkSurface,
): number {
const surfaceOverride = (() => {
+1 -1
View File
@@ -22,7 +22,7 @@ vi.mock("../agents/model-catalog.js", () => ({
}));
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-stream-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-stream-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
+1 -1
View File
@@ -35,7 +35,7 @@ vi.mock("../agents/model-catalog.js", () => ({
}));
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-reply-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-reply-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
+1 -1
View File
@@ -32,7 +32,7 @@ function makeResult(text: string) {
}
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-queue-"));
const base = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-queue-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
+3 -3
View File
@@ -26,7 +26,7 @@ const webMocks = vi.hoisted(() => ({
vi.mock("../web/session.js", () => webMocks);
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = await fs.mkdtemp(join(tmpdir(), "clawdis-triggers-"));
const base = await fs.mkdtemp(join(tmpdir(), "clawdbot-triggers-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
@@ -580,7 +580,7 @@ describe("trigger handling", () => {
allowFrom: ["*"],
},
session: {
store: join(tmpdir(), `clawdis-session-test-${Date.now()}.json`),
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
},
},
);
@@ -619,7 +619,7 @@ describe("trigger handling", () => {
allowFrom: ["*"],
},
session: {
store: join(tmpdir(), `clawdis-session-test-${Date.now()}.json`),
store: join(tmpdir(), `clawdbot-session-test-${Date.now()}.json`),
},
},
);
+2 -2
View File
@@ -13,7 +13,7 @@ import {
} from "../agents/workspace.js";
import {
type AgentElevatedAllowFromConfig,
type ClawdisConfig,
type ClawdbotConfig,
loadConfig,
} from "../config/config.js";
import { resolveSessionTranscriptPath } from "../config/sessions.js";
@@ -180,7 +180,7 @@ function isApprovedElevatedSender(params: {
export async function getReplyFromConfig(
ctx: MsgContext,
opts?: GetReplyOptions,
configOverride?: ClawdisConfig,
configOverride?: ClawdbotConfig,
): Promise<ReplyPayload | ReplyPayload[] | undefined> {
const cfg = configOverride ?? loadConfig();
const agentCfg = cfg.agent;
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { resolveTextChunkLimit, type TextChunkSurface } from "../chunk.js";
const DEFAULT_BLOCK_STREAM_MIN = 800;
@@ -23,7 +23,7 @@ function normalizeChunkSurface(surface?: string): TextChunkSurface | undefined {
}
export function resolveBlockStreamingChunking(
cfg: ClawdisConfig | undefined,
cfg: ClawdbotConfig | undefined,
surface?: string,
): {
minChars: number;
+6 -6
View File
@@ -1,11 +1,11 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
type SessionEntry,
type SessionScope,
saveSessionStore,
} from "../../config/sessions.js";
import { logVerbose } from "../../globals.js";
import { triggerClawdisRestart } from "../../infra/restart.js";
import { triggerClawdbotRestart } from "../../infra/restart.js";
import { resolveSendPolicy } from "../../sessions/send-policy.js";
import { normalizeE164 } from "../../utils.js";
import { resolveHeartbeatSeconds } from "../../web/reconnect.js";
@@ -37,7 +37,7 @@ export type CommandContext = {
export function buildCommandContext(params: {
ctx: MsgContext;
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
sessionKey?: string;
isGroup: boolean;
triggerBodyNormalized: string;
@@ -99,7 +99,7 @@ export function buildCommandContext(params: {
export async function handleCommands(params: {
ctx: MsgContext;
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
command: CommandContext;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
@@ -226,11 +226,11 @@ export async function handleCommands(params: {
);
return { shouldContinue: false };
}
const restartMethod = triggerClawdisRestart();
const restartMethod = triggerClawdbotRestart();
return {
shouldContinue: false,
reply: {
text: `⚙️ Restarting clawdis via ${restartMethod}; give me a few seconds to come back online.`,
text: `⚙️ Restarting clawdbot via ${restartMethod}; give me a few seconds to come back online.`,
},
};
}
+4 -4
View File
@@ -11,7 +11,7 @@ import {
resolveConfiguredModelRef,
resolveModelRefFromString,
} from "../../agents/model-selection.js";
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import { enqueueSystemEvent } from "../../infra/system-events.js";
import { extractModelDirective } from "../model.js";
@@ -134,7 +134,7 @@ export function isDirectiveOnly(params: {
directives: InlineDirectives;
cleanedBody: string;
ctx: MsgContext;
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
isGroup: boolean;
}): boolean {
const { directives, cleanedBody, ctx, cfg, isGroup } = params;
@@ -436,7 +436,7 @@ export async function persistInlineDirectives(params: {
model: string;
initialModelLabel: string;
formatModelSwitchEvent: (label: string, alias?: string) => string;
agentCfg: ClawdisConfig["agent"] | undefined;
agentCfg: ClawdbotConfig["agent"] | undefined;
}): Promise<{ provider: string; model: string; contextTokens: number }> {
const {
directives,
@@ -551,7 +551,7 @@ export async function persistInlineDirectives(params: {
};
}
export function resolveDefaultModel(params: { cfg: ClawdisConfig }): {
export function resolveDefaultModel(params: { cfg: ClawdbotConfig }): {
defaultProvider: string;
defaultModel: string;
aliasIndex: ModelAliasIndex;
+3 -3
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type {
GroupKeyResolution,
SessionEntry,
@@ -7,7 +7,7 @@ import { normalizeGroupActivation } from "../group-activation.js";
import type { TemplateContext } from "../templating.js";
export function resolveGroupRequireMention(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
ctx: TemplateContext;
groupResolution?: GroupKeyResolution;
}): boolean {
@@ -86,7 +86,7 @@ export function buildGroupIntro(params: {
: "Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included).";
const silenceLine =
activation === "always"
? `If no response is needed, reply with exactly "${params.silentToken}" (no other text) so Clawdis stays silent.`
? `If no response is needed, reply with exactly "${params.silentToken}" (no other text) so Clawdbot stays silent.`
: undefined;
const cautionLine =
activation === "always"
+2 -2
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { MsgContext } from "../templating.js";
export function stripStructuralPrefixes(text: string): string {
@@ -18,7 +18,7 @@ export function stripStructuralPrefixes(text: string): string {
export function stripMentions(
text: string,
ctx: MsgContext,
cfg: ClawdisConfig | undefined,
cfg: ClawdbotConfig | undefined,
): string {
let result = text;
const patterns = cfg?.routing?.groupChat?.mentionPatterns ?? [];
+4 -4
View File
@@ -8,7 +8,7 @@ import {
resolveModelRefFromString,
resolveThinkingDefault,
} from "../../agents/model-selection.js";
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import type { ThinkLevel } from "./directives.js";
@@ -32,8 +32,8 @@ type ModelSelectionState = {
};
export async function createModelSelectionState(params: {
cfg: ClawdisConfig;
agentCfg: ClawdisConfig["agent"] | undefined;
cfg: ClawdbotConfig;
agentCfg: ClawdbotConfig["agent"] | undefined;
sessionEntry?: SessionEntry;
sessionStore?: Record<string, SessionEntry>;
sessionKey?: string;
@@ -177,7 +177,7 @@ export function resolveModelDirectiveSelection(params: {
}
export function resolveContextTokens(params: {
agentCfg: ClawdisConfig["agent"] | undefined;
agentCfg: ClawdbotConfig["agent"] | undefined;
model: string;
}): number {
return (
+3 -3
View File
@@ -1,6 +1,6 @@
import type { SkillSnapshot } from "../../agents/skills.js";
import { parseDurationMs } from "../../cli/parse-duration.js";
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import type { SessionEntry } from "../../config/sessions.js";
import { defaultRuntime } from "../../runtime.js";
import type { ElevatedLevel, ThinkLevel, VerboseLevel } from "./directives.js";
@@ -28,7 +28,7 @@ export type FollowupRun = {
surface?: string;
sessionFile: string;
workspaceDir: string;
config: ClawdisConfig;
config: ClawdbotConfig;
skillsSnapshot?: SkillSnapshot;
provider: string;
model: string;
@@ -435,7 +435,7 @@ function defaultQueueModeForSurface(surface?: string): QueueMode {
return "collect";
}
export function resolveQueueSettings(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
surface?: string;
sessionEntry?: SessionEntry;
inlineMode?: QueueMode;
+3 -3
View File
@@ -1,13 +1,13 @@
import crypto from "node:crypto";
import { buildWorkspaceSkillSnapshot } from "../../agents/skills.js";
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
import { buildProviderSummary } from "../../infra/provider-summary.js";
import { drainSystemEvents } from "../../infra/system-events.js";
export async function prependSystemEvents(params: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
isMainSession: boolean;
isNewSession: boolean;
prefixedBodyBase: string;
@@ -49,7 +49,7 @@ export async function ensureSkillSnapshot(params: {
sessionId?: string;
isFirstTurnInSession: boolean;
workspaceDir: string;
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
}): Promise<{
sessionEntry?: SessionEntry;
skillsSnapshot?: SessionEntry["skillsSnapshot"];
+2 -2
View File
@@ -1,6 +1,6 @@
import crypto from "node:crypto";
import type { ClawdisConfig } from "../../config/config.js";
import type { ClawdbotConfig } from "../../config/config.js";
import {
buildGroupDisplayName,
DEFAULT_IDLE_MINUTES,
@@ -36,7 +36,7 @@ export type SessionInitResult = {
export async function initSessionState(params: {
ctx: MsgContext;
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
}): Promise<SessionInitResult> {
const { ctx, cfg } = params;
const sessionCfg = cfg.session;
+3 -3
View File
@@ -74,7 +74,7 @@ describe("buildStatusMessage", () => {
});
it("prefers cached prompt tokens from the session log", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-status-"));
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-status-"));
const previousHome = process.env.HOME;
process.env.HOME = dir;
try {
@@ -83,11 +83,11 @@ describe("buildStatusMessage", () => {
"./status.js"
);
const storePath = path.join(dir, ".clawdis", "sessions", "sessions.json");
const storePath = path.join(dir, ".clawdbot", "sessions", "sessions.json");
const sessionId = "sess-1";
const logPath = path.join(
dir,
".clawdis",
".clawdbot",
"sessions",
`${sessionId}.jsonl`,
);
+4 -4
View File
@@ -12,7 +12,7 @@ import {
normalizeUsage,
type UsageLike,
} from "../agents/usage.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
resolveSessionTranscriptPath,
type SessionEntry,
@@ -21,7 +21,7 @@ import {
import { shortenHomePath } from "../utils.js";
import type { ThinkLevel, VerboseLevel } from "./thinking.js";
type AgentConfig = NonNullable<ClawdisConfig["agent"]>;
type AgentConfig = NonNullable<ClawdbotConfig["agent"]>;
type StatusArgs = {
agent: AgentConfig;
@@ -79,7 +79,7 @@ const readUsageFromSessionLog = (
model?: string;
}
| undefined => {
// Transcripts always live at: ~/.clawdis/sessions/<SessionId>.jsonl
// Transcripts always live at: ~/.clawdbot/sessions/<SessionId>.jsonl
if (!sessionId) return undefined;
const logPath = resolveSessionTranscriptPath(sessionId);
if (!fs.existsSync(logPath)) return undefined;
@@ -166,7 +166,7 @@ export function buildStatusMessage(args: StatusArgs): string {
const webLine = (() => {
if (args.webLinked === false) {
return "Web: not linked — run `clawdis login` to scan the QR.";
return "Web: not linked — run `clawdbot login` to scan the QR.";
}
const authAge = formatAge(args.webAuthAgeMs);
const heartbeat =
+1 -1
View File
@@ -26,7 +26,7 @@ describe("transcribeInboundAudio", () => {
it("downloads mediaUrl to temp file and returns transcript", async () => {
const tmpBuf = Buffer.from("audio-bytes");
const tmpFile = path.join(os.tmpdir(), `clawdis-audio-${Date.now()}.ogg`);
const tmpFile = path.join(os.tmpdir(), `clawdbot-audio-${Date.now()}.ogg`);
await fs.writeFile(tmpFile, tmpBuf);
const fetchMock = vi.fn(async () => ({
+3 -3
View File
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { logVerbose, shouldLogVerbose } from "../globals.js";
import { runExec } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js";
@@ -14,7 +14,7 @@ export function isAudio(mediaType?: string | null) {
}
export async function transcribeInboundAudio(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
ctx: MsgContext,
runtime: RuntimeEnv,
): Promise<{ text: string } | undefined> {
@@ -32,7 +32,7 @@ export async function transcribeInboundAudio(
const buffer = Buffer.from(arrayBuf);
tmpPath = path.join(
os.tmpdir(),
`clawdis-audio-${crypto.randomUUID()}.ogg`,
`clawdbot-audio-${crypto.randomUUID()}.ogg`,
);
await fs.writeFile(tmpPath, buffer);
mediaPath = tmpPath;
+4 -4
View File
@@ -29,7 +29,7 @@ describe("browser chrome profile decoration", () => {
it("writes expected name + signed ARGB seed to Chrome prefs", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
path.join(os.tmpdir(), "clawdbot-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
@@ -71,7 +71,7 @@ describe("browser chrome profile decoration", () => {
it("best-effort writes name when color is invalid", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
path.join(os.tmpdir(), "clawdbot-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: "lobster-orange" });
@@ -89,7 +89,7 @@ describe("browser chrome profile decoration", () => {
it("recovers from missing/invalid preference files", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
path.join(os.tmpdir(), "clawdbot-chrome-test-"),
);
try {
await fsp.mkdir(path.join(userDataDir, "Default"), { recursive: true });
@@ -116,7 +116,7 @@ describe("browser chrome profile decoration", () => {
it("is idempotent when rerun on an existing profile", async () => {
const userDataDir = await fsp.mkdtemp(
path.join(os.tmpdir(), "clawdis-chrome-test-"),
path.join(os.tmpdir(), "clawdbot-chrome-test-"),
);
try {
decorateClawdProfile(userDataDir, { color: DEFAULT_CLAWD_BROWSER_COLOR });
+1 -1
View File
@@ -15,7 +15,7 @@ function enhanceBrowserFetchError(
const code = extractErrorCode(cause) ?? extractErrorCode(err) ?? "";
const hint =
"Start (or restart) the Clawdis gateway (Clawdis.app menubar, or `clawdis gateway`) and try again.";
"Start (or restart) the Clawdbot gateway (Clawdbot.app menubar, or `clawdbot gateway`) and try again.";
if (code === "ECONNREFUSED") {
return new Error(
+3 -3
View File
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import type { BrowserProfileConfig, ClawdisConfig } from "../config/config.js";
import type { BrowserProfileConfig, ClawdbotConfig } from "../config/config.js";
import { loadConfig, writeConfigFile } from "../config/config.js";
import { resolveClawdUserDataDir } from "./chrome.js";
import { parseHttpUrl, resolveProfile } from "./config.js";
@@ -86,7 +86,7 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) {
profileConfig = { cdpPort, color: profileColor };
}
const nextConfig: ClawdisConfig = {
const nextConfig: ClawdbotConfig = {
...cfg,
browser: {
...cfg.browser,
@@ -157,7 +157,7 @@ export function createBrowserProfilesService(ctx: BrowserRouteContext) {
}
const { [name]: _removed, ...remainingProfiles } = profiles;
const nextConfig: ClawdisConfig = {
const nextConfig: ClawdbotConfig = {
...cfg,
browser: {
...cfg.browser,
+1 -1
View File
@@ -277,7 +277,7 @@ function createProfileContext(
// HTTP responds but WebSocket fails - port in use by something else
if (!profileState.running) {
throw new Error(
`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by clawdis. ` +
`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by clawdbot. ` +
`Run action=reset-profile profile=${profile.name} to kill the process.`,
);
}
+11 -11
View File
@@ -5,9 +5,9 @@ import { fileURLToPath } from "node:url";
import { detectMime } from "../media/mime.js";
export const A2UI_PATH = "/__clawdis__/a2ui";
export const CANVAS_HOST_PATH = "/__clawdis__/canvas";
export const CANVAS_WS_PATH = "/__clawdis/ws";
export const A2UI_PATH = "/__clawdbot__/a2ui";
export const CANVAS_HOST_PATH = "/__clawdbot__/canvas";
export const CANVAS_WS_PATH = "/__clawdbot/ws";
let cachedA2uiRootReal: string | null | undefined;
let resolvingA2uiRoot: Promise<string | null> | null = null;
@@ -98,9 +98,9 @@ export function injectCanvasLiveReload(html: string): string {
(() => {
// Cross-platform action bridge helper.
// Works on:
// - iOS: window.webkit.messageHandlers.clawdisCanvasA2UIAction.postMessage(...)
// - Android: window.clawdisCanvasA2UIAction.postMessage(...)
const actionHandlerName = "clawdisCanvasA2UIAction";
// - iOS: window.webkit.messageHandlers.clawdbotCanvasA2UIAction.postMessage(...)
// - Android: window.clawdbotCanvasA2UIAction.postMessage(...)
const actionHandlerName = "clawdbotCanvasA2UIAction";
function postToNode(payload) {
try {
const raw = typeof payload === "string" ? payload : JSON.stringify(payload);
@@ -125,11 +125,11 @@ export function injectCanvasLiveReload(html: string): string {
const action = { ...userAction, id };
return postToNode({ userAction: action });
}
globalThis.Clawdis = globalThis.Clawdis ?? {};
globalThis.Clawdis.postMessage = postToNode;
globalThis.Clawdis.sendUserAction = sendUserAction;
globalThis.clawdisPostMessage = postToNode;
globalThis.clawdisSendUserAction = sendUserAction;
globalThis.Clawdbot = globalThis.Clawdbot ?? {};
globalThis.Clawdbot.postMessage = postToNode;
globalThis.Clawdbot.sendUserAction = sendUserAction;
globalThis.clawdbotPostMessage = postToNode;
globalThis.clawdbotSendUserAction = sendUserAction;
try {
const proto = location.protocol === "https:" ? "wss" : "ws";
+18 -18
View File
@@ -17294,7 +17294,7 @@ const cardShadow = isAndroid ? "0 2px 10px rgba(0,0,0,.18)" : "0 10px 30px rgba(
const buttonShadow = isAndroid ? "0 2px 10px rgba(6, 182, 212, 0.14)" : "0 10px 25px rgba(6, 182, 212, 0.18)";
const statusShadow = isAndroid ? "0 2px 10px rgba(0, 0, 0, 0.18)" : "0 10px 24px rgba(0, 0, 0, 0.25)";
const statusBlur = isAndroid ? "10px" : "14px";
const clawdisTheme = {
const clawdbotTheme = {
components: {
AudioPlayer: emptyClasses(),
Button: emptyClasses(),
@@ -17450,7 +17450,7 @@ const clawdisTheme = {
Image: { borderRadius: "12px" }
}
};
var ClawdisA2UIHost = class extends i$6 {
var ClawdbotA2UIHost = class extends i$6 {
static properties = {
surfaces: { state: true },
pendingAction: { state: true },
@@ -17459,7 +17459,7 @@ var ClawdisA2UIHost = class extends i$6 {
#processor = Data.createSignalA2uiMessageProcessor();
#themeProvider = new i$3(this, {
context: themeContext,
initialValue: clawdisTheme
initialValue: clawdbotTheme
});
surfaces = [];
pendingAction = null;
@@ -17472,10 +17472,10 @@ var ClawdisA2UIHost = class extends i$6 {
position: relative;
box-sizing: border-box;
padding:
var(--clawdis-a2ui-inset-top, 0px)
var(--clawdis-a2ui-inset-right, 0px)
var(--clawdis-a2ui-inset-bottom, 0px)
var(--clawdis-a2ui-inset-left, 0px);
var(--clawdbot-a2ui-inset-top, 0px)
var(--clawdbot-a2ui-inset-right, 0px)
var(--clawdbot-a2ui-inset-bottom, 0px)
var(--clawdbot-a2ui-inset-left, 0px);
}
#surfaces {
@@ -17484,14 +17484,14 @@ var ClawdisA2UIHost = class extends i$6 {
gap: 12px;
height: 100%;
overflow: auto;
padding-bottom: var(--clawdis-a2ui-scroll-pad-bottom, 0px);
padding-bottom: var(--clawdbot-a2ui-scroll-pad-bottom, 0px);
}
.status {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--clawdis-a2ui-status-top, 12px);
top: var(--clawdbot-a2ui-status-top, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -17512,7 +17512,7 @@ var ClawdisA2UIHost = class extends i$6 {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: var(--clawdis-a2ui-toast-bottom, 12px);
bottom: var(--clawdbot-a2ui-toast-bottom, 12px);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -17538,7 +17538,7 @@ var ClawdisA2UIHost = class extends i$6 {
position: absolute;
left: 50%;
transform: translateX(-50%);
top: var(--clawdis-a2ui-empty-top, var(--clawdis-a2ui-status-top, 12px));
top: var(--clawdbot-a2ui-empty-top, var(--clawdbot-a2ui-status-top, 12px));
text-align: center;
opacity: 0.8;
padding: 10px 12px;
@@ -17570,20 +17570,20 @@ var ClawdisA2UIHost = class extends i$6 {
`;
connectedCallback() {
super.connectedCallback();
globalThis.clawdisA2UI = {
globalThis.clawdbotA2UI = {
applyMessages: (messages) => this.applyMessages(messages),
reset: () => this.reset(),
getSurfaces: () => Array.from(this.#processor.getSurfaces().keys())
};
this.addEventListener("a2uiaction", (evt) => this.#handleA2UIAction(evt));
this.#statusListener = (evt) => this.#handleActionStatus(evt);
globalThis.addEventListener("clawdis:a2ui-action-status", this.#statusListener);
globalThis.addEventListener("clawdbot:a2ui-action-status", this.#statusListener);
this.#syncSurfaces();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this.#statusListener) {
globalThis.removeEventListener("clawdis:a2ui-action-status", this.#statusListener);
globalThis.removeEventListener("clawdbot:a2ui-action-status", this.#statusListener);
this.#statusListener = null;
}
}
@@ -17688,11 +17688,11 @@ var ClawdisA2UIHost = class extends i$6 {
timestamp: new Date().toISOString(),
...Object.keys(context).length ? { context } : {}
};
globalThis.__clawdisLastA2UIAction = userAction;
const handler = globalThis.webkit?.messageHandlers?.clawdisCanvasA2UIAction ?? globalThis.clawdisCanvasA2UIAction;
globalThis.__clawdbotLastA2UIAction = userAction;
const handler = globalThis.webkit?.messageHandlers?.clawdbotCanvasA2UIAction ?? globalThis.clawdbotCanvasA2UIAction;
if (handler?.postMessage) {
try {
if (handler === globalThis.clawdisCanvasA2UIAction) {
if (handler === globalThis.clawdbotCanvasA2UIAction) {
handler.postMessage(JSON.stringify({ userAction }));
} else {
handler.postMessage({ userAction });
@@ -17765,4 +17765,4 @@ var ClawdisA2UIHost = class extends i$6 {
</section>`;
}
};
customElements.define("clawdis-a2ui-host", ClawdisA2UIHost);
customElements.define("clawdbot-a2ui-host", ClawdbotA2UIHost);
+28 -28
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Clawdis Canvas</title>
<title>Clawdbot Canvas</title>
<script>
(() => {
try {
@@ -57,7 +57,7 @@
backface-visibility: hidden;
opacity: 0.45;
pointer-events: none;
animation: clawdis-grid-drift 140s ease-in-out infinite alternate;
animation: clawdbot-grid-drift 140s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::before { opacity: 0.80; }
body::after {
@@ -75,7 +75,7 @@
backface-visibility: hidden;
transform: translate3d(0,0,0);
pointer-events: none;
animation: clawdis-glow-drift 110s ease-in-out infinite alternate;
animation: clawdbot-glow-drift 110s ease-in-out infinite alternate;
}
:root[data-platform="android"] body::after { opacity: 0.85; }
@supports (mix-blend-mode: screen) {
@@ -84,12 +84,12 @@
@supports not (mix-blend-mode: screen) {
body::after { opacity: 0.70; }
}
@keyframes clawdis-grid-drift {
@keyframes clawdbot-grid-drift {
0% { transform: translate3d(-12px, 8px, 0) rotate(-7deg); opacity: 0.40; }
50% { transform: translate3d( 10px,-7px, 0) rotate(-6.6deg); opacity: 0.56; }
100% { transform: translate3d(-8px, 6px, 0) rotate(-7.2deg); opacity: 0.42; }
}
@keyframes clawdis-glow-drift {
@keyframes clawdbot-glow-drift {
0% { transform: translate3d(-18px, 12px, 0) scale(1.02); opacity: 0.40; }
50% { transform: translate3d( 14px,-10px, 0) scale(1.05); opacity: 0.52; }
100% { transform: translate3d(-10px, 8px, 0) scale(1.03); opacity: 0.43; }
@@ -103,14 +103,14 @@
touch-action: none;
z-index: 1;
}
:root[data-platform="android"] #clawdis-canvas {
:root[data-platform="android"] #clawdbot-canvas {
background:
radial-gradient(1100px 800px at 20% 15%, rgba(42, 113, 255, 0.78), rgba(0,0,0,0) 58%),
radial-gradient(900px 650px at 82% 28%, rgba(255, 0, 138, 0.66), rgba(0,0,0,0) 62%),
radial-gradient(1000px 900px at 60% 88%, rgba(0, 209, 255, 0.58), rgba(0,0,0,0) 62%),
#141c33;
}
#clawdis-status {
#clawdbot-status {
position: fixed;
inset: 0;
display: none;
@@ -122,7 +122,7 @@
pointer-events: none;
z-index: 3;
}
#clawdis-status .card {
#clawdbot-status .card {
width: min(560px, 88vw);
text-align: left;
padding: 14px 16px 12px;
@@ -134,52 +134,52 @@
-webkit-backdrop-filter: blur(18px) saturate(140%);
backdrop-filter: blur(18px) saturate(140%);
}
#clawdis-status .title {
#clawdbot-status .title {
font: 600 12px/1.2 -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
letter-spacing: 0.45px;
text-transform: uppercase;
color: rgba(255,255,255,0.7);
}
#clawdis-status .subtitle {
#clawdbot-status .subtitle {
margin-top: 8px;
font: 500 13px/1.45 -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
color: rgba(255,255,255,0.9);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
clawdis-a2ui-host {
clawdbot-a2ui-host {
display: block;
height: 100%;
position: fixed;
inset: 0;
z-index: 4;
--clawdis-a2ui-inset-top: 28px;
--clawdis-a2ui-inset-right: 0px;
--clawdis-a2ui-inset-bottom: 0px;
--clawdis-a2ui-inset-left: 0px;
--clawdis-a2ui-scroll-pad-bottom: 0px;
--clawdis-a2ui-status-top: calc(50% - 18px);
--clawdis-a2ui-empty-top: 18px;
--clawdbot-a2ui-inset-top: 28px;
--clawdbot-a2ui-inset-right: 0px;
--clawdbot-a2ui-inset-bottom: 0px;
--clawdbot-a2ui-inset-left: 0px;
--clawdbot-a2ui-scroll-pad-bottom: 0px;
--clawdbot-a2ui-status-top: calc(50% - 18px);
--clawdbot-a2ui-empty-top: 18px;
}
</style>
</head>
<body>
<canvas id="clawdis-canvas"></canvas>
<div id="clawdis-status">
<canvas id="clawdbot-canvas"></canvas>
<div id="clawdbot-status">
<div class="card">
<div class="title" id="clawdis-status-title">Ready</div>
<div class="subtitle" id="clawdis-status-subtitle">Waiting for agent</div>
<div class="title" id="clawdbot-status-title">Ready</div>
<div class="subtitle" id="clawdbot-status-subtitle">Waiting for agent</div>
</div>
</div>
<clawdis-a2ui-host></clawdis-a2ui-host>
<clawdbot-a2ui-host></clawdbot-a2ui-host>
<script src="a2ui.bundle.js"></script>
<script>
(() => {
const canvas = document.getElementById('clawdis-canvas');
const canvas = document.getElementById('clawdbot-canvas');
const ctx = canvas.getContext('2d');
const statusEl = document.getElementById('clawdis-status');
const titleEl = document.getElementById('clawdis-status-title');
const subtitleEl = document.getElementById('clawdis-status-subtitle');
const statusEl = document.getElementById('clawdbot-status');
const titleEl = document.getElementById('clawdbot-status-title');
const subtitleEl = document.getElementById('clawdbot-status-subtitle');
const debugStatusEnabledByQuery = (() => {
try {
const params = new URLSearchParams(window.location.search);
@@ -217,7 +217,7 @@
statusEl.style.display = 'none';
}
window.__clawdis = {
window.__clawdbot = {
canvas,
ctx,
setDebugStatusEnabled,
+14 -14
View File
@@ -19,12 +19,12 @@ describe("canvas host", () => {
const out = injectCanvasLiveReload("<html><body>Hello</body></html>");
expect(out).toContain(CANVAS_WS_PATH);
expect(out).toContain("location.reload");
expect(out).toContain("clawdisCanvasA2UIAction");
expect(out).toContain("clawdisSendUserAction");
expect(out).toContain("clawdbotCanvasA2UIAction");
expect(out).toContain("clawdbotSendUserAction");
});
it("creates a default index.html when missing", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const server = await startCanvasHost({
runtime: defaultRuntime,
@@ -41,7 +41,7 @@ describe("canvas host", () => {
const html = await res.text();
expect(res.status).toBe(200);
expect(html).toContain("Interactive test page");
expect(html).toContain("clawdisSendUserAction");
expect(html).toContain("clawdbotSendUserAction");
expect(html).toContain(CANVAS_WS_PATH);
} finally {
await server.close();
@@ -50,7 +50,7 @@ describe("canvas host", () => {
});
it("skips live reload injection when disabled", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
await fs.writeFile(
path.join(dir, "index.html"),
"<html><body>no-reload</body></html>",
@@ -86,7 +86,7 @@ describe("canvas host", () => {
});
it("serves canvas content from the mounted base path", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
await fs.writeFile(
path.join(dir, "index.html"),
"<html><body>v1</body></html>",
@@ -137,7 +137,7 @@ describe("canvas host", () => {
});
it("reuses a handler without closing it twice", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
await fs.writeFile(
path.join(dir, "index.html"),
"<html><body>v1</body></html>",
@@ -174,7 +174,7 @@ describe("canvas host", () => {
});
it("serves HTML with injection and broadcasts reload on file changes", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const index = path.join(dir, "index.html");
await fs.writeFile(index, "<html><body>v1</body></html>", "utf8");
@@ -234,7 +234,7 @@ describe("canvas host", () => {
});
it("serves the gateway-hosted A2UI scaffold", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-canvas-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-canvas-"));
const server = await startCanvasHost({
runtime: defaultRuntime,
@@ -246,19 +246,19 @@ describe("canvas host", () => {
try {
const res = await fetch(
`http://127.0.0.1:${server.port}/__clawdis__/a2ui/`,
`http://127.0.0.1:${server.port}/__clawdbot__/a2ui/`,
);
const html = await res.text();
expect(res.status).toBe(200);
expect(html).toContain("clawdis-a2ui-host");
expect(html).toContain("clawdisCanvasA2UIAction");
expect(html).toContain("clawdbot-a2ui-host");
expect(html).toContain("clawdbotCanvasA2UIAction");
const bundleRes = await fetch(
`http://127.0.0.1:${server.port}/__clawdis__/a2ui/a2ui.bundle.js`,
`http://127.0.0.1:${server.port}/__clawdbot__/a2ui/a2ui.bundle.js`,
);
const js = await bundleRes.text();
expect(bundleRes.status).toBe(200);
expect(js).toContain("clawdisA2UI");
expect(js).toContain("clawdbotA2UI");
} finally {
await server.close();
await fs.rm(dir, { recursive: true, force: true });
+10 -10
View File
@@ -68,7 +68,7 @@ function defaultIndexHTML() {
return `<!doctype html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Clawdis Canvas</title>
<title>Clawdbot Canvas</title>
<style>
html, body { height: 100%; margin: 0; background: #000; color: #fff; font: 16px/1.4 -apple-system, BlinkMacSystemFont, system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif; }
.wrap { min-height: 100%; display: grid; place-items: center; padding: 24px; }
@@ -86,7 +86,7 @@ function defaultIndexHTML() {
<div class="wrap">
<div class="card">
<div class="title">
<h1>Clawdis Canvas</h1>
<h1>Clawdbot Canvas</h1>
<div class="sub">Interactive test page (auto-reload enabled)</div>
</div>
@@ -107,26 +107,26 @@ function defaultIndexHTML() {
const statusEl = document.getElementById("status");
const log = (msg) => { logEl.textContent = String(msg); };
const hasIOS = () => !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.clawdisCanvasA2UIAction);
const hasAndroid = () => !!(window.clawdisCanvasA2UIAction && typeof window.clawdisCanvasA2UIAction.postMessage === "function");
const hasHelper = () => typeof window.clawdisSendUserAction === "function";
const hasIOS = () => !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.clawdbotCanvasA2UIAction);
const hasAndroid = () => !!(window.clawdbotCanvasA2UIAction && typeof window.clawdbotCanvasA2UIAction.postMessage === "function");
const hasHelper = () => typeof window.clawdbotSendUserAction === "function";
statusEl.innerHTML =
"Bridge: " +
(hasHelper() ? "<span class='ok'>ready</span>" : "<span class='bad'>missing</span>") +
" · iOS=" + (hasIOS() ? "yes" : "no") +
" · Android=" + (hasAndroid() ? "yes" : "no");
window.addEventListener("clawdis:a2ui-action-status", (ev) => {
window.addEventListener("clawdbot:a2ui-action-status", (ev) => {
const d = ev && ev.detail || {};
log("Action status: id=" + (d.id || "?") + " ok=" + String(!!d.ok) + (d.error ? (" error=" + d.error) : ""));
});
function send(name, sourceComponentId) {
if (!hasHelper()) {
log("No action bridge found. Ensure you're viewing this on an iOS/Android Clawdis node canvas.");
log("No action bridge found. Ensure you're viewing this on an iOS/Android Clawdbot node canvas.");
return;
}
const ok = window.clawdisSendUserAction({
const ok = window.clawdbotSendUserAction({
name,
surfaceId: "main",
sourceComponentId,
@@ -184,7 +184,7 @@ async function resolveFilePath(rootReal: string, urlPath: string) {
}
function isDisabledByEnv() {
if (process.env.CLAWDIS_SKIP_CANVAS_HOST === "1") return true;
if (process.env.CLAWDBOT_SKIP_CANVAS_HOST === "1") return true;
if (process.env.NODE_ENV === "test") return true;
if (process.env.VITEST) return true;
return false;
@@ -341,7 +341,7 @@ export async function createCanvasHostHandler(
res.statusCode = 404;
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(
`<!doctype html><meta charset="utf-8" /><title>Clawdis Canvas</title><pre>Missing file.\nCreate ${rootDir}/index.html</pre>`,
`<!doctype html><meta charset="utf-8" /><title>Clawdbot Canvas</title><pre>Missing file.\nCreate ${rootDir}/index.html</pre>`,
);
return true;
}
+27 -27
View File
@@ -1,32 +1,32 @@
export const browserCoreExamples = [
"clawdis browser status",
"clawdis browser start",
"clawdis browser stop",
"clawdis browser tabs",
"clawdis browser open https://example.com",
"clawdis browser focus abcd1234",
"clawdis browser close abcd1234",
"clawdis browser screenshot",
"clawdis browser screenshot --full-page",
"clawdis browser screenshot --ref 12",
"clawdis browser snapshot",
"clawdis browser snapshot --format aria --limit 200",
"clawdbot browser status",
"clawdbot browser start",
"clawdbot browser stop",
"clawdbot browser tabs",
"clawdbot browser open https://example.com",
"clawdbot browser focus abcd1234",
"clawdbot browser close abcd1234",
"clawdbot browser screenshot",
"clawdbot browser screenshot --full-page",
"clawdbot browser screenshot --ref 12",
"clawdbot browser snapshot",
"clawdbot browser snapshot --format aria --limit 200",
];
export const browserActionExamples = [
"clawdis browser navigate https://example.com",
"clawdis browser resize 1280 720",
"clawdis browser click 12 --double",
'clawdis browser type 23 "hello" --submit',
"clawdis browser press Enter",
"clawdis browser hover 44",
"clawdis browser drag 10 11",
"clawdis browser select 9 OptionA OptionB",
"clawdis browser upload /tmp/file.pdf",
'clawdis browser fill --fields \'[{"ref":"1","value":"Ada"}]\'',
"clawdis browser dialog --accept",
'clawdis browser wait --text "Done"',
"clawdis browser evaluate --fn '(el) => el.textContent' --ref 7",
"clawdis browser console --level error",
"clawdis browser pdf",
"clawdbot browser navigate https://example.com",
"clawdbot browser resize 1280 720",
"clawdbot browser click 12 --double",
'clawdbot browser type 23 "hello" --submit',
"clawdbot browser press Enter",
"clawdbot browser hover 44",
"clawdbot browser drag 10 11",
"clawdbot browser select 9 OptionA OptionB",
"clawdbot browser upload /tmp/file.pdf",
'clawdbot browser fill --fields \'[{"ref":"1","value":"Ada"}]\'',
"clawdbot browser dialog --accept",
'clawdbot browser wait --text "Done"',
"clawdbot browser evaluate --fn '(el) => el.textContent' --ref 7",
"clawdbot browser console --level error",
"clawdbot browser pdf",
];
+2 -2
View File
@@ -18,7 +18,7 @@ export function registerBrowserCli(program: Command) {
.description("Manage clawd's dedicated browser (Chrome/Chromium)")
.option(
"--url <url>",
"Override browser control URL (default from ~/.clawdis/clawdis.json)",
"Override browser control URL (default from ~/.clawdbot/clawdbot.json)",
)
.option("--profile <name>", "Browser profile name (default from config)")
.option("--json", "Output machine-readable JSON", false)
@@ -29,7 +29,7 @@ export function registerBrowserCli(program: Command) {
.action(() => {
browser.outputHelp();
defaultRuntime.error(
danger('Missing subcommand. Try: "clawdis browser status"'),
danger('Missing subcommand. Try: "clawdbot browser status"'),
);
defaultRuntime.exit(1);
});
+1 -1
View File
@@ -499,7 +499,7 @@ export function registerCanvasCli(program: Command) {
const { version, messageCount } = validateA2UIJsonl(jsonl);
if (version === "v0.9") {
throw new Error(
"Detected A2UI v0.9 JSONL (createSurface). Clawdis currently supports v0.8 only.",
"Detected A2UI v0.9 JSONL (createSurface). Clawdbot currently supports v0.8 only.",
);
}
await invokeCanvas(opts, "canvas.a2ui.pushJSONL", { jsonl });
+6 -6
View File
@@ -103,7 +103,7 @@ export function registerDnsCli(program: Command) {
dns
.command("setup")
.description(
"Set up CoreDNS to serve clawdis.internal for unicast DNS-SD (Wide-Area Bonjour)",
"Set up CoreDNS to serve clawdbot.internal for unicast DNS-SD (Wide-Area Bonjour)",
)
.option(
"--apply",
@@ -122,7 +122,7 @@ export function registerDnsCli(program: Command) {
`Detected tailnet IP: ${tailnetIPv4 ?? "—"}${tailnetIPv6 ? ` (v6 ${tailnetIPv6})` : ""}`,
);
console.log("");
console.log("Recommended ~/.clawdis/clawdis.json:");
console.log("Recommended ~/.clawdbot/clawdbot.json:");
console.log(
JSON.stringify(
{
@@ -138,7 +138,7 @@ export function registerDnsCli(program: Command) {
console.log(
`- Add nameserver: ${tailnetIPv4 ?? "<this machine's tailnet IPv4>"}`,
);
console.log(`- Restrict to domain (Split DNS): clawdis.internal`);
console.log(`- Restrict to domain (Split DNS): clawdbot.internal`);
if (!opts.apply) {
console.log("");
@@ -160,7 +160,7 @@ export function registerDnsCli(program: Command) {
const corefilePath = path.join(etcDir, "Corefile");
const confDir = path.join(etcDir, "conf.d");
const importGlob = path.join(confDir, "*.server");
const serverPath = path.join(confDir, "clawdis.internal.server");
const serverPath = path.join(confDir, "clawdbot.internal.server");
run("brew", ["list", "coredns"], { allowFailure: true });
run("brew", ["install", "coredns"], {
@@ -202,7 +202,7 @@ export function registerDnsCli(program: Command) {
const serial = `${y}${m}${d}01`;
const zoneLines = [
`; created by clawdis dns setup (will be overwritten by the gateway when wide-area discovery is enabled)`,
`; created by clawdbot dns setup (will be overwritten by the gateway when wide-area discovery is enabled)`,
`$ORIGIN ${WIDE_AREA_DISCOVERY_DOMAIN}`,
`$TTL 60`,
`@ IN SOA ns1 hostmaster ${serial} 7200 3600 1209600 60`,
@@ -224,7 +224,7 @@ export function registerDnsCli(program: Command) {
if (cfg.discovery?.wideArea?.enabled !== true) {
console.log("");
console.log(
"Note: enable discovery.wideArea.enabled in ~/.clawdis/clawdis.json on the gateway and restart the gateway so it writes the DNS-SD zone.",
"Note: enable discovery.wideArea.enabled in ~/.clawdbot/clawdbot.json on the gateway and restart the gateway so it writes the DNS-SD zone.",
);
}
});
+7 -7
View File
@@ -2,7 +2,7 @@ import fs from "node:fs";
import type { Command } from "commander";
import {
CONFIG_PATH_CLAWDIS,
CONFIG_PATH_CLAWDBOT,
loadConfig,
resolveGatewayPort,
} from "../config/config.js";
@@ -160,7 +160,7 @@ export function registerGatewayCli(program: Command) {
)
.option(
"--token <token>",
"Shared token required in connect.params.auth.token (default: CLAWDIS_GATEWAY_TOKEN env if set)",
"Shared token required in connect.params.auth.token (default: CLAWDBOT_GATEWAY_TOKEN env if set)",
)
.option("--auth <mode>", 'Gateway auth mode ("token"|"password")')
.option("--password <password>", "Password for auth mode=password")
@@ -218,7 +218,7 @@ export function registerGatewayCli(program: Command) {
return;
}
if (opts.token) {
process.env.CLAWDIS_GATEWAY_TOKEN = String(opts.token);
process.env.CLAWDBOT_GATEWAY_TOKEN = String(opts.token);
}
const authModeRaw = opts.auth ? String(opts.auth) : undefined;
const authMode =
@@ -305,7 +305,7 @@ export function registerGatewayCli(program: Command) {
)
.option(
"--token <token>",
"Shared token required in connect.params.auth.token (default: CLAWDIS_GATEWAY_TOKEN env if set)",
"Shared token required in connect.params.auth.token (default: CLAWDBOT_GATEWAY_TOKEN env if set)",
)
.option("--auth <mode>", 'Gateway auth mode ("token"|"password")')
.option("--password <password>", "Password for auth mode=password")
@@ -404,7 +404,7 @@ export function registerGatewayCli(program: Command) {
}
}
if (opts.token) {
process.env.CLAWDIS_GATEWAY_TOKEN = String(opts.token);
process.env.CLAWDBOT_GATEWAY_TOKEN = String(opts.token);
}
const authModeRaw = opts.auth ? String(opts.auth) : undefined;
const authMode =
@@ -430,12 +430,12 @@ export function registerGatewayCli(program: Command) {
defaultRuntime.exit(1);
return;
}
const configExists = fs.existsSync(CONFIG_PATH_CLAWDIS);
const configExists = fs.existsSync(CONFIG_PATH_CLAWDBOT);
const mode = cfg.gateway?.mode;
if (!opts.allowUnconfigured && mode !== "local") {
if (!configExists) {
defaultRuntime.error(
"Missing config. Run `clawdis setup` or set gateway.mode=local (or pass --allow-unconfigured).",
"Missing config. Run `clawdbot setup` or set gateway.mode=local (or pass --allow-unconfigured).",
);
} else {
defaultRuntime.error(
+8 -8
View File
@@ -78,7 +78,7 @@ describe("gateway SIGTERM", () => {
it("exits 0 on SIGTERM", { timeout: 90_000 }, async () => {
const port = await getFreePort();
const stateDir = fs.mkdtempSync(
path.join(os.tmpdir(), "clawdis-gateway-test-"),
path.join(os.tmpdir(), "clawdbot-gateway-test-"),
);
const out: string[] = [];
const err: string[] = [];
@@ -100,14 +100,14 @@ describe("gateway SIGTERM", () => {
cwd: process.cwd(),
env: {
...process.env,
CLAWDIS_STATE_DIR: stateDir,
CLAWDIS_CONFIG_PATH: path.join(stateDir, "clawdis.json"),
CLAWDIS_SKIP_PROVIDERS: "1",
CLAWDIS_SKIP_BROWSER_CONTROL_SERVER: "1",
CLAWDIS_SKIP_CANVAS_HOST: "1",
CLAWDBOT_STATE_DIR: stateDir,
CLAWDBOT_CONFIG_PATH: path.join(stateDir, "clawdbot.json"),
CLAWDBOT_SKIP_PROVIDERS: "1",
CLAWDBOT_SKIP_BROWSER_CONTROL_SERVER: "1",
CLAWDBOT_SKIP_CANVAS_HOST: "1",
// Avoid port collisions with other test processes that may also start a bridge server.
CLAWDIS_BRIDGE_HOST: "127.0.0.1",
CLAWDIS_BRIDGE_PORT: "0",
CLAWDBOT_BRIDGE_HOST: "127.0.0.1",
CLAWDBOT_BRIDGE_PORT: "0",
},
stdio: ["ignore", "pipe", "pipe"],
},
+5 -5
View File
@@ -30,7 +30,7 @@ export function registerHooksCli(program: Command) {
gmail
.command("setup")
.description("Configure Gmail watch + Pub/Sub + Clawdis hooks")
.description("Configure Gmail watch + Pub/Sub + Clawdbot hooks")
.requiredOption("--account <email>", "Gmail account to watch")
.option("--project <id>", "GCP project id (OAuth client owner)")
.option("--topic <name>", "Pub/Sub topic name", DEFAULT_GMAIL_TOPIC)
@@ -40,8 +40,8 @@ export function registerHooksCli(program: Command) {
DEFAULT_GMAIL_SUBSCRIPTION,
)
.option("--label <label>", "Gmail label to watch", DEFAULT_GMAIL_LABEL)
.option("--hook-url <url>", "Clawdis hook URL")
.option("--hook-token <token>", "Clawdis hook token")
.option("--hook-url <url>", "Clawdbot hook URL")
.option("--hook-token <token>", "Clawdbot hook token")
.option("--push-token <token>", "Push token for gog watch serve")
.option(
"--bind <host>",
@@ -90,8 +90,8 @@ export function registerHooksCli(program: Command) {
.option("--topic <topic>", "Pub/Sub topic path (projects/.../topics/..)")
.option("--subscription <name>", "Pub/Sub subscription name")
.option("--label <label>", "Gmail label to watch")
.option("--hook-url <url>", "Clawdis hook URL")
.option("--hook-token <token>", "Clawdis hook token")
.option("--hook-url <url>", "Clawdbot hook URL")
.option("--hook-token <token>", "Clawdbot hook token")
.option("--push-token <token>", "Push token for gog watch serve")
.option("--bind <host>", "gog watch serve bind host")
.option("--port <port>", "gog watch serve port")
+2 -2
View File
@@ -51,11 +51,11 @@ describe("nodes camera helpers", () => {
tmpDir: "/tmp",
id: "id1",
});
expect(p).toBe(path.join("/tmp", "clawdis-camera-snap-front-id1.jpg"));
expect(p).toBe(path.join("/tmp", "clawdbot-camera-snap-front-id1.jpg"));
});
it("writes base64 to file", async () => {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-test-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-test-"));
const out = path.join(dir, "x.bin");
await writeBase64ToFile(out, "aGk=");
await expect(fs.readFile(out, "utf8")).resolves.toBe("hi");
+1 -1
View File
@@ -81,7 +81,7 @@ export function cameraTempPath(opts: {
const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`;
return path.join(
tmpDir,
`clawdis-camera-${opts.kind}${facingPart}-${id}${ext}`,
`clawdbot-camera-${opts.kind}${facingPart}-${id}${ext}`,
);
}
+1 -1
View File
@@ -37,5 +37,5 @@ export function canvasSnapshotTempPath(opts: {
const tmpDir = opts.tmpDir ?? os.tmpdir();
const id = opts.id ?? randomUUID();
const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`;
return path.join(tmpDir, `clawdis-canvas-snapshot-${id}${ext}`);
return path.join(tmpDir, `clawdbot-canvas-snapshot-${id}${ext}`);
}
+1 -1
View File
@@ -35,6 +35,6 @@ describe("nodes screen helpers", () => {
tmpDir: "/tmp",
id: "id1",
});
expect(p).toBe("/tmp/clawdis-screen-record-id1.mp4");
expect(p).toBe("/tmp/clawdbot-screen-record-id1.mp4");
});
});
+1 -1
View File
@@ -49,7 +49,7 @@ export function screenRecordTempPath(opts: {
const tmpDir = opts.tmpDir ?? os.tmpdir();
const id = opts.id ?? randomUUID();
const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`;
return path.join(tmpDir, `clawdis-screen-record-${id}${ext}`);
return path.join(tmpDir, `clawdbot-screen-record-${id}${ext}`);
}
export async function writeScreenRecordToFile(
+3 -3
View File
@@ -381,7 +381,7 @@ describe("cli program", () => {
const out = String(runtime.log.mock.calls[0]?.[0] ?? "");
const mediaPath = out.replace(/^MEDIA:/, "").trim();
expect(mediaPath).toMatch(/clawdis-camera-clip-front-.*\.mp4$/);
expect(mediaPath).toMatch(/clawdbot-camera-clip-front-.*\.mp4$/);
try {
await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("hi");
@@ -607,7 +607,7 @@ describe("cli program", () => {
const out = String(runtime.log.mock.calls[0]?.[0] ?? "");
const mediaPath = out.replace(/^MEDIA:/, "").trim();
expect(mediaPath).toMatch(/clawdis-canvas-snapshot-.*\.png$/);
expect(mediaPath).toMatch(/clawdbot-canvas-snapshot-.*\.png$/);
try {
await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("hi");
@@ -645,7 +645,7 @@ describe("cli program", () => {
const out = String(runtime.log.mock.calls[0]?.[0] ?? "");
const mediaPath = out.replace(/^MEDIA:/, "").trim();
expect(mediaPath).toMatch(/clawdis-canvas-snapshot-.*\.png$/);
expect(mediaPath).toMatch(/clawdbot-canvas-snapshot-.*\.png$/);
try {
await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("hi");
+28 -28
View File
@@ -34,12 +34,12 @@ export function buildProgram() {
const TAGLINE =
"Send, receive, and auto-reply on WhatsApp (web) and Telegram (bot).";
program.name("clawdis").description("").version(PROGRAM_VERSION);
program.name("clawdbot").description("").version(PROGRAM_VERSION);
const formatIntroLine = (version: string, rich = true) => {
const base = `📡 clawdis ${version}${TAGLINE}`;
const base = `📡 clawdbot ${version}${TAGLINE}`;
return rich && chalk.level > 0
? `${chalk.bold.cyan("📡 clawdis")} ${chalk.white(version)} ${chalk.gray("—")} ${chalk.green(TAGLINE)}`
? `${chalk.bold.cyan("📡 clawdbot")} ${chalk.white(version)} ${chalk.gray("—")} ${chalk.green(TAGLINE)}`
: base;
};
@@ -80,32 +80,32 @@ export function buildProgram() {
.join("\n");
defaultRuntime.error(
danger(
`Legacy config entries detected. Run "clawdis doctor" (or ask your agent) to migrate.\n${issues}`,
`Legacy config entries detected. Run "clawdbot doctor" (or ask your agent) to migrate.\n${issues}`,
),
);
process.exit(1);
});
const examples = [
[
"clawdis login --verbose",
"clawdbot login --verbose",
"Link personal WhatsApp Web and show QR + connection logs.",
],
[
'clawdis send --to +15555550123 --message "Hi" --json',
'clawdbot send --to +15555550123 --message "Hi" --json',
"Send via your web session and print JSON result.",
],
["clawdis gateway --port 18789", "Run the WebSocket Gateway locally."],
["clawdbot gateway --port 18789", "Run the WebSocket Gateway locally."],
[
"clawdis gateway --force",
"clawdbot gateway --force",
"Kill anything bound to the default gateway port, then start it.",
],
["clawdis gateway ...", "Gateway control via WebSocket."],
["clawdbot gateway ...", "Gateway control via WebSocket."],
[
'clawdis agent --to +15555550123 --message "Run summary" --deliver',
'clawdbot agent --to +15555550123 --message "Run summary" --deliver',
"Talk directly to the agent using the Gateway; optionally send the WhatsApp reply.",
],
[
'clawdis send --provider telegram --to @mychat --message "Hi"',
'clawdbot send --provider telegram --to @mychat --message "Hi"',
"Send via your Telegram bot.",
],
] as const;
@@ -121,7 +121,7 @@ export function buildProgram() {
program
.command("setup")
.description("Initialize ~/.clawdis/clawdis.json and the agent workspace")
.description("Initialize ~/.clawdbot/clawdbot.json and the agent workspace")
.option(
"--workspace <dir>",
"Agent workspace directory (default: ~/clawd; stored as agent.workspace)",
@@ -327,10 +327,10 @@ export function buildProgram() {
"after",
`
Examples:
clawdis send --to +15555550123 --message "Hi"
clawdis send --to +15555550123 --message "Hi" --media photo.jpg
clawdis send --to +15555550123 --message "Hi" --dry-run # print payload only
clawdis send --to +15555550123 --message "Hi" --json # machine-readable result`,
clawdbot send --to +15555550123 --message "Hi"
clawdbot send --to +15555550123 --message "Hi" --media photo.jpg
clawdbot send --to +15555550123 --message "Hi" --dry-run # print payload only
clawdbot send --to +15555550123 --message "Hi" --json # machine-readable result`,
)
.action(async (opts) => {
setVerbose(Boolean(opts.verbose));
@@ -377,10 +377,10 @@ Examples:
"after",
`
Examples:
clawdis agent --to +15555550123 --message "status update"
clawdis agent --session-id 1234 --message "Summarize inbox" --thinking medium
clawdis agent --to +15555550123 --message "Trace logs" --verbose on --json
clawdis agent --to +15555550123 --message "Summon reply" --deliver
clawdbot agent --to +15555550123 --message "status update"
clawdbot agent --session-id 1234 --message "Summarize inbox" --thinking medium
clawdbot agent --to +15555550123 --message "Trace logs" --verbose on --json
clawdbot agent --to +15555550123 --message "Summon reply" --deliver
`,
)
.action(async (opts) => {
@@ -420,10 +420,10 @@ Examples:
"after",
`
Examples:
clawdis status # show linked account + session store summary
clawdis status --json # machine-readable output
clawdis status --deep # run provider probes (WA + Telegram + Discord + Slack + Signal)
clawdis status --deep --timeout 5000 # tighten probe timeout`,
clawdbot status # show linked account + session store summary
clawdbot status --json # machine-readable output
clawdbot status --deep # run provider probes (WA + Telegram + Discord + Slack + Signal)
clawdbot status --deep --timeout 5000 # tighten probe timeout`,
)
.action(async (opts) => {
setVerbose(Boolean(opts.verbose));
@@ -501,10 +501,10 @@ Examples:
"after",
`
Examples:
clawdis sessions # list all sessions
clawdis sessions --active 120 # only last 2 hours
clawdis sessions --json # machine-readable output
clawdis sessions --store ./tmp/sessions.json
clawdbot sessions # list all sessions
clawdbot sessions --active 120 # only last 2 hours
clawdbot sessions --json # machine-readable output
clawdbot sessions --store ./tmp/sessions.json
Shows token usage per session when the agent reports it; set agent.contextTokens to see % of your model window.`,
)
+5 -5
View File
@@ -23,7 +23,7 @@ vi.mock("../agents/model-catalog.js", () => ({
import { loadModelCatalog } from "../agents/model-catalog.js";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import * as configModule from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import { agentCommand } from "./agent.js";
@@ -39,7 +39,7 @@ const runtime: RuntimeEnv = {
const configSpy = vi.spyOn(configModule, "loadConfig");
async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
const base = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-agent-"));
const base = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-agent-"));
const previousHome = process.env.HOME;
process.env.HOME = base;
try {
@@ -53,9 +53,9 @@ async function withTempHome<T>(fn: (home: string) => Promise<T>): Promise<T> {
function mockConfig(
home: string,
storePath: string,
routingOverrides?: Partial<NonNullable<ClawdisConfig["routing"]>>,
agentOverrides?: Partial<NonNullable<ClawdisConfig["agent"]>>,
telegramOverrides?: Partial<NonNullable<ClawdisConfig["telegram"]>>,
routingOverrides?: Partial<NonNullable<ClawdbotConfig["routing"]>>,
agentOverrides?: Partial<NonNullable<ClawdbotConfig["agent"]>>,
telegramOverrides?: Partial<NonNullable<ClawdbotConfig["telegram"]>>,
) {
configSpy.mockReturnValue({
agent: {
+3 -3
View File
@@ -27,7 +27,7 @@ import {
type VerboseLevel,
} from "../auto-reply/thinking.js";
import { type CliDeps, createDefaultDeps } from "../cli/deps.js";
import { type ClawdisConfig, loadConfig } from "../config/config.js";
import { type ClawdbotConfig, loadConfig } from "../config/config.js";
import {
DEFAULT_IDLE_MINUTES,
loadSessionStore,
@@ -77,7 +77,7 @@ type SessionResolution = {
};
function resolveSession(opts: {
cfg: ClawdisConfig;
cfg: ClawdbotConfig;
to?: string;
sessionId?: string;
}): SessionResolution {
@@ -540,7 +540,7 @@ export async function agentCommand(
}
if (deliveryProvider === "webchat") {
const err = new Error(
"Delivering to WebChat is not supported via `clawdis agent`; use WhatsApp/Telegram or run with --deliver=false.",
"Delivering to WebChat is not supported via `clawdbot agent`; use WhatsApp/Telegram or run with --deliver=false.",
);
if (!bestEffortDeliver) throw err;
logDeliveryError(err);
+14 -14
View File
@@ -11,9 +11,9 @@ import {
text,
} from "@clack/prompts";
import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDIS,
CONFIG_PATH_CLAWDBOT,
readConfigFileSnapshot,
resolveGatewayPort,
writeConfigFile,
@@ -66,10 +66,10 @@ type ConfigureWizardParams = {
};
async function promptGatewayConfig(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
runtime: RuntimeEnv,
): Promise<{
config: ClawdisConfig;
config: ClawdbotConfig;
port: number;
token?: string;
}> {
@@ -221,9 +221,9 @@ async function promptGatewayConfig(
}
async function promptAuthConfig(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
runtime: RuntimeEnv,
): Promise<ClawdisConfig> {
): Promise<ClawdbotConfig> {
const authChoice = guardCancel(
await select({
message: "Model/auth choice",
@@ -399,8 +399,8 @@ async function maybeInstallDaemon(params: {
await resolveGatewayProgramArguments({ port: params.port, dev: devMode });
const environment: Record<string, string | undefined> = {
PATH: process.env.PATH,
CLAWDIS_GATEWAY_TOKEN: params.gatewayToken,
CLAWDIS_LAUNCHD_LABEL:
CLAWDBOT_GATEWAY_TOKEN: params.gatewayToken,
CLAWDBOT_LAUNCHD_LABEL:
process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined,
};
await service.install({
@@ -418,12 +418,12 @@ export async function runConfigureWizard(
) {
printWizardHeader(runtime);
intro(
opts.command === "update" ? "Clawdis update wizard" : "Clawdis configure",
opts.command === "update" ? "Clawdbot update wizard" : "Clawdbot configure",
);
const prompter = createClackPrompter();
const snapshot = await readConfigFileSnapshot();
let baseConfig: ClawdisConfig = snapshot.valid ? snapshot.config : {};
let baseConfig: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
if (snapshot.exists) {
const title = snapshot.valid
@@ -453,10 +453,10 @@ export async function runConfigureWizard(
const localUrl = "ws://127.0.0.1:18789";
const localProbe = await probeGatewayReachable({
url: localUrl,
token: process.env.CLAWDIS_GATEWAY_TOKEN,
token: process.env.CLAWDBOT_GATEWAY_TOKEN,
password:
baseConfig.gateway?.auth?.password ??
process.env.CLAWDIS_GATEWAY_PASSWORD,
process.env.CLAWDBOT_GATEWAY_PASSWORD,
});
const remoteUrl = baseConfig.gateway?.remote?.url?.trim() ?? "";
const remoteProbe = remoteUrl
@@ -498,7 +498,7 @@ export async function runConfigureWizard(
mode,
});
await writeConfigFile(remoteConfig);
runtime.log(`Updated ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
outro("Remote gateway configured.");
return;
}
@@ -583,7 +583,7 @@ export async function runConfigureWizard(
mode,
});
await writeConfigFile(nextConfig);
runtime.log(`Updated ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
if (selected.includes("daemon")) {
if (!selected.includes("gateway")) {
+2 -2
View File
@@ -19,7 +19,7 @@ vi.mock("../agents/skills-status.js", () => ({
}));
vi.mock("../config/config.js", () => ({
CONFIG_PATH_CLAWDIS: "/tmp/clawdis.json",
CONFIG_PATH_CLAWDBOT: "/tmp/clawdbot.json",
readConfigFileSnapshot,
writeConfigFile,
migrateLegacyConfig,
@@ -54,7 +54,7 @@ vi.mock("./onboard-helpers.js", () => ({
describe("doctor", () => {
it("migrates routing.allowFrom to whatsapp.allowFrom", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdis.json",
path: "/tmp/clawdbot.json",
exists: true,
raw: "{}",
parsed: { routing: { allowFrom: ["+15555550123"] } },
+6 -6
View File
@@ -1,9 +1,9 @@
import { confirm, intro, note, outro } from "@clack/prompts";
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDIS,
CONFIG_PATH_CLAWDBOT,
migrateLegacyConfig,
readConfigFileSnapshot,
writeConfigFile,
@@ -20,16 +20,16 @@ import {
printWizardHeader,
} from "./onboard-helpers.js";
function resolveMode(cfg: ClawdisConfig): "local" | "remote" {
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
return cfg.gateway?.mode === "remote" ? "remote" : "local";
}
export async function doctorCommand(runtime: RuntimeEnv = defaultRuntime) {
printWizardHeader(runtime);
intro("Clawdis doctor");
intro("Clawdbot doctor");
const snapshot = await readConfigFileSnapshot();
let cfg: ClawdisConfig = snapshot.valid ? snapshot.config : {};
let cfg: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
if (
snapshot.exists &&
!snapshot.valid &&
@@ -130,7 +130,7 @@ export async function doctorCommand(runtime: RuntimeEnv = defaultRuntime) {
cfg = applyWizardMetadata(cfg, { command: "doctor", mode: resolveMode(cfg) });
await writeConfigFile(cfg);
runtime.log(`Updated ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
outro("Doctor complete.");
}
+1 -1
View File
@@ -104,7 +104,7 @@ describe("getHealthSnapshot", () => {
});
it("treats telegram.tokenFile as configured", async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdis-health-"));
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-health-"));
const tokenFile = path.join(tmpDir, "telegram-token");
fs.writeFileSync(tokenFile, "t-file\n", "utf-8");
testConfig = { telegram: { tokenFile } };
+1 -1
View File
@@ -125,7 +125,7 @@ export async function healthCommand(
runtime.log(
summary.web.linked
? `Web: linked (auth age ${summary.web.authAgeMs ? `${Math.round(summary.web.authAgeMs / 60000)}m` : "unknown"})`
: "Web: not linked (run clawdis login)",
: "Web: not linked (run clawdbot login)",
);
if (summary.web.linked) {
logWebSelfId(runtime, true);
+4 -4
View File
@@ -4,8 +4,8 @@ import path from "node:path";
import type { OAuthCredentials, OAuthProvider } from "@mariozechner/pi-ai";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
import { resolveClawdisAgentDir } from "../agents/agent-paths.js";
import type { ClawdisConfig } from "../config/config.js";
import { resolveClawdbotAgentDir } from "../agents/agent-paths.js";
import type { ClawdbotConfig } from "../config/config.js";
import { CONFIG_DIR } from "../utils.js";
export async function writeOAuthCredentials(
@@ -29,12 +29,12 @@ export async function writeOAuthCredentials(
}
export async function setAnthropicApiKey(key: string) {
const agentDir = resolveClawdisAgentDir();
const agentDir = resolveClawdbotAgentDir();
const authStorage = discoverAuthStorage(agentDir);
authStorage.set("anthropic", { type: "api_key", key });
}
export function applyMinimaxConfig(cfg: ClawdisConfig): ClawdisConfig {
export function applyMinimaxConfig(cfg: ClawdbotConfig): ClawdbotConfig {
const allowed = new Set(cfg.agent?.allowedModels ?? []);
allowed.add("anthropic/claude-opus-4-5");
allowed.add("lmstudio/minimax-m2.1-gs32");
+6 -6
View File
@@ -9,8 +9,8 @@ import {
DEFAULT_AGENT_WORKSPACE_DIR,
ensureAgentWorkspace,
} from "../agents/workspace.js";
import type { ClawdisConfig } from "../config/config.js";
import { CONFIG_PATH_CLAWDIS } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { CONFIG_PATH_CLAWDBOT } from "../config/config.js";
import { resolveSessionTranscriptsDir } from "../config/sessions.js";
import { callGateway } from "../gateway/call.js";
import { normalizeControlUiBasePath } from "../gateway/control-ui.js";
@@ -29,7 +29,7 @@ export function guardCancel<T>(value: T, runtime: RuntimeEnv): T {
return value;
}
export function summarizeExistingConfig(config: ClawdisConfig): string {
export function summarizeExistingConfig(config: ClawdbotConfig): string {
const rows: string[] = [];
if (config.agent?.workspace)
rows.push(`workspace: ${config.agent.workspace}`);
@@ -64,9 +64,9 @@ export function printWizardHeader(runtime: RuntimeEnv) {
}
export function applyWizardMetadata(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
params: { command: string; mode: "local" | "remote" },
): ClawdisConfig {
): ClawdbotConfig {
const commit =
process.env.GIT_COMMIT?.trim() || process.env.GIT_SHA?.trim() || undefined;
return {
@@ -145,7 +145,7 @@ export async function handleReset(
workspaceDir: string,
runtime: RuntimeEnv,
) {
await moveToTrash(CONFIG_PATH_CLAWDIS, runtime);
await moveToTrash(CONFIG_PATH_CLAWDBOT, runtime);
if (scope === "config") return;
await moveToTrash(path.join(CONFIG_DIR, "credentials"), runtime);
await moveToTrash(resolveSessionTranscriptsDir(), runtime);
+9 -9
View File
@@ -1,8 +1,8 @@
import path from "node:path";
import {
type ClawdisConfig,
CONFIG_PATH_CLAWDIS,
type ClawdbotConfig,
CONFIG_PATH_CLAWDBOT,
readConfigFileSnapshot,
resolveGatewayPort,
writeConfigFile,
@@ -32,7 +32,7 @@ export async function runNonInteractiveOnboarding(
runtime: RuntimeEnv = defaultRuntime,
) {
const snapshot = await readConfigFileSnapshot();
const baseConfig: ClawdisConfig = snapshot.valid ? snapshot.config : {};
const baseConfig: ClawdbotConfig = snapshot.valid ? snapshot.config : {};
const mode: OnboardMode = opts.mode ?? "local";
if (mode === "remote") {
@@ -43,7 +43,7 @@ export async function runNonInteractiveOnboarding(
return;
}
let nextConfig: ClawdisConfig = {
let nextConfig: ClawdbotConfig = {
...baseConfig,
gateway: {
...baseConfig.gateway,
@@ -56,7 +56,7 @@ export async function runNonInteractiveOnboarding(
};
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig);
runtime.log(`Updated ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
const payload = {
mode,
@@ -76,7 +76,7 @@ export async function runNonInteractiveOnboarding(
(opts.workspace ?? baseConfig.agent?.workspace ?? DEFAULT_WORKSPACE).trim(),
);
let nextConfig: ClawdisConfig = {
let nextConfig: ClawdbotConfig = {
...baseConfig,
agent: {
...baseConfig.agent,
@@ -200,7 +200,7 @@ export async function runNonInteractiveOnboarding(
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig);
runtime.log(`Updated ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Updated ${CONFIG_PATH_CLAWDBOT}`);
await ensureWorkspaceAndSessions(workspaceDir, runtime);
if (opts.installDaemon) {
@@ -212,8 +212,8 @@ export async function runNonInteractiveOnboarding(
await resolveGatewayProgramArguments({ port, dev: devMode });
const environment: Record<string, string | undefined> = {
PATH: process.env.PATH,
CLAWDIS_GATEWAY_TOKEN: gatewayToken,
CLAWDIS_LAUNCHD_LABEL:
CLAWDBOT_GATEWAY_TOKEN: gatewayToken,
CLAWDBOT_LAUNCHD_LABEL:
process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined,
};
await service.install({
+15 -15
View File
@@ -1,6 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import { loginWeb } from "../provider-web.js";
import type { RuntimeEnv } from "../runtime.js";
import { normalizeE164 } from "../utils.js";
@@ -64,11 +64,11 @@ async function noteDiscordTokenHelp(prompter: WizardPrompter): Promise<void> {
}
function buildSlackManifest(botName: string) {
const safeName = botName.trim() || "Clawdis";
const safeName = botName.trim() || "Clawdbot";
const manifest = {
display_information: {
name: safeName,
description: `${safeName} connector for Clawdis`,
description: `${safeName} connector for Clawdbot`,
},
features: {
bot_user: {
@@ -82,7 +82,7 @@ function buildSlackManifest(botName: string) {
slash_commands: [
{
command: "/clawd",
description: "Send a message to Clawdis",
description: "Send a message to Clawdbot",
should_escape: false,
},
],
@@ -153,7 +153,7 @@ async function noteSlackTokenHelp(
);
}
function setWhatsAppAllowFrom(cfg: ClawdisConfig, allowFrom?: string[]) {
function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]) {
return {
...cfg,
whatsapp: {
@@ -164,10 +164,10 @@ function setWhatsAppAllowFrom(cfg: ClawdisConfig, allowFrom?: string[]) {
}
async function promptWhatsAppAllowFrom(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
_runtime: RuntimeEnv,
prompter: WizardPrompter,
): Promise<ClawdisConfig> {
): Promise<ClawdbotConfig> {
const existingAllowFrom = cfg.whatsapp?.allowFrom ?? [];
const existingLabel =
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
@@ -236,11 +236,11 @@ async function promptWhatsAppAllowFrom(
}
export async function setupProviders(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
runtime: RuntimeEnv,
prompter: WizardPrompter,
options?: { allowDisable?: boolean; allowSignalInstall?: boolean },
): Promise<ClawdisConfig> {
): Promise<ClawdbotConfig> {
const whatsappLinked = await detectWhatsAppLinked();
const telegramEnv = Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
const discordEnv = Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
@@ -330,7 +330,7 @@ export async function setupProviders(
await prompter.note(
[
"Scan the QR with WhatsApp on your phone.",
"Credentials are stored under ~/.clawdis/credentials/ for future runs.",
"Credentials are stored under ~/.clawdbot/credentials/ for future runs.",
].join("\n"),
"WhatsApp linking",
);
@@ -349,7 +349,7 @@ export async function setupProviders(
}
} else if (!whatsappLinked) {
await prompter.note(
"Run `clawdis login` later to link WhatsApp.",
"Run `clawdbot login` later to link WhatsApp.",
"WhatsApp",
);
}
@@ -483,7 +483,7 @@ export async function setupProviders(
const slackBotName = String(
await prompter.text({
message: "Slack bot display name (used for manifest)",
initialValue: "Clawdis",
initialValue: "Clawdbot",
}),
).trim();
if (!slackConfigured) {
@@ -641,9 +641,9 @@ export async function setupProviders(
await prompter.note(
[
'Link device with: signal-cli link -n "Clawdis"',
'Link device with: signal-cli link -n "Clawdbot"',
"Scan QR in Signal → Linked Devices",
"Then run: clawdis gateway call providers.status --params '{\"probe\":true}'",
"Then run: clawdbot gateway call providers.status --params '{\"probe\":true}'",
].join("\n"),
"Signal next steps",
);
@@ -679,7 +679,7 @@ export async function setupProviders(
await prompter.note(
[
"Ensure Clawdis has Full Disk Access to Messages DB.",
"Ensure Clawdbot has Full Disk Access to Messages DB.",
"Grant Automation permission for Messages when prompted.",
"List chats with: imsg chats --limit 20",
].join("\n"),
+3 -3
View File
@@ -1,4 +1,4 @@
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { GatewayBonjourBeacon } from "../infra/bonjour-discovery.js";
import { discoverGatewayBeacons } from "../infra/bonjour-discovery.js";
import type { WizardPrompter } from "../wizard/prompts.js";
@@ -25,9 +25,9 @@ function ensureWsUrl(value: string): string {
}
export async function promptRemoteGatewayConfig(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
prompter: WizardPrompter,
): Promise<ClawdisConfig> {
): Promise<ClawdbotConfig> {
let selectedBeacon: GatewayBonjourBeacon | null = null;
let suggestedUrl = cfg.gateway?.remote?.url ?? DEFAULT_GATEWAY_URL;
+7 -7
View File
@@ -1,6 +1,6 @@
import { installSkill } from "../agents/skills-install.js";
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
import type { ClawdisConfig } from "../config/config.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import type { WizardPrompter } from "../wizard/prompts.js";
import { detectBinary, resolveNodeManagerOptions } from "./onboard-helpers.js";
@@ -30,10 +30,10 @@ function formatSkillHint(skill: {
}
function upsertSkillEntry(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
skillKey: string,
patch: { apiKey?: string },
): ClawdisConfig {
): ClawdbotConfig {
const entries = { ...cfg.skills?.entries };
const existing = (entries[skillKey] as { apiKey?: string } | undefined) ?? {};
entries[skillKey] = { ...existing, ...patch };
@@ -47,11 +47,11 @@ function upsertSkillEntry(
}
export async function setupSkills(
cfg: ClawdisConfig,
cfg: ClawdbotConfig,
workspaceDir: string,
runtime: RuntimeEnv,
prompter: WizardPrompter,
): Promise<ClawdisConfig> {
): Promise<ClawdbotConfig> {
const report = buildWorkspaceSkillStatus(workspaceDir, { config: cfg });
const eligible = report.skills.filter((s) => s.eligible);
const missing = report.skills.filter(
@@ -109,7 +109,7 @@ export async function setupSkills(
options: resolveNodeManagerOptions(),
})) as "npm" | "pnpm" | "bun";
let next: ClawdisConfig = {
let next: ClawdbotConfig = {
...cfg,
skills: {
...cfg.skills,
@@ -166,7 +166,7 @@ export async function setupSkills(
if (result.stderr) runtime.log(result.stderr.trim());
else if (result.stdout) runtime.log(result.stdout.trim());
runtime.log(
"Tip: run `clawdis doctor` to review skills + requirements.",
"Tip: run `clawdbot doctor` to review skills + requirements.",
);
}
}
+11 -11
View File
@@ -7,20 +7,20 @@ import {
DEFAULT_AGENT_WORKSPACE_DIR,
ensureAgentWorkspace,
} from "../agents/workspace.js";
import { type ClawdisConfig, CONFIG_PATH_CLAWDIS } from "../config/config.js";
import { type ClawdbotConfig, CONFIG_PATH_CLAWDBOT } from "../config/config.js";
import { resolveSessionTranscriptsDir } from "../config/sessions.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
async function readConfigFileRaw(): Promise<{
exists: boolean;
parsed: ClawdisConfig;
parsed: ClawdbotConfig;
}> {
try {
const raw = await fs.readFile(CONFIG_PATH_CLAWDIS, "utf-8");
const raw = await fs.readFile(CONFIG_PATH_CLAWDBOT, "utf-8");
const parsed = JSON5.parse(raw);
if (parsed && typeof parsed === "object") {
return { exists: true, parsed: parsed as ClawdisConfig };
return { exists: true, parsed: parsed as ClawdbotConfig };
}
return { exists: true, parsed: {} };
} catch {
@@ -28,10 +28,10 @@ async function readConfigFileRaw(): Promise<{
}
}
async function writeConfigFile(cfg: ClawdisConfig) {
await fs.mkdir(path.dirname(CONFIG_PATH_CLAWDIS), { recursive: true });
async function writeConfigFile(cfg: ClawdbotConfig) {
await fs.mkdir(path.dirname(CONFIG_PATH_CLAWDBOT), { recursive: true });
const json = JSON.stringify(cfg, null, 2).trimEnd().concat("\n");
await fs.writeFile(CONFIG_PATH_CLAWDIS, json, "utf-8");
await fs.writeFile(CONFIG_PATH_CLAWDBOT, json, "utf-8");
}
export async function setupCommand(
@@ -50,7 +50,7 @@ export async function setupCommand(
const workspace =
desiredWorkspace ?? agent.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR;
const next: ClawdisConfig = {
const next: ClawdbotConfig = {
...cfg,
agent: {
...agent,
@@ -62,11 +62,11 @@ export async function setupCommand(
await writeConfigFile(next);
runtime.log(
!existingRaw.exists
? `Wrote ${CONFIG_PATH_CLAWDIS}`
: `Updated ${CONFIG_PATH_CLAWDIS} (set agent.workspace)`,
? `Wrote ${CONFIG_PATH_CLAWDBOT}`
: `Updated ${CONFIG_PATH_CLAWDBOT} (set agent.workspace)`,
);
} else {
runtime.log(`Config OK: ${CONFIG_PATH_CLAWDIS}`);
runtime.log(`Config OK: ${CONFIG_PATH_CLAWDBOT}`);
}
const ws = await ensureAgentWorkspace({
+2 -2
View File
@@ -132,7 +132,7 @@ export async function installSignalCli(
"https://api.github.com/repos/AsamK/signal-cli/releases/latest";
const response = await fetch(apiUrl, {
headers: {
"User-Agent": "clawdis",
"User-Agent": "clawdbot",
Accept: "application/vnd.github+json",
},
});
@@ -158,7 +158,7 @@ export async function installSignalCli(
};
}
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-signal-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-signal-"));
const archivePath = path.join(tmpDir, assetName);
runtime.log(`Downloading signal-cli ${version} (${assetName})…`);
+56 -56
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,
+1 -1
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";
+7 -7
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();
+8 -8
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;
+2 -2
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);
+3 -3
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).',
},
];
+15 -15
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;
+6 -6
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,
+1 -1
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,
+1 -1
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 {
+29 -5
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[];
};
+5 -5
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),
),
};
}
+22 -1
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

Some files were not shown because too many files have changed in this diff Show More