refactor: rename clawdbot to moltbot with legacy compat

This commit is contained in:
Peter Steinberger
2026-01-27 12:19:58 +00:00
parent 83460df96f
commit 6d16a658e5
1839 changed files with 11250 additions and 11199 deletions

View File

@@ -16,7 +16,7 @@ export function resolveBundledHooksDir(): string | undefined {
}
// npm: resolve `<packageRoot>/dist/hooks/bundled` relative to this module (compiled hooks).
// This path works when installed via npm: node_modules/clawdbot/dist/hooks/bundled-dir.js
// This path works when installed via npm: node_modules/moltbot/dist/hooks/bundled-dir.js
try {
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
const distBundled = path.join(moduleDir, "bundled");

View File

@@ -4,12 +4,12 @@ description: "Run BOOT.md on gateway startup"
homepage: https://docs.molt.bot/hooks#boot-md
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "🚀",
"events": ["gateway:startup"],
"requires": { "config": ["workspace.dir"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Clawdbot" }],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
},
}
---

View File

@@ -1,11 +1,11 @@
import type { CliDeps } from "../../../cli/deps.js";
import { createDefaultDeps } from "../../../cli/deps.js";
import type { ClawdbotConfig } from "../../../config/config.js";
import type { MoltbotConfig } from "../../../config/config.js";
import { runBootOnce } from "../../../gateway/boot.js";
import type { HookHandler } from "../../hooks.js";
type BootHookContext = {
cfg?: ClawdbotConfig;
cfg?: MoltbotConfig;
workspaceDir?: string;
deps?: CliDeps;
};

View File

@@ -4,11 +4,11 @@ description: "Log all command events to a centralized audit file"
homepage: https://docs.molt.bot/hooks#command-logger
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "📝",
"events": ["command"],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Clawdbot" }],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
},
}
---
@@ -62,7 +62,7 @@ No configuration needed. The hook automatically:
To disable this hook:
```bash
clawdbot hooks disable command-logger
moltbot hooks disable command-logger
```
Or via config:
@@ -90,7 +90,7 @@ The hook does not automatically rotate logs. To manage log size, you can:
```
2. **Use logrotate** (Linux):
Create `/etc/logrotate.d/clawdbot`:
Create `/etc/logrotate.d/moltbot`:
```
/home/username/.clawdbot/logs/commands.log {
weekly

View File

@@ -4,12 +4,12 @@ description: "Save session context to memory when /new command is issued"
homepage: https://docs.molt.bot/hooks#session-memory
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "💾",
"events": ["command:new"],
"requires": { "config": ["workspace.dir"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Clawdbot" }],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
},
}
---
@@ -68,7 +68,7 @@ No additional configuration required. The hook automatically:
To disable this hook:
```bash
clawdbot hooks disable session-memory
moltbot hooks disable session-memory
```
Or remove it from your config:

View File

@@ -8,7 +8,7 @@
import fs from "node:fs/promises";
import path from "node:path";
import os from "node:os";
import type { ClawdbotConfig } from "../../../config/config.js";
import type { MoltbotConfig } from "../../../config/config.js";
import { resolveAgentWorkspaceDir } from "../../../agents/agent-scope.js";
import { resolveAgentIdFromSessionKey } from "../../../routing/session-key.js";
import type { HookHandler } from "../../hooks.js";
@@ -67,7 +67,7 @@ const saveSessionToMemory: HookHandler = async (event) => {
console.log("[session-memory] Hook triggered for /new command");
const context = event.context || {};
const cfg = context.cfg as ClawdbotConfig | undefined;
const cfg = context.cfg as MoltbotConfig | undefined;
const agentId = resolveAgentIdFromSessionKey(event.sessionKey);
const workspaceDir = cfg
? resolveAgentWorkspaceDir(cfg, agentId)
@@ -106,11 +106,11 @@ const saveSessionToMemory: HookHandler = async (event) => {
// Dynamically import the LLM slug generator (avoids module caching issues)
// When compiled, handler is at dist/hooks/bundled/session-memory/handler.js
// Going up ../.. puts us at dist/hooks/, so just add llm-slug-generator.js
const clawdbotRoot = path.resolve(
const moltbotRoot = path.resolve(
path.dirname(import.meta.url.replace("file://", "")),
"../..",
);
const slugGenPath = path.join(clawdbotRoot, "llm-slug-generator.js");
const slugGenPath = path.join(moltbotRoot, "llm-slug-generator.js");
const { generateSlugViaLLM } = await import(slugGenPath);
// Use LLM to generate a descriptive slug

View File

@@ -4,12 +4,12 @@ description: "Swap SOUL.md with SOUL_EVIL.md during a purge window or by random
homepage: https://docs.molt.bot/hooks/soul-evil
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "😈",
"events": ["agent:bootstrap"],
"requires": { "config": ["hooks.internal.entries.soul-evil.enabled"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Clawdbot" }],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
},
}
---
@@ -31,7 +31,7 @@ You can change the filename via hook config.
## Configuration
Add this to your config (`~/.clawdbot/clawdbot.json`):
Add this to your config (`~/.clawdbot/moltbot.json`):
```json
{
@@ -67,5 +67,5 @@ Add this to your config (`~/.clawdbot/clawdbot.json`):
## Enable
```bash
clawdbot hooks enable soul-evil
moltbot hooks enable soul-evil
```

View File

@@ -5,19 +5,19 @@ import { describe, expect, it } from "vitest";
import handler from "./handler.js";
import { createHookEvent } from "../../hooks.js";
import type { AgentBootstrapHookContext } from "../../hooks.js";
import type { ClawdbotConfig } from "../../../config/config.js";
import type { MoltbotConfig } from "../../../config/config.js";
import { makeTempWorkspace, writeWorkspaceFile } from "../../../test-helpers/workspace.js";
describe("soul-evil hook", () => {
it("skips subagent sessions", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
await writeWorkspaceFile({
dir: tempDir,
name: "SOUL_EVIL.md",
content: "chaotic",
});
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
entries: {

View File

@@ -1,4 +1,4 @@
import type { ClawdbotConfig } from "../../../config/config.js";
import type { MoltbotConfig } from "../../../config/config.js";
import { isSubagentSessionKey } from "../../../routing/session-key.js";
import { resolveHookConfig } from "../../config.js";
import { isAgentBootstrapEvent, type HookHandler } from "../../hooks.js";
@@ -11,7 +11,7 @@ const soulEvilHook: HookHandler = async (event) => {
const context = event.context;
if (context.sessionKey && isSubagentSessionKey(context.sessionKey)) return;
const cfg = context.cfg as ClawdbotConfig | undefined;
const cfg = context.cfg as MoltbotConfig | undefined;
const hookConfig = resolveHookConfig(cfg, HOOK_KEY);
if (!hookConfig || hookConfig.enabled === false) return;

View File

@@ -1,6 +1,6 @@
import fs from "node:fs";
import path from "node:path";
import type { ClawdbotConfig, HookConfig } from "../config/config.js";
import type { MoltbotConfig, HookConfig } from "../config/config.js";
import { resolveHookKey } from "./frontmatter.js";
import type { HookEligibilityContext, HookEntry } from "./types.js";
@@ -18,7 +18,7 @@ function isTruthy(value: unknown): boolean {
return true;
}
export function resolveConfigPath(config: ClawdbotConfig | undefined, pathStr: string) {
export function resolveConfigPath(config: MoltbotConfig | undefined, pathStr: string) {
const parts = pathStr.split(".").filter(Boolean);
let current: unknown = config;
for (const part of parts) {
@@ -28,7 +28,7 @@ export function resolveConfigPath(config: ClawdbotConfig | undefined, pathStr: s
return current;
}
export function isConfigPathTruthy(config: ClawdbotConfig | undefined, pathStr: string): boolean {
export function isConfigPathTruthy(config: MoltbotConfig | undefined, pathStr: string): boolean {
const value = resolveConfigPath(config, pathStr);
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
return DEFAULT_CONFIG_VALUES[pathStr] === true;
@@ -37,7 +37,7 @@ export function isConfigPathTruthy(config: ClawdbotConfig | undefined, pathStr:
}
export function resolveHookConfig(
config: ClawdbotConfig | undefined,
config: MoltbotConfig | undefined,
hookKey: string,
): HookConfig | undefined {
const hooks = config?.hooks?.internal?.entries;
@@ -68,14 +68,14 @@ export function hasBinary(bin: string): boolean {
export function shouldIncludeHook(params: {
entry: HookEntry;
config?: ClawdbotConfig;
config?: MoltbotConfig;
eligibility?: HookEligibilityContext;
}): boolean {
const { entry, config, eligibility } = params;
const hookKey = resolveHookKey(entry.hook.name, entry);
const hookConfig = resolveHookConfig(config, hookKey);
const pluginManaged = entry.hook.source === "clawdbot-plugin";
const osList = entry.clawdbot?.os ?? [];
const pluginManaged = entry.hook.source === "moltbot-plugin";
const osList = entry.metadata?.os ?? [];
const remotePlatforms = eligibility?.remote?.platforms ?? [];
// Check if explicitly disabled
@@ -91,12 +91,12 @@ export function shouldIncludeHook(params: {
}
// If marked as 'always', bypass all other checks
if (entry.clawdbot?.always === true) {
if (entry.metadata?.always === true) {
return true;
}
// Check required binaries (all must be present)
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
const requiredBins = entry.metadata?.requires?.bins ?? [];
if (requiredBins.length > 0) {
for (const bin of requiredBins) {
if (hasBinary(bin)) continue;
@@ -106,7 +106,7 @@ export function shouldIncludeHook(params: {
}
// Check anyBins (at least one must be present)
const requiredAnyBins = entry.clawdbot?.requires?.anyBins ?? [];
const requiredAnyBins = entry.metadata?.requires?.anyBins ?? [];
if (requiredAnyBins.length > 0) {
const anyFound =
requiredAnyBins.some((bin) => hasBinary(bin)) ||
@@ -115,7 +115,7 @@ export function shouldIncludeHook(params: {
}
// Check required environment variables
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
const requiredEnv = entry.metadata?.requires?.env ?? [];
if (requiredEnv.length > 0) {
for (const envName of requiredEnv) {
if (process.env[envName]) continue;
@@ -125,7 +125,7 @@ export function shouldIncludeHook(params: {
}
// Check required config paths
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
const requiredConfig = entry.metadata?.requires?.config ?? [];
if (requiredConfig.length > 0) {
for (const configPath of requiredConfig) {
if (!isConfigPathTruthy(config, configPath)) return false;

View File

@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import {
parseFrontmatter,
resolveClawdbotMetadata,
resolveMoltbotMetadata,
resolveHookInvocationPolicy,
} from "./frontmatter.js";
@@ -41,7 +41,7 @@ name: session-memory
description: "Save session context"
metadata:
{
"clawdbot": {
"moltbot": {
"emoji": "💾",
"events": ["command:new"]
}
@@ -58,8 +58,8 @@ metadata:
// Verify the metadata is valid JSON
const parsed = JSON.parse(result.metadata as string);
expect(parsed.clawdbot.emoji).toBe("💾");
expect(parsed.clawdbot.events).toEqual(["command:new"]);
expect(parsed.moltbot.emoji).toBe("💾");
expect(parsed.moltbot.events).toEqual(["command:new"]);
});
it("parses multi-line metadata with complex nested structure", () => {
@@ -68,7 +68,7 @@ name: command-logger
description: "Log all command events"
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "📝",
"events": ["command"],
@@ -83,21 +83,21 @@ metadata:
expect(result.metadata).toBeDefined();
const parsed = JSON.parse(result.metadata as string);
expect(parsed.clawdbot.emoji).toBe("📝");
expect(parsed.clawdbot.events).toEqual(["command"]);
expect(parsed.clawdbot.requires.config).toEqual(["workspace.dir"]);
expect(parsed.clawdbot.install[0].kind).toBe("bundled");
expect(parsed.moltbot.emoji).toBe("📝");
expect(parsed.moltbot.events).toEqual(["command"]);
expect(parsed.moltbot.requires.config).toEqual(["workspace.dir"]);
expect(parsed.moltbot.install[0].kind).toBe("bundled");
});
it("handles single-line metadata (inline JSON)", () => {
const content = `---
name: simple-hook
metadata: {"clawdbot": {"events": ["test"]}}
metadata: {"moltbot": {"events": ["test"]}}
---
`;
const result = parseFrontmatter(content);
expect(result.name).toBe("simple-hook");
expect(result.metadata).toBe('{"clawdbot": {"events": ["test"]}}');
expect(result.metadata).toBe('{"moltbot": {"events": ["test"]}}');
});
it("handles mixed single-line and multi-line values", () => {
@@ -107,7 +107,7 @@ description: "A hook with mixed values"
homepage: https://example.com
metadata:
{
"clawdbot": {
"moltbot": {
"events": ["command:new"]
}
}
@@ -148,12 +148,12 @@ description: 'single-quoted'
});
});
describe("resolveClawdbotMetadata", () => {
it("extracts clawdbot metadata from parsed frontmatter", () => {
describe("resolveMoltbotMetadata", () => {
it("extracts moltbot metadata from parsed frontmatter", () => {
const frontmatter = {
name: "test-hook",
metadata: JSON.stringify({
clawdbot: {
moltbot: {
emoji: "🔥",
events: ["command:new", "command:reset"],
requires: {
@@ -164,7 +164,7 @@ describe("resolveClawdbotMetadata", () => {
}),
};
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result).toBeDefined();
expect(result?.emoji).toBe("🔥");
expect(result?.events).toEqual(["command:new", "command:reset"]);
@@ -174,15 +174,15 @@ describe("resolveClawdbotMetadata", () => {
it("returns undefined when metadata is missing", () => {
const frontmatter = { name: "no-metadata" };
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result).toBeUndefined();
});
it("returns undefined when clawdbot key is missing", () => {
it("returns undefined when moltbot key is missing", () => {
const frontmatter = {
metadata: JSON.stringify({ other: "data" }),
};
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result).toBeUndefined();
});
@@ -190,41 +190,41 @@ describe("resolveClawdbotMetadata", () => {
const frontmatter = {
metadata: "not valid json {",
};
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result).toBeUndefined();
});
it("handles install specs", () => {
const frontmatter = {
metadata: JSON.stringify({
clawdbot: {
moltbot: {
events: ["command"],
install: [
{ id: "bundled", kind: "bundled", label: "Bundled with Clawdbot" },
{ id: "npm", kind: "npm", package: "@clawdbot/hook" },
{ id: "bundled", kind: "bundled", label: "Bundled with Moltbot" },
{ id: "npm", kind: "npm", package: "@moltbot/hook" },
],
},
}),
};
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result?.install).toHaveLength(2);
expect(result?.install?.[0].kind).toBe("bundled");
expect(result?.install?.[1].kind).toBe("npm");
expect(result?.install?.[1].package).toBe("@clawdbot/hook");
expect(result?.install?.[1].package).toBe("@moltbot/hook");
});
it("handles os restrictions", () => {
const frontmatter = {
metadata: JSON.stringify({
clawdbot: {
moltbot: {
events: ["command"],
os: ["darwin", "linux"],
},
}),
};
const result = resolveClawdbotMetadata(frontmatter);
const result = resolveMoltbotMetadata(frontmatter);
expect(result?.os).toEqual(["darwin", "linux"]);
});
@@ -236,12 +236,12 @@ description: "Save session context to memory when /new command is issued"
homepage: https://docs.molt.bot/hooks#session-memory
metadata:
{
"clawdbot":
"moltbot":
{
"emoji": "💾",
"events": ["command:new"],
"requires": { "config": ["workspace.dir"] },
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Clawdbot" }],
"install": [{ "id": "bundled", "kind": "bundled", "label": "Bundled with Moltbot" }],
},
}
---
@@ -253,28 +253,28 @@ metadata:
expect(frontmatter.name).toBe("session-memory");
expect(frontmatter.metadata).toBeDefined();
const clawdbot = resolveClawdbotMetadata(frontmatter);
expect(clawdbot).toBeDefined();
expect(clawdbot?.emoji).toBe("💾");
expect(clawdbot?.events).toEqual(["command:new"]);
expect(clawdbot?.requires?.config).toEqual(["workspace.dir"]);
expect(clawdbot?.install?.[0].kind).toBe("bundled");
const moltbot = resolveMoltbotMetadata(frontmatter);
expect(moltbot).toBeDefined();
expect(moltbot?.emoji).toBe("💾");
expect(moltbot?.events).toEqual(["command:new"]);
expect(moltbot?.requires?.config).toEqual(["workspace.dir"]);
expect(moltbot?.install?.[0].kind).toBe("bundled");
});
it("parses YAML metadata map", () => {
const content = `---
name: yaml-metadata
metadata:
clawdbot:
moltbot:
emoji: disk
events:
- command:new
---
`;
const frontmatter = parseFrontmatter(content);
const clawdbot = resolveClawdbotMetadata(frontmatter);
expect(clawdbot?.emoji).toBe("disk");
expect(clawdbot?.events).toEqual(["command:new"]);
const moltbot = resolveMoltbotMetadata(frontmatter);
expect(moltbot?.emoji).toBe("disk");
expect(moltbot?.events).toEqual(["command:new"]);
});
});

View File

@@ -1,9 +1,10 @@
import JSON5 from "json5";
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
import { parseFrontmatterBlock } from "../markdown/frontmatter.js";
import { parseBooleanValue } from "../utils/boolean.js";
import type {
ClawdbotHookMetadata,
MoltbotHookMetadata,
HookEntry,
HookInstallSpec,
HookInvocationPolicy,
@@ -62,33 +63,35 @@ function parseFrontmatterBool(value: string | undefined, fallback: boolean): boo
return parsed === undefined ? fallback : parsed;
}
export function resolveClawdbotMetadata(
export function resolveMoltbotMetadata(
frontmatter: ParsedHookFrontmatter,
): ClawdbotHookMetadata | undefined {
): MoltbotHookMetadata | undefined {
const raw = getFrontmatterValue(frontmatter, "metadata");
if (!raw) return undefined;
try {
const parsed = JSON5.parse(raw) as { clawdbot?: unknown };
const parsed = JSON5.parse(raw) as { moltbot?: unknown } & Partial<
Record<typeof LEGACY_MANIFEST_KEY, unknown>
>;
if (!parsed || typeof parsed !== "object") return undefined;
const clawdbot = (parsed as { clawdbot?: unknown }).clawdbot;
if (!clawdbot || typeof clawdbot !== "object") return undefined;
const clawdbotObj = clawdbot as Record<string, unknown>;
const metadataRaw = parsed.moltbot ?? parsed[LEGACY_MANIFEST_KEY];
if (!metadataRaw || typeof metadataRaw !== "object") return undefined;
const metadataObj = metadataRaw as Record<string, unknown>;
const requiresRaw =
typeof clawdbotObj.requires === "object" && clawdbotObj.requires !== null
? (clawdbotObj.requires as Record<string, unknown>)
typeof metadataObj.requires === "object" && metadataObj.requires !== null
? (metadataObj.requires as Record<string, unknown>)
: undefined;
const installRaw = Array.isArray(clawdbotObj.install) ? (clawdbotObj.install as unknown[]) : [];
const installRaw = Array.isArray(metadataObj.install) ? (metadataObj.install as unknown[]) : [];
const install = installRaw
.map((entry) => parseInstallSpec(entry))
.filter((entry): entry is HookInstallSpec => Boolean(entry));
const osRaw = normalizeStringList(clawdbotObj.os);
const eventsRaw = normalizeStringList(clawdbotObj.events);
const osRaw = normalizeStringList(metadataObj.os);
const eventsRaw = normalizeStringList(metadataObj.events);
return {
always: typeof clawdbotObj.always === "boolean" ? clawdbotObj.always : undefined,
emoji: typeof clawdbotObj.emoji === "string" ? clawdbotObj.emoji : undefined,
homepage: typeof clawdbotObj.homepage === "string" ? clawdbotObj.homepage : undefined,
hookKey: typeof clawdbotObj.hookKey === "string" ? clawdbotObj.hookKey : undefined,
export: typeof clawdbotObj.export === "string" ? clawdbotObj.export : undefined,
always: typeof metadataObj.always === "boolean" ? metadataObj.always : undefined,
emoji: typeof metadataObj.emoji === "string" ? metadataObj.emoji : undefined,
homepage: typeof metadataObj.homepage === "string" ? metadataObj.homepage : undefined,
hookKey: typeof metadataObj.hookKey === "string" ? metadataObj.hookKey : undefined,
export: typeof metadataObj.export === "string" ? metadataObj.export : undefined,
os: osRaw.length > 0 ? osRaw : undefined,
events: eventsRaw.length > 0 ? eventsRaw : [],
requires: requiresRaw
@@ -115,5 +118,5 @@ export function resolveHookInvocationPolicy(
}
export function resolveHookKey(hookName: string, entry?: HookEntry): string {
return entry?.clawdbot?.hookKey ?? hookName;
return entry?.metadata?.hookKey ?? hookName;
}

View File

@@ -1,8 +1,8 @@
import { spawn } from "node:child_process";
import {
type ClawdbotConfig,
CONFIG_PATH_CLAWDBOT,
type MoltbotConfig,
CONFIG_PATH,
loadConfig,
readConfigFileSnapshot,
resolveGatewayPort,
@@ -99,7 +99,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
const configSnapshot = await readConfigFileSnapshot();
if (!configSnapshot.valid) {
throw new Error(`Config invalid: ${CONFIG_PATH_CLAWDBOT}`);
throw new Error(`Config invalid: ${CONFIG_PATH}`);
}
const baseConfig = configSnapshot.config;
@@ -210,7 +210,7 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
true,
);
const nextConfig: ClawdbotConfig = {
const nextConfig: MoltbotConfig = {
...baseConfig,
hooks: {
...baseConfig.hooks,
@@ -277,8 +277,8 @@ export async function runGmailSetup(opts: GmailSetupOptions) {
defaultRuntime.log(`- subscription: ${subscription}`);
defaultRuntime.log(`- push endpoint: ${pushEndpoint}`);
defaultRuntime.log(`- hook url: ${hookUrl}`);
defaultRuntime.log(`- config: ${displayPath(CONFIG_PATH_CLAWDBOT)}`);
defaultRuntime.log(`Next: ${formatCliCommand("clawdbot webhooks gmail run")}`);
defaultRuntime.log(`- config: ${displayPath(CONFIG_PATH)}`);
defaultRuntime.log(`Next: ${formatCliCommand("moltbot webhooks gmail run")}`);
}
export async function runGmailService(opts: GmailRunOptions) {

View File

@@ -14,7 +14,7 @@ describe("resolvePythonExecutablePath", () => {
itUnix(
"resolves a working python path and caches the result",
async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-python-"));
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-python-"));
const originalPath = process.env.PATH;
try {
const realPython = path.join(tmp, "python-real");

View File

@@ -7,7 +7,7 @@
import { type ChildProcess, spawn } from "node:child_process";
import { hasBinary } from "../agents/skills.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { runCommandWithTimeout } from "../process/exec.js";
import {
@@ -121,7 +121,7 @@ export type GmailWatcherStartResult = {
* Start the Gmail watcher service.
* Called automatically by the gateway if hooks.gmail is configured.
*/
export async function startGmailWatcher(cfg: ClawdbotConfig): Promise<GmailWatcherStartResult> {
export async function startGmailWatcher(cfg: MoltbotConfig): Promise<GmailWatcherStartResult> {
// Check if gmail hooks are configured
if (!cfg.hooks?.enabled) {
return { started: false, reason: "hooks not enabled" };

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { type ClawdbotConfig, DEFAULT_GATEWAY_PORT } from "../config/config.js";
import { type MoltbotConfig, DEFAULT_GATEWAY_PORT } from "../config/config.js";
import {
buildDefaultHookUrl,
buildTopicPath,
@@ -11,12 +11,12 @@ const baseConfig = {
hooks: {
token: "hook-token",
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
},
},
} satisfies ClawdbotConfig;
} satisfies MoltbotConfig;
describe("gmail hook config", () => {
it("builds default hook url", () => {
@@ -37,7 +37,7 @@ describe("gmail hook config", () => {
const result = resolveGmailHookRuntimeConfig(baseConfig, {});
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.value.account).toBe("clawdbot@gmail.com");
expect(result.value.account).toBe("moltbot@gmail.com");
expect(result.value.label).toBe("INBOX");
expect(result.value.includeBody).toBe(true);
expect(result.value.serve.port).toBe(8788);
@@ -50,7 +50,7 @@ describe("gmail hook config", () => {
{
hooks: {
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
},
@@ -67,7 +67,7 @@ describe("gmail hook config", () => {
hooks: {
token: "hook-token",
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
tailscale: { mode: "funnel" },
@@ -89,7 +89,7 @@ describe("gmail hook config", () => {
hooks: {
token: "hook-token",
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
serve: { path: "/gmail-pubsub" },
@@ -112,7 +112,7 @@ describe("gmail hook config", () => {
hooks: {
token: "hook-token",
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
serve: { path: "/custom" },
@@ -135,7 +135,7 @@ describe("gmail hook config", () => {
hooks: {
token: "hook-token",
gmail: {
account: "clawdbot@gmail.com",
account: "moltbot@gmail.com",
topic: "projects/demo/topics/gog-gmail-watch",
pushToken: "push-token",
serve: { path: "/custom" },

View File

@@ -1,7 +1,7 @@
import { randomBytes } from "node:crypto";
import {
type ClawdbotConfig,
type MoltbotConfig,
DEFAULT_GATEWAY_PORT,
type HooksGmailTailscaleMode,
resolveGatewayPort,
@@ -95,7 +95,7 @@ export function buildDefaultHookUrl(
}
export function resolveGmailHookRuntimeConfig(
cfg: ClawdbotConfig,
cfg: MoltbotConfig,
overrides: GmailHookOverrides,
): { ok: true; value: GmailHookRuntimeConfig } | { ok: false; error: string } {
const hooks = cfg.hooks;

View File

@@ -6,7 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const tempDirs: string[] = [];
async function makeTempDir() {
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-hooks-e2e-"));
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hooks-e2e-"));
tempDirs.push(dir);
return dir;
}
@@ -63,7 +63,7 @@ describe("hooks install (e2e)", () => {
{
name: "@acme/hello-hooks",
version: "0.0.0",
clawdbot: { hooks: ["./hooks/hello-hook"] },
moltbot: { hooks: ["./hooks/hello-hook"] },
},
null,
2,
@@ -77,7 +77,7 @@ describe("hooks install (e2e)", () => {
"---",
'name: "hello-hook"',
'description: "Test hook"',
'metadata: {"clawdbot":{"events":["command:new"]}}',
'metadata: {"moltbot":{"events":["command:new"]}}',
"---",
"",
"# Hello Hook",

View File

@@ -1,6 +1,6 @@
import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
import { CONFIG_DIR } from "../utils.js";
import { hasBinary, isConfigPathTruthy, resolveConfigPath, resolveHookConfig } from "./config.js";
import type { HookEligibilityContext, HookEntry, HookInstallSpec } from "./types.js";
@@ -60,11 +60,11 @@ export type HookStatusReport = {
};
function resolveHookKey(entry: HookEntry): string {
return entry.clawdbot?.hookKey ?? entry.hook.name;
return entry.metadata?.hookKey ?? entry.hook.name;
}
function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] {
const install = entry.clawdbot?.install ?? [];
const install = entry.metadata?.install ?? [];
if (install.length === 0) return [];
// For hooks, we just list all install options
@@ -75,7 +75,7 @@ function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] {
if (!label) {
if (spec.kind === "bundled") {
label = "Bundled with Clawdbot";
label = "Bundled with Moltbot";
} else if (spec.kind === "npm" && spec.package) {
label = `Install ${spec.package} (npm)`;
} else if (spec.kind === "git" && spec.repository) {
@@ -91,28 +91,28 @@ function normalizeInstallOptions(entry: HookEntry): HookInstallOption[] {
function buildHookStatus(
entry: HookEntry,
config?: ClawdbotConfig,
config?: MoltbotConfig,
eligibility?: HookEligibilityContext,
): HookStatusEntry {
const hookKey = resolveHookKey(entry);
const hookConfig = resolveHookConfig(config, hookKey);
const managedByPlugin = entry.hook.source === "clawdbot-plugin";
const managedByPlugin = entry.hook.source === "moltbot-plugin";
const disabled = managedByPlugin ? false : hookConfig?.enabled === false;
const always = entry.clawdbot?.always === true;
const emoji = entry.clawdbot?.emoji ?? entry.frontmatter.emoji;
const always = entry.metadata?.always === true;
const emoji = entry.metadata?.emoji ?? entry.frontmatter.emoji;
const homepageRaw =
entry.clawdbot?.homepage ??
entry.metadata?.homepage ??
entry.frontmatter.homepage ??
entry.frontmatter.website ??
entry.frontmatter.url;
const homepage = homepageRaw?.trim() ? homepageRaw.trim() : undefined;
const events = entry.clawdbot?.events ?? [];
const events = entry.metadata?.events ?? [];
const requiredBins = entry.clawdbot?.requires?.bins ?? [];
const requiredAnyBins = entry.clawdbot?.requires?.anyBins ?? [];
const requiredEnv = entry.clawdbot?.requires?.env ?? [];
const requiredConfig = entry.clawdbot?.requires?.config ?? [];
const requiredOs = entry.clawdbot?.os ?? [];
const requiredBins = entry.metadata?.requires?.bins ?? [];
const requiredAnyBins = entry.metadata?.requires?.anyBins ?? [];
const requiredEnv = entry.metadata?.requires?.env ?? [];
const requiredConfig = entry.metadata?.requires?.config ?? [];
const requiredOs = entry.metadata?.os ?? [];
const missingBins = requiredBins.filter((bin) => {
if (hasBinary(bin)) return false;
@@ -202,7 +202,7 @@ function buildHookStatus(
export function buildWorkspaceHookStatus(
workspaceDir: string,
opts?: {
config?: ClawdbotConfig;
config?: MoltbotConfig;
managedHooksDir?: string;
entries?: HookEntry[];
eligibility?: HookEligibilityContext;

View File

@@ -9,7 +9,7 @@ import { afterEach, describe, expect, it } from "vitest";
const tempDirs: string[] = [];
function makeTempDir() {
const dir = path.join(os.tmpdir(), `clawdbot-hook-install-${randomUUID()}`);
const dir = path.join(os.tmpdir(), `moltbot-hook-install-${randomUUID()}`);
fs.mkdirSync(dir, { recursive: true });
tempDirs.push(dir);
return dir;
@@ -35,9 +35,9 @@ describe("installHooksFromArchive", () => {
zip.file(
"package/package.json",
JSON.stringify({
name: "@clawdbot/zip-hooks",
name: "@moltbot/zip-hooks",
version: "0.0.1",
clawdbot: { hooks: ["./hooks/zip-hook"] },
moltbot: { hooks: ["./hooks/zip-hook"] },
}),
);
zip.file(
@@ -46,7 +46,7 @@ describe("installHooksFromArchive", () => {
"---",
"name: zip-hook",
"description: Zip hook",
'metadata: {"clawdbot":{"events":["command:new"]}}',
'metadata: {"moltbot":{"events":["command:new"]}}',
"---",
"",
"# Zip Hook",
@@ -78,9 +78,9 @@ describe("installHooksFromArchive", () => {
fs.writeFileSync(
path.join(pkgDir, "package.json"),
JSON.stringify({
name: "@clawdbot/tar-hooks",
name: "@moltbot/tar-hooks",
version: "0.0.1",
clawdbot: { hooks: ["./hooks/tar-hook"] },
moltbot: { hooks: ["./hooks/tar-hook"] },
}),
"utf-8",
);
@@ -90,7 +90,7 @@ describe("installHooksFromArchive", () => {
"---",
"name: tar-hook",
"description: Tar hook",
'metadata: {"clawdbot":{"events":["command:new"]}}',
'metadata: {"moltbot":{"events":["command:new"]}}',
"---",
"",
"# Tar Hook",
@@ -128,7 +128,7 @@ describe("installHooksFromPath", () => {
"---",
"name: my-hook",
"description: My hook",
'metadata: {"clawdbot":{"events":["command:new"]}}',
'metadata: {"moltbot":{"events":["command:new"]}}',
"---",
"",
"# My Hook",

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
import { runCommandWithTimeout } from "../process/exec.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import {
@@ -22,7 +23,8 @@ type HookPackageManifest = {
name?: string;
version?: string;
dependencies?: Record<string, string>;
clawdbot?: { hooks?: string[] };
moltbot?: { hooks?: string[] };
[LEGACY_MANIFEST_KEY]?: { hooks?: string[] };
};
export type InstallHooksResult =
@@ -54,14 +56,14 @@ export function resolveHookInstallDir(hookId: string, hooksDir?: string): string
return path.join(hooksBase, safeDirName(hookId));
}
async function ensureClawdbotHooks(manifest: HookPackageManifest) {
const hooks = manifest.clawdbot?.hooks;
async function ensureMoltbotHooks(manifest: HookPackageManifest) {
const hooks = manifest.moltbot?.hooks ?? manifest[LEGACY_MANIFEST_KEY]?.hooks;
if (!Array.isArray(hooks)) {
throw new Error("package.json missing clawdbot.hooks");
throw new Error("package.json missing moltbot.hooks");
}
const list = hooks.map((e) => (typeof e === "string" ? e.trim() : "")).filter(Boolean);
if (list.length === 0) {
throw new Error("package.json clawdbot.hooks is empty");
throw new Error("package.json moltbot.hooks is empty");
}
return list;
}
@@ -120,7 +122,7 @@ async function installHookPackageFromDir(params: {
let hookEntries: string[];
try {
hookEntries = await ensureClawdbotHooks(manifest);
hookEntries = await ensureMoltbotHooks(manifest);
} catch (err) {
return { ok: false, error: String(err) };
}
@@ -293,7 +295,7 @@ export async function installHooksFromArchive(params: {
return { ok: false, error: `unsupported archive: ${archivePath}` };
}
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-hook-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hook-"));
const extractDir = path.join(tmpDir, "extract");
await fs.mkdir(extractDir, { recursive: true });
@@ -351,7 +353,7 @@ export async function installHooksFromNpmSpec(params: {
const spec = params.spec.trim();
if (!spec) return { ok: false, error: "missing npm spec" };
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-hook-pack-"));
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-hook-pack-"));
logger.info?.(`Downloading ${spec}`);
const res = await runCommandWithTimeout(["npm", "pack", spec], {
timeoutMs: Math.max(timeoutMs, 300_000),

View File

@@ -1,9 +1,9 @@
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
import type { HookInstallRecord } from "../config/types.hooks.js";
export type HookInstallUpdate = HookInstallRecord & { hookId: string };
export function recordHookInstall(cfg: ClawdbotConfig, update: HookInstallUpdate): ClawdbotConfig {
export function recordHookInstall(cfg: MoltbotConfig, update: HookInstallUpdate): MoltbotConfig {
const { hookId, ...record } = update;
const installs = {
...cfg.hooks?.internal?.installs,

View File

@@ -1,19 +1,19 @@
/**
* Hook system for clawdbot agent events
* Hook system for moltbot agent events
*
* Provides an extensible event-driven hook system for agent events
* like command processing, session lifecycle, etc.
*/
import type { WorkspaceBootstrapFile } from "../agents/workspace.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
export type InternalHookEventType = "command" | "session" | "agent" | "gateway";
export type AgentBootstrapHookContext = {
workspaceDir: string;
bootstrapFiles: WorkspaceBootstrapFile[];
cfg?: ClawdbotConfig;
cfg?: MoltbotConfig;
sessionKey?: string;
sessionId?: string;
agentId?: string;

View File

@@ -6,7 +6,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
import {
resolveDefaultAgentId,
resolveAgentWorkspaceDir,
@@ -18,7 +18,7 @@ import {
*/
export async function generateSlugViaLLM(params: {
sessionContent: string;
cfg: ClawdbotConfig;
cfg: MoltbotConfig;
}): Promise<string | null> {
let tempSessionFile: string | null = null;
@@ -28,7 +28,7 @@ export async function generateSlugViaLLM(params: {
const agentDir = resolveAgentDir(params.cfg, agentId);
// Create a temporary session file for this one-off LLM call
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-slug-"));
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-slug-"));
tempSessionFile = path.join(tempDir, "session.jsonl");
const prompt = `Based on this conversation, generate a short 1-2 word filename slug (lowercase, hyphen-separated, no file extension).

View File

@@ -9,7 +9,7 @@ import {
triggerInternalHook,
createInternalHookEvent,
} from "./internal-hooks.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
describe("loader", () => {
let tmpDir: string;
@@ -18,7 +18,7 @@ describe("loader", () => {
beforeEach(async () => {
clearInternalHooks();
// Create a temp directory for test modules
tmpDir = path.join(os.tmpdir(), `clawdbot-test-${Date.now()}`);
tmpDir = path.join(os.tmpdir(), `moltbot-test-${Date.now()}`);
await fs.mkdir(tmpDir, { recursive: true });
// Disable bundled hooks during tests by setting env var to non-existent directory
@@ -44,7 +44,7 @@ describe("loader", () => {
describe("loadInternalHooks", () => {
it("should return 0 when hooks are not enabled", async () => {
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: false,
@@ -57,7 +57,7 @@ describe("loader", () => {
});
it("should return 0 when hooks config is missing", async () => {
const cfg: ClawdbotConfig = {};
const cfg: MoltbotConfig = {};
const count = await loadInternalHooks(cfg, tmpDir);
expect(count).toBe(0);
});
@@ -72,7 +72,7 @@ describe("loader", () => {
`;
await fs.writeFile(handlerPath, handlerCode, "utf-8");
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -101,7 +101,7 @@ describe("loader", () => {
await fs.writeFile(handler1Path, "export default async function() {}", "utf-8");
await fs.writeFile(handler2Path, "export default async function() {}", "utf-8");
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -131,7 +131,7 @@ describe("loader", () => {
`;
await fs.writeFile(handlerPath, handlerCode, "utf-8");
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -153,7 +153,7 @@ describe("loader", () => {
it("should handle module loading errors gracefully", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -184,7 +184,7 @@ describe("loader", () => {
const handlerPath = path.join(tmpDir, "bad-export.js");
await fs.writeFile(handlerPath, 'export default "not a function";', "utf-8");
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -213,7 +213,7 @@ describe("loader", () => {
// Get relative path from cwd
const relativePath = path.relative(process.cwd(), handlerPath);
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,
@@ -245,7 +245,7 @@ describe("loader", () => {
`;
await fs.writeFile(handlerPath, handlerCode, "utf-8");
const cfg: ClawdbotConfig = {
const cfg: MoltbotConfig = {
hooks: {
internal: {
enabled: true,

View File

@@ -8,7 +8,7 @@
import { pathToFileURL } from "node:url";
import path from "node:path";
import { registerInternalHook } from "./internal-hooks.js";
import type { ClawdbotConfig } from "../config/config.js";
import type { MoltbotConfig } from "../config/config.js";
import type { InternalHookHandler } from "./internal-hooks.js";
import { loadWorkspaceHookEntries } from "./workspace.js";
import { resolveHookConfig } from "./config.js";
@@ -21,7 +21,7 @@ import { shouldIncludeHook } from "./config.js";
* 1. Directory-based discovery (bundled, managed, workspace)
* 2. Legacy config handlers (backwards compatibility)
*
* @param cfg - Clawdbot configuration
* @param cfg - Moltbot configuration
* @param workspaceDir - Workspace directory for hook discovery
* @returns Number of handlers successfully loaded
*
@@ -33,10 +33,7 @@ import { shouldIncludeHook } from "./config.js";
* console.log(`Loaded ${count} hook handlers`);
* ```
*/
export async function loadInternalHooks(
cfg: ClawdbotConfig,
workspaceDir: string,
): Promise<number> {
export async function loadInternalHooks(cfg: MoltbotConfig, workspaceDir: string): Promise<number> {
// Check if hooks are enabled
if (!cfg.hooks?.internal?.enabled) {
return 0;
@@ -66,7 +63,7 @@ export async function loadInternalHooks(
const mod = (await import(cacheBustedUrl)) as Record<string, unknown>;
// Get handler function (default or named export)
const exportName = entry.clawdbot?.export ?? "default";
const exportName = entry.metadata?.export ?? "default";
const handler = mod[exportName];
if (typeof handler !== "function") {
@@ -77,7 +74,7 @@ export async function loadInternalHooks(
}
// Register for all events listed in metadata
const events = entry.clawdbot?.events ?? [];
const events = entry.metadata?.events ?? [];
if (events.length === 0) {
console.warn(`Hook warning: Hook '${entry.hook.name}' has no events defined in metadata`);
continue;

View File

@@ -1,7 +1,7 @@
import path from "node:path";
import { pathToFileURL } from "node:url";
import type { ClawdbotPluginApi } from "../plugins/types.js";
import type { MoltbotPluginApi } from "../plugins/types.js";
import type { HookEntry } from "./types.js";
import { shouldIncludeHook } from "./config.js";
import { loadHookEntriesFromDir } from "./workspace.js";
@@ -14,36 +14,36 @@ export type PluginHookLoadResult = {
errors: string[];
};
function resolveHookDir(api: ClawdbotPluginApi, dir: string): string {
function resolveHookDir(api: MoltbotPluginApi, dir: string): string {
if (path.isAbsolute(dir)) return dir;
return path.resolve(path.dirname(api.source), dir);
}
function normalizePluginHookEntry(api: ClawdbotPluginApi, entry: HookEntry): HookEntry {
function normalizePluginHookEntry(api: MoltbotPluginApi, entry: HookEntry): HookEntry {
return {
...entry,
hook: {
...entry.hook,
source: "clawdbot-plugin",
source: "moltbot-plugin",
pluginId: api.id,
},
clawdbot: {
...entry.clawdbot,
hookKey: entry.clawdbot?.hookKey ?? `${api.id}:${entry.hook.name}`,
events: entry.clawdbot?.events ?? [],
metadata: {
...entry.metadata,
hookKey: entry.metadata?.hookKey ?? `${api.id}:${entry.hook.name}`,
events: entry.metadata?.events ?? [],
},
};
}
async function loadHookHandler(
entry: HookEntry,
api: ClawdbotPluginApi,
api: MoltbotPluginApi,
): Promise<InternalHookHandler | null> {
try {
const url = pathToFileURL(entry.hook.handlerPath).href;
const cacheBustedUrl = `${url}?t=${Date.now()}`;
const mod = (await import(cacheBustedUrl)) as Record<string, unknown>;
const exportName = entry.clawdbot?.export ?? "default";
const exportName = entry.metadata?.export ?? "default";
const handler = mod[exportName];
if (typeof handler === "function") {
return handler as InternalHookHandler;
@@ -57,13 +57,13 @@ async function loadHookHandler(
}
export async function registerPluginHooksFromDir(
api: ClawdbotPluginApi,
api: MoltbotPluginApi,
dir: string,
): Promise<PluginHookLoadResult> {
const resolvedDir = resolveHookDir(api, dir);
const hooks = loadHookEntriesFromDir({
dir: resolvedDir,
source: "clawdbot-plugin",
source: "moltbot-plugin",
pluginId: api.id,
});
@@ -76,7 +76,7 @@ export async function registerPluginHooksFromDir(
for (const entry of hooks) {
const normalizedEntry = normalizePluginHookEntry(api, entry);
const events = normalizedEntry.clawdbot?.events ?? [];
const events = normalizedEntry.metadata?.events ?? [];
if (events.length === 0) {
api.logger.warn?.(`[hooks] ${entry.hook.name} has no events; skipping`);
api.registerHook(events, async () => undefined, {

View File

@@ -116,7 +116,7 @@ describe("decideSoulEvil", () => {
describe("applySoulEvilOverride", () => {
it("replaces SOUL content when evil is active and file exists", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
await writeWorkspaceFile({
dir: tempDir,
name: DEFAULT_SOUL_EVIL_FILENAME,
@@ -140,7 +140,7 @@ describe("applySoulEvilOverride", () => {
});
it("leaves SOUL content when evil file is missing", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
const files = makeFiles({
path: path.join(tempDir, DEFAULT_SOUL_FILENAME),
});
@@ -158,7 +158,7 @@ describe("applySoulEvilOverride", () => {
});
it("uses custom evil filename when configured", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
await writeWorkspaceFile({
dir: tempDir,
name: "SOUL_EVIL_CUSTOM.md",
@@ -182,7 +182,7 @@ describe("applySoulEvilOverride", () => {
});
it("warns and skips when evil file is empty", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
await writeWorkspaceFile({
dir: tempDir,
name: DEFAULT_SOUL_EVIL_FILENAME,
@@ -209,7 +209,7 @@ describe("applySoulEvilOverride", () => {
});
it("leaves files untouched when SOUL.md is not in bootstrap files", async () => {
const tempDir = await makeTempWorkspace("clawdbot-soul-");
const tempDir = await makeTempWorkspace("moltbot-soul-");
await writeWorkspaceFile({
dir: tempDir,
name: DEFAULT_SOUL_EVIL_FILENAME,

View File

@@ -7,7 +7,7 @@ export type HookInstallSpec = {
bins?: string[];
};
export type ClawdbotHookMetadata = {
export type MoltbotHookMetadata = {
always?: boolean;
hookKey?: string;
emoji?: string;
@@ -35,7 +35,7 @@ export type ParsedHookFrontmatter = Record<string, string>;
export type Hook = {
name: string;
description: string;
source: "clawdbot-bundled" | "clawdbot-managed" | "clawdbot-workspace" | "clawdbot-plugin";
source: "moltbot-bundled" | "moltbot-managed" | "moltbot-workspace" | "moltbot-plugin";
pluginId?: string;
filePath: string; // Path to HOOK.md
baseDir: string; // Directory containing hook
@@ -47,7 +47,7 @@ export type HookSource = Hook["source"];
export type HookEntry = {
hook: Hook;
frontmatter: ParsedHookFrontmatter;
clawdbot?: ClawdbotHookMetadata;
metadata?: MoltbotHookMetadata;
invocation?: HookInvocationPolicy;
};

View File

@@ -1,13 +1,14 @@
import fs from "node:fs";
import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js";
import { LEGACY_MANIFEST_KEY } from "../compat/legacy-names.js";
import type { MoltbotConfig } from "../config/config.js";
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
import { resolveBundledHooksDir } from "./bundled-dir.js";
import { shouldIncludeHook } from "./config.js";
import {
parseFrontmatter,
resolveClawdbotMetadata,
resolveMoltbotMetadata,
resolveHookInvocationPolicy,
} from "./frontmatter.js";
import type {
@@ -21,12 +22,13 @@ import type {
type HookPackageManifest = {
name?: string;
clawdbot?: { hooks?: string[] };
moltbot?: { hooks?: string[] };
[LEGACY_MANIFEST_KEY]?: { hooks?: string[] };
};
function filterHookEntries(
entries: HookEntry[],
config?: ClawdbotConfig,
config?: MoltbotConfig,
eligibility?: HookEligibilityContext,
): HookEntry[] {
return entries.filter((entry) => shouldIncludeHook({ entry, config, eligibility }));
@@ -44,7 +46,7 @@ function readHookPackageManifest(dir: string): HookPackageManifest | null {
}
function resolvePackageHooks(manifest: HookPackageManifest): string[] {
const raw = manifest.clawdbot?.hooks;
const raw = manifest.moltbot?.hooks ?? manifest[LEGACY_MANIFEST_KEY]?.hooks;
if (!Array.isArray(raw)) return [];
return raw.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean);
}
@@ -167,7 +169,7 @@ export function loadHookEntriesFromDir(params: {
pluginId: params.pluginId,
},
frontmatter,
clawdbot: resolveClawdbotMetadata(frontmatter),
metadata: resolveMoltbotMetadata(frontmatter),
invocation: resolveHookInvocationPolicy(frontmatter),
};
return entry;
@@ -177,7 +179,7 @@ export function loadHookEntriesFromDir(params: {
function loadHookEntries(
workspaceDir: string,
opts?: {
config?: ClawdbotConfig;
config?: MoltbotConfig;
managedHooksDir?: string;
bundledHooksDir?: string;
},
@@ -193,23 +195,23 @@ function loadHookEntries(
const bundledHooks = bundledHooksDir
? loadHooksFromDir({
dir: bundledHooksDir,
source: "clawdbot-bundled",
source: "moltbot-bundled",
})
: [];
const extraHooks = extraDirs.flatMap((dir) => {
const resolved = resolveUserPath(dir);
return loadHooksFromDir({
dir: resolved,
source: "clawdbot-workspace", // Extra dirs treated as workspace
source: "moltbot-workspace", // Extra dirs treated as workspace
});
});
const managedHooks = loadHooksFromDir({
dir: managedHooksDir,
source: "clawdbot-managed",
source: "moltbot-managed",
});
const workspaceHooks = loadHooksFromDir({
dir: workspaceHooksDir,
source: "clawdbot-workspace",
source: "moltbot-workspace",
});
const merged = new Map<string, Hook>();
@@ -230,7 +232,7 @@ function loadHookEntries(
return {
hook,
frontmatter,
clawdbot: resolveClawdbotMetadata(frontmatter),
metadata: resolveMoltbotMetadata(frontmatter),
invocation: resolveHookInvocationPolicy(frontmatter),
};
});
@@ -239,7 +241,7 @@ function loadHookEntries(
export function buildWorkspaceHookSnapshot(
workspaceDir: string,
opts?: {
config?: ClawdbotConfig;
config?: MoltbotConfig;
managedHooksDir?: string;
bundledHooksDir?: string;
entries?: HookEntry[];
@@ -253,7 +255,7 @@ export function buildWorkspaceHookSnapshot(
return {
hooks: eligible.map((entry) => ({
name: entry.hook.name,
events: entry.clawdbot?.events ?? [],
events: entry.metadata?.events ?? [],
})),
resolvedHooks: eligible.map((entry) => entry.hook),
version: opts?.snapshotVersion,
@@ -263,7 +265,7 @@ export function buildWorkspaceHookSnapshot(
export function loadWorkspaceHookEntries(
workspaceDir: string,
opts?: {
config?: ClawdbotConfig;
config?: MoltbotConfig;
managedHooksDir?: string;
bundledHooksDir?: string;
},