From 506bed5aed40820565b7db66a963b8163968208f Mon Sep 17 00:00:00 2001 From: Josh Long Date: Mon, 26 Jan 2026 22:07:43 +0000 Subject: [PATCH 001/101] feat(telegram): add sticker support with vision caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for receiving and sending Telegram stickers: Inbound: - Receive static WEBP stickers (skip animated/video) - Process stickers through dedicated vision call for descriptions - Cache vision descriptions to avoid repeated API calls - Graceful error handling for fetch failures Outbound: - Add sticker action to send stickers by fileId - Add sticker-search action to find cached stickers by query - Accept stickerId from shared schema, convert to fileId Cache: - Store sticker metadata (fileId, emoji, setName, description) - Fuzzy search by description, emoji, and set name - Persist to ~/.clawdbot/telegram/sticker-cache.json Config: - Single `channels.telegram.actions.sticker` option enables both send and search actions 🤖 AI-assisted: Built with Claude Code (claude-opus-4-5) Testing: Fully tested - unit tests pass, live tested on dev gateway The contributor understands and has reviewed all code changes. Co-Authored-By: Claude Opus 4.5 --- docs/channels/telegram.md | 124 +++++++++ src/agents/tools/telegram-actions.ts | 61 +++++ src/auto-reply/templating.ts | 3 + src/channels/plugins/actions/telegram.ts | 40 +++ src/channels/plugins/message-action-names.ts | 1 + src/config/types.telegram.ts | 2 + src/config/zod-schema.providers-core.ts | 1 + src/infra/outbound/message-action-spec.ts | 1 + src/telegram/bot-handlers.ts | 31 ++- src/telegram/bot-message-context.ts | 49 +++- src/telegram/bot-message-dispatch.ts | 46 ++++ ...s-media-file-path-no-file-download.test.ts | 196 +++++++++++++ src/telegram/bot/delivery.ts | 76 +++++- src/telegram/bot/types.ts | 14 + ...send.returns-undefined-empty-input.test.ts | 183 ++++++++++++- src/telegram/send.ts | 93 +++++++ src/telegram/sticker-cache.test.ts | 257 ++++++++++++++++++ src/telegram/sticker-cache.ts | 201 ++++++++++++++ 18 files changed, 1365 insertions(+), 14 deletions(-) create mode 100644 src/telegram/sticker-cache.test.ts create mode 100644 src/telegram/sticker-cache.ts diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index 39f3a2ec3..2d8c472bd 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -383,6 +383,129 @@ For message tool sends, set `asVoice: true` with a voice-compatible audio `media } ``` +## Stickers + +Clawdbot supports receiving and sending Telegram stickers with intelligent caching. + +### Receiving stickers + +When a user sends a sticker, Clawdbot handles it based on the sticker type: + +- **Static stickers (WEBP):** Downloaded and processed through vision. The sticker appears as a `` placeholder in the message content. +- **Animated stickers (TGS):** Skipped (Lottie format not supported for processing). +- **Video stickers (WEBM):** Skipped (video format not supported for processing). + +Template context fields available when receiving stickers: +- `StickerEmoji` — the emoji associated with the sticker +- `StickerSetName` — the name of the sticker set +- `StickerFileId` — the Telegram file ID (used for sending the same sticker back) + +### Sticker cache + +Stickers are processed through the AI's vision capabilities to generate descriptions. Since the same stickers are often sent repeatedly, Clawdbot caches these descriptions to avoid redundant API calls. + +**How it works:** + +1. **First encounter:** The sticker image is sent to the AI for vision analysis. The AI generates a description (e.g., "A cartoon cat waving enthusiastically"). +2. **Cache storage:** The description is saved along with the sticker's file ID, emoji, and set name. +3. **Subsequent encounters:** When the same sticker is seen again, the cached description is used directly. The image is not sent to the AI. + +**Cache location:** `~/.clawdbot/telegram/sticker-cache.json` + +**Cache entry format:** +```json +{ + "fileId": "CAACAgIAAxkBAAI...", + "emoji": "👋", + "setName": "CoolCats", + "description": "A cartoon cat waving enthusiastically", + "addedAt": "2026-01-15T10:30:00.000Z" +} +``` + +**Benefits:** +- Reduces API costs by avoiding repeated vision calls for the same sticker +- Faster response times for cached stickers (no vision processing delay) +- Enables sticker search functionality based on cached descriptions + +The cache is populated automatically as stickers are received. There is no manual cache management required. + +### Sending stickers + +The agent can send and search stickers using the `sticker` and `sticker-search` actions. These are disabled by default and must be enabled in config: + +```json5 +{ + channels: { + telegram: { + actions: { + sticker: true + } + } + } +} +``` + +**Send a sticker:** + +```json5 +{ + "action": "sticker", + "channel": "telegram", + "to": "123456789", + "fileId": "CAACAgIAAxkBAAI..." +} +``` + +Parameters: +- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `StickerFileId` when receiving a sticker, or from a `sticker-search` result. +- `replyTo` (optional) — message ID to reply to. +- `threadId` (optional) — message thread ID for forum topics. + +**Search for stickers:** + +The agent can search cached stickers by description, emoji, or set name: + +```json5 +{ + "action": "sticker-search", + "channel": "telegram", + "query": "cat waving", + "limit": 5 +} +``` + +Returns matching stickers from the cache: +```json5 +{ + "ok": true, + "count": 2, + "stickers": [ + { + "fileId": "CAACAgIAAxkBAAI...", + "emoji": "👋", + "description": "A cartoon cat waving enthusiastically", + "setName": "CoolCats" + } + ] +} +``` + +The search uses fuzzy matching across description text, emoji characters, and set names. + +**Example with threading:** + +```json5 +{ + "action": "sticker", + "channel": "telegram", + "to": "-1001234567890", + "fileId": "CAACAgIAAxkBAAI...", + "replyTo": 42, + "threadId": 123 +} +``` + ## Streaming (drafts) Telegram can stream **draft bubbles** while the agent is generating a response. Clawdbot uses Bot API `sendMessageDraft` (not real messages) and then sends the @@ -537,6 +660,7 @@ Provider options: - `channels.telegram.actions.reactions`: gate Telegram tool reactions. - `channels.telegram.actions.sendMessage`: gate Telegram tool message sends. - `channels.telegram.actions.deleteMessage`: gate Telegram tool message deletes. +- `channels.telegram.actions.sticker`: gate Telegram sticker actions — send and search (default: false). - `channels.telegram.reactionNotifications`: `off | own | all` — control which reactions trigger system events (default: `own` when not set). - `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set). diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index 891ab2b45..40a97d874 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -6,7 +6,9 @@ import { editMessageTelegram, reactMessageTelegram, sendMessageTelegram, + sendStickerTelegram, } from "../../telegram/send.js"; +import { getCacheStats, searchStickers } from "../../telegram/sticker-cache.js"; import { resolveTelegramToken } from "../../telegram/token.js"; import { resolveTelegramInlineButtonsScope, @@ -255,5 +257,64 @@ export async function handleTelegramAction( }); } + if (action === "sendSticker") { + if (!isActionEnabled("sticker")) { + throw new Error( + "Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true.", + ); + } + const to = readStringParam(params, "to", { required: true }); + const fileId = readStringParam(params, "fileId", { required: true }); + const replyToMessageId = readNumberParam(params, "replyToMessageId", { + integer: true, + }); + const messageThreadId = readNumberParam(params, "messageThreadId", { + integer: true, + }); + const token = resolveTelegramToken(cfg, { accountId }).token; + if (!token) { + throw new Error( + "Telegram bot token missing. Set TELEGRAM_BOT_TOKEN or channels.telegram.botToken.", + ); + } + const result = await sendStickerTelegram(to, fileId, { + token, + accountId: accountId ?? undefined, + replyToMessageId: replyToMessageId ?? undefined, + messageThreadId: messageThreadId ?? undefined, + }); + return jsonResult({ + ok: true, + messageId: result.messageId, + chatId: result.chatId, + }); + } + + if (action === "searchSticker") { + if (!isActionEnabled("sticker")) { + throw new Error( + "Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true.", + ); + } + const query = readStringParam(params, "query", { required: true }); + const limit = readNumberParam(params, "limit", { integer: true }) ?? 5; + const results = searchStickers(query, limit); + return jsonResult({ + ok: true, + count: results.length, + stickers: results.map((s) => ({ + fileId: s.fileId, + emoji: s.emoji, + description: s.description, + setName: s.setName, + })), + }); + } + + if (action === "stickerCacheStats") { + const stats = getCacheStats(); + return jsonResult({ ok: true, ...stats }); + } + throw new Error(`Unsupported Telegram action: ${action}`); } diff --git a/src/auto-reply/templating.ts b/src/auto-reply/templating.ts index dd424ee71..79692a50d 100644 --- a/src/auto-reply/templating.ts +++ b/src/auto-reply/templating.ts @@ -1,4 +1,5 @@ import type { ChannelId } from "../channels/plugins/types.js"; +import type { StickerMetadata } from "../telegram/bot/types.js"; import type { InternalMessageChannel } from "../utils/message-channel.js"; import type { CommandArgs } from "./commands-registry.types.js"; import type { @@ -64,6 +65,8 @@ export type MsgContext = { MediaPaths?: string[]; MediaUrls?: string[]; MediaTypes?: string[]; + /** Telegram sticker metadata (emoji, set name, file IDs, cached description). */ + Sticker?: StickerMetadata; OutputDir?: string; OutputBase?: string; /** Remote host for SCP when media lives on a different machine (e.g., clawdbot@192.168.64.3). */ diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index 364707e0a..f8c7dc0fb 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -1,6 +1,7 @@ import { createActionGate, readNumberParam, + readStringArrayParam, readStringOrNumberParam, readStringParam, } from "../../../agents/tools/common.js"; @@ -45,6 +46,10 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { if (gate("reactions")) actions.add("react"); if (gate("deleteMessage")) actions.add("delete"); if (gate("editMessage")) actions.add("edit"); + if (gate("sticker")) { + actions.add("sticker"); + actions.add("sticker-search"); + } return Array.from(actions); }, supportsButtons: ({ cfg }) => { @@ -141,6 +146,41 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { ); } + if (action === "sticker") { + const to = + readStringParam(params, "to") ?? readStringParam(params, "target", { required: true }); + // Accept stickerId (array from shared schema) and use first element as fileId + const stickerIds = readStringArrayParam(params, "stickerId"); + const fileId = stickerIds?.[0] ?? readStringParam(params, "fileId", { required: true }); + const replyToMessageId = readNumberParam(params, "replyTo", { integer: true }); + const messageThreadId = readNumberParam(params, "threadId", { integer: true }); + return await handleTelegramAction( + { + action: "sendSticker", + to, + fileId, + replyToMessageId: replyToMessageId ?? undefined, + messageThreadId: messageThreadId ?? undefined, + accountId: accountId ?? undefined, + }, + cfg, + ); + } + + if (action === "sticker-search") { + const query = readStringParam(params, "query", { required: true }); + const limit = readNumberParam(params, "limit", { integer: true }); + return await handleTelegramAction( + { + action: "searchSticker", + query, + limit: limit ?? undefined, + accountId: accountId ?? undefined, + }, + cfg, + ); + } + throw new Error(`Action ${action} is not supported for provider ${providerId}.`); }, }; diff --git a/src/channels/plugins/message-action-names.ts b/src/channels/plugins/message-action-names.ts index c884f6da3..1884cacb0 100644 --- a/src/channels/plugins/message-action-names.ts +++ b/src/channels/plugins/message-action-names.ts @@ -25,6 +25,7 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [ "thread-reply", "search", "sticker", + "sticker-search", "member-info", "role-info", "emoji-list", diff --git a/src/config/types.telegram.ts b/src/config/types.telegram.ts index 4d476f88e..9a96bce45 100644 --- a/src/config/types.telegram.ts +++ b/src/config/types.telegram.ts @@ -16,6 +16,8 @@ export type TelegramActionConfig = { sendMessage?: boolean; deleteMessage?: boolean; editMessage?: boolean; + /** Enable sticker actions (send and search). */ + sticker?: boolean; }; export type TelegramNetworkConfig = { diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index fbf6a2173..ed7dda22a 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -128,6 +128,7 @@ export const TelegramAccountSchemaBase = z reactions: z.boolean().optional(), sendMessage: z.boolean().optional(), deleteMessage: z.boolean().optional(), + sticker: z.boolean().optional(), }) .strict() .optional(), diff --git a/src/infra/outbound/message-action-spec.ts b/src/infra/outbound/message-action-spec.ts index c4f712e0f..639e641d0 100644 --- a/src/infra/outbound/message-action-spec.ts +++ b/src/infra/outbound/message-action-spec.ts @@ -30,6 +30,7 @@ export const MESSAGE_ACTION_TARGET_MODE: Record m.msg.caption || m.msg.text); const primaryEntry = captionMsg ?? entry.messages[0]; - const allMedia: Array<{ path: string; contentType?: string }> = []; + const allMedia: Array<{ + path: string; + contentType?: string; + stickerMetadata?: { emoji?: string; setName?: string; fileId?: string }; + }> = []; for (const { ctx } of entry.messages) { const media = await resolveMedia(ctx, mediaMaxBytes, opts.token, opts.proxyFetch); if (media) { - allMedia.push({ path: media.path, contentType: media.contentType }); + allMedia.push({ + path: media.path, + contentType: media.contentType, + stickerMetadata: media.stickerMetadata, + }); } } @@ -595,7 +603,24 @@ export const registerTelegramHandlers = ({ } throw mediaErr; } - const allMedia = media ? [{ path: media.path, contentType: media.contentType }] : []; + + // Skip sticker-only messages where the sticker was skipped (animated/video) + // These have no media and no text content to process. + const hasText = Boolean((msg.text ?? msg.caption ?? "").trim()); + if (msg.sticker && !media && !hasText) { + logVerbose("telegram: skipping sticker-only message (unsupported sticker type)"); + return; + } + + const allMedia = media + ? [ + { + path: media.path, + contentType: media.contentType, + stickerMetadata: media.stickerMetadata, + }, + ] + : []; const senderId = msg.from?.id ? String(msg.from.id) : ""; const conversationKey = resolvedThreadId != null ? `${chatId}:topic:${resolvedThreadId}` : String(chatId); diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index a054943a2..71ac8a011 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -49,7 +49,17 @@ import { import { upsertTelegramPairingRequest } from "./pairing-store.js"; import type { TelegramContext } from "./bot/types.js"; -type TelegramMediaRef = { path: string; contentType?: string }; +type TelegramMediaRef = { + path: string; + contentType?: string; + stickerMetadata?: { + emoji?: string; + setName?: string; + fileId?: string; + fileUniqueId?: string; + cachedDescription?: string; + }; +}; type TelegramMessageContextOptions = { forceWasMentioned?: boolean; @@ -302,6 +312,18 @@ export const buildTelegramMessageContext = async ({ else if (msg.video) placeholder = ""; else if (msg.audio || msg.voice) placeholder = ""; else if (msg.document) placeholder = ""; + else if (msg.sticker) placeholder = ""; + + // Check if sticker has a cached description - if so, use it instead of sending the image + const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription; + const stickerCacheHit = Boolean(cachedStickerDescription); + if (stickerCacheHit) { + // Format cached description with sticker context + const emoji = allMedia[0]?.stickerMetadata?.emoji; + const setName = allMedia[0]?.stickerMetadata?.setName; + const stickerContext = [emoji, setName ? `from "${setName}"` : null].filter(Boolean).join(" "); + placeholder = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${cachedStickerDescription}`; + } const locationData = extractTelegramLocation(msg); const locationText = locationData ? formatLocationText(locationData) : undefined; @@ -525,15 +547,26 @@ export const buildTelegramMessageContext = async ({ ForwardedDate: forwardOrigin?.date ? forwardOrigin.date * 1000 : undefined, Timestamp: msg.date ? msg.date * 1000 : undefined, WasMentioned: isGroup ? effectiveWasMentioned : undefined, - MediaPath: allMedia[0]?.path, - MediaType: allMedia[0]?.contentType, - MediaUrl: allMedia[0]?.path, - MediaPaths: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined, - MediaUrls: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined, - MediaTypes: - allMedia.length > 0 + // Filter out cached stickers from media - their description is already in the message body + MediaPath: stickerCacheHit ? undefined : allMedia[0]?.path, + MediaType: stickerCacheHit ? undefined : allMedia[0]?.contentType, + MediaUrl: stickerCacheHit ? undefined : allMedia[0]?.path, + MediaPaths: stickerCacheHit + ? undefined + : allMedia.length > 0 + ? allMedia.map((m) => m.path) + : undefined, + MediaUrls: stickerCacheHit + ? undefined + : allMedia.length > 0 + ? allMedia.map((m) => m.path) + : undefined, + MediaTypes: stickerCacheHit + ? undefined + : allMedia.length > 0 ? (allMedia.map((m) => m.contentType).filter(Boolean) as string[]) : undefined, + Sticker: allMedia[0]?.stickerMetadata, ...(locationData ? toLocationContext(locationData) : undefined), CommandAuthorized: commandAuthorized, MessageThreadId: resolvedThreadId, diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index 334c4c212..e24796d6c 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -12,6 +12,8 @@ import { resolveMarkdownTableMode } from "../config/markdown-tables.js"; import { deliverReplies } from "./bot/delivery.js"; import { resolveTelegramDraftStreamingChunking } from "./draft-chunking.js"; import { createTelegramDraftStream } from "./draft-stream.js"; +import { cacheSticker, describeStickerImage } from "./sticker-cache.js"; +import { resolveAgentDir } from "../agents/agent-scope.js"; export const dispatchTelegramMessage = async ({ context, @@ -128,6 +130,49 @@ export const dispatchTelegramMessage = async ({ }); const chunkMode = resolveChunkMode(cfg, "telegram", route.accountId); + // Handle uncached stickers: get a dedicated vision description before dispatch + // This ensures we cache a raw description rather than a conversational response + const sticker = ctxPayload.Sticker; + if (sticker?.fileUniqueId && !sticker.cachedDescription && ctxPayload.MediaPath) { + const agentDir = resolveAgentDir(cfg, route.agentId); + const description = await describeStickerImage({ + imagePath: ctxPayload.MediaPath, + cfg, + agentDir, + }); + if (description) { + // Format the description with sticker context + const stickerContext = [sticker.emoji, sticker.setName ? `from "${sticker.setName}"` : null] + .filter(Boolean) + .join(" "); + const formattedDesc = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${description}`; + + // Update context to use description instead of image + sticker.cachedDescription = description; + ctxPayload.Body = formattedDesc; + ctxPayload.BodyForAgent = formattedDesc; + // Clear media paths so native vision doesn't process the image again + ctxPayload.MediaPath = undefined; + ctxPayload.MediaType = undefined; + ctxPayload.MediaUrl = undefined; + ctxPayload.MediaPaths = undefined; + ctxPayload.MediaUrls = undefined; + ctxPayload.MediaTypes = undefined; + + // Cache the description for future encounters + cacheSticker({ + fileId: sticker.fileId, + fileUniqueId: sticker.fileUniqueId, + emoji: sticker.emoji, + setName: sticker.setName, + description, + cachedAt: new Date().toISOString(), + receivedFrom: ctxPayload.From, + }); + logVerbose(`telegram: cached sticker description for ${sticker.fileUniqueId}`); + } + } + const { queuedFinal } = await dispatchReplyWithBufferedBlockDispatcher({ ctx: ctxPayload, cfg, @@ -139,6 +184,7 @@ export const dispatchTelegramMessage = async ({ await flushDraft(); draftStream?.stop(); } + await deliverReplies({ replies: [payload], chatId: String(chatId), diff --git a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts index b6c1ca419..dd75e6798 100644 --- a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts +++ b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts @@ -405,6 +405,202 @@ describe("telegram media groups", () => { ); }); +describe("telegram stickers", () => { + const STICKER_TEST_TIMEOUT_MS = process.platform === "win32" ? 30_000 : 20_000; + + it( + "downloads static sticker (WEBP) and includes sticker metadata", + async () => { + const { createTelegramBot } = await import("./bot.js"); + const replyModule = await import("../auto-reply/reply.js"); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + + onSpy.mockReset(); + replySpy.mockReset(); + sendChatActionSpy.mockReset(); + + const runtimeLog = vi.fn(); + const runtimeError = vi.fn(); + createTelegramBot({ + token: "tok", + runtime: { + log: runtimeLog, + error: runtimeError, + exit: () => { + throw new Error("exit"); + }, + }, + }); + const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( + ctx: Record, + ) => Promise; + expect(handler).toBeDefined(); + + const fetchSpy = vi.spyOn(globalThis, "fetch" as never).mockResolvedValueOnce({ + ok: true, + status: 200, + statusText: "OK", + headers: { get: () => "image/webp" }, + arrayBuffer: async () => new Uint8Array([0x52, 0x49, 0x46, 0x46]).buffer, // RIFF header + } as Response); + + await handler({ + message: { + message_id: 100, + chat: { id: 1234, type: "private" }, + sticker: { + file_id: "sticker_file_id_123", + file_unique_id: "sticker_unique_123", + type: "regular", + width: 512, + height: 512, + is_animated: false, + is_video: false, + emoji: "🎉", + set_name: "TestStickerPack", + }, + date: 1736380800, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ file_path: "stickers/sticker.webp" }), + }); + + expect(runtimeError).not.toHaveBeenCalled(); + expect(fetchSpy).toHaveBeenCalledWith( + "https://api.telegram.org/file/bottok/stickers/sticker.webp", + ); + expect(replySpy).toHaveBeenCalledTimes(1); + const payload = replySpy.mock.calls[0][0]; + expect(payload.Body).toContain(""); + expect(payload.Sticker?.emoji).toBe("🎉"); + expect(payload.Sticker?.setName).toBe("TestStickerPack"); + expect(payload.Sticker?.fileId).toBe("sticker_file_id_123"); + + fetchSpy.mockRestore(); + }, + STICKER_TEST_TIMEOUT_MS, + ); + + it( + "skips animated stickers (TGS format)", + async () => { + const { createTelegramBot } = await import("./bot.js"); + const replyModule = await import("../auto-reply/reply.js"); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + + onSpy.mockReset(); + replySpy.mockReset(); + + const runtimeError = vi.fn(); + const fetchSpy = vi.spyOn(globalThis, "fetch" as never); + + createTelegramBot({ + token: "tok", + runtime: { + log: vi.fn(), + error: runtimeError, + exit: () => { + throw new Error("exit"); + }, + }, + }); + const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( + ctx: Record, + ) => Promise; + expect(handler).toBeDefined(); + + await handler({ + message: { + message_id: 101, + chat: { id: 1234, type: "private" }, + sticker: { + file_id: "animated_sticker_id", + file_unique_id: "animated_unique", + type: "regular", + width: 512, + height: 512, + is_animated: true, // TGS format + is_video: false, + emoji: "😎", + set_name: "AnimatedPack", + }, + date: 1736380800, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ file_path: "stickers/animated.tgs" }), + }); + + // Should not attempt to download animated stickers + expect(fetchSpy).not.toHaveBeenCalled(); + // Should still process the message (as text-only, no media) + expect(replySpy).not.toHaveBeenCalled(); // No text content, so no reply generated + expect(runtimeError).not.toHaveBeenCalled(); + + fetchSpy.mockRestore(); + }, + STICKER_TEST_TIMEOUT_MS, + ); + + it( + "skips video stickers (WEBM format)", + async () => { + const { createTelegramBot } = await import("./bot.js"); + const replyModule = await import("../auto-reply/reply.js"); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + + onSpy.mockReset(); + replySpy.mockReset(); + + const runtimeError = vi.fn(); + const fetchSpy = vi.spyOn(globalThis, "fetch" as never); + + createTelegramBot({ + token: "tok", + runtime: { + log: vi.fn(), + error: runtimeError, + exit: () => { + throw new Error("exit"); + }, + }, + }); + const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( + ctx: Record, + ) => Promise; + expect(handler).toBeDefined(); + + await handler({ + message: { + message_id: 102, + chat: { id: 1234, type: "private" }, + sticker: { + file_id: "video_sticker_id", + file_unique_id: "video_unique", + type: "regular", + width: 512, + height: 512, + is_animated: false, + is_video: true, // WEBM format + emoji: "🎬", + set_name: "VideoPack", + }, + date: 1736380800, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ file_path: "stickers/video.webm" }), + }); + + // Should not attempt to download video stickers + expect(fetchSpy).not.toHaveBeenCalled(); + expect(replySpy).not.toHaveBeenCalled(); + expect(runtimeError).not.toHaveBeenCalled(); + + fetchSpy.mockRestore(); + }, + STICKER_TEST_TIMEOUT_MS, + ); +}); + describe("telegram text fragments", () => { beforeEach(() => { vi.useFakeTimers(); diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index c2489300c..f950417c7 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -21,7 +21,8 @@ import { loadWebMedia } from "../../web/media.js"; import { buildInlineKeyboard } from "../send.js"; import { resolveTelegramVoiceSend } from "../voice.js"; import { buildTelegramThreadParams, resolveTelegramReplyId } from "./helpers.js"; -import type { TelegramContext } from "./types.js"; +import type { StickerMetadata, TelegramContext } from "./types.js"; +import { getCachedSticker } from "../sticker-cache.js"; const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i; const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/; @@ -261,8 +262,79 @@ export async function resolveMedia( maxBytes: number, token: string, proxyFetch?: typeof fetch, -): Promise<{ path: string; contentType?: string; placeholder: string } | null> { +): Promise<{ + path: string; + contentType?: string; + placeholder: string; + stickerMetadata?: StickerMetadata; +} | null> { const msg = ctx.message; + + // Handle stickers separately - only static stickers (WEBP) are supported + if (msg.sticker) { + const sticker = msg.sticker; + // Skip animated (TGS) and video (WEBM) stickers - only static WEBP supported + if (sticker.is_animated || sticker.is_video) { + logVerbose("telegram: skipping animated/video sticker (only static stickers supported)"); + return null; + } + if (!sticker.file_id) return null; + + try { + const file = await ctx.getFile(); + if (!file.file_path) { + logVerbose("telegram: getFile returned no file_path for sticker"); + return null; + } + const fetchImpl = proxyFetch ?? globalThis.fetch; + if (!fetchImpl) { + logVerbose("telegram: fetch not available for sticker download"); + return null; + } + const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`; + const fetched = await fetchRemoteMedia({ + url, + fetchImpl, + filePathHint: file.file_path, + }); + const saved = await saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", maxBytes); + + // Check sticker cache for existing description + const cached = sticker.file_unique_id ? getCachedSticker(sticker.file_unique_id) : null; + if (cached) { + logVerbose(`telegram: sticker cache hit for ${sticker.file_unique_id}`); + return { + path: saved.path, + contentType: saved.contentType, + placeholder: "", + stickerMetadata: { + emoji: cached.emoji, + setName: cached.setName, + fileId: cached.fileId, + fileUniqueId: sticker.file_unique_id, + cachedDescription: cached.description, + }, + }; + } + + // Cache miss - return metadata for vision processing + return { + path: saved.path, + contentType: saved.contentType, + placeholder: "", + stickerMetadata: { + emoji: sticker.emoji ?? undefined, + setName: sticker.set_name ?? undefined, + fileId: sticker.file_id, + fileUniqueId: sticker.file_unique_id, + }, + }; + } catch (err) { + logVerbose(`telegram: failed to process sticker: ${err}`); + return null; + } + } + const m = msg.photo?.[msg.photo.length - 1] ?? msg.video ?? msg.document ?? msg.audio ?? msg.voice; if (!m?.file_id) return null; diff --git a/src/telegram/bot/types.ts b/src/telegram/bot/types.ts index 1174503b4..3e106b885 100644 --- a/src/telegram/bot/types.ts +++ b/src/telegram/bot/types.ts @@ -67,3 +67,17 @@ export interface TelegramVenue { google_place_id?: string; google_place_type?: string; } + +/** Telegram sticker metadata for context enrichment. */ +export interface StickerMetadata { + /** Emoji associated with the sticker. */ + emoji?: string; + /** Name of the sticker set the sticker belongs to. */ + setName?: string; + /** Telegram file_id for sending the sticker back. */ + fileId?: string; + /** Stable file_unique_id for cache deduplication. */ + fileUniqueId?: string; + /** Cached description from previous vision processing (skip re-processing if present). */ + cachedDescription?: string; +} diff --git a/src/telegram/send.returns-undefined-empty-input.test.ts b/src/telegram/send.returns-undefined-empty-input.test.ts index d086fe2a3..b6b497789 100644 --- a/src/telegram/send.returns-undefined-empty-input.test.ts +++ b/src/telegram/send.returns-undefined-empty-input.test.ts @@ -4,6 +4,7 @@ const { botApi, botCtorSpy } = vi.hoisted(() => ({ botApi: { sendMessage: vi.fn(), setMessageReaction: vi.fn(), + sendSticker: vi.fn(), }, botCtorSpy: vi.fn(), })); @@ -43,7 +44,7 @@ vi.mock("../config/config.js", async (importOriginal) => { }; }); -import { buildInlineKeyboard, sendMessageTelegram } from "./send.js"; +import { buildInlineKeyboard, sendMessageTelegram, sendStickerTelegram } from "./send.js"; describe("buildInlineKeyboard", () => { it("returns undefined for empty input", () => { @@ -566,3 +567,183 @@ describe("sendMessageTelegram", () => { }); }); }); + +describe("sendStickerTelegram", () => { + beforeEach(() => { + loadConfig.mockReturnValue({}); + botApi.sendSticker.mockReset(); + botCtorSpy.mockReset(); + }); + + it("sends a sticker by file_id", async () => { + const chatId = "123"; + const fileId = "CAACAgIAAxkBAAI...sticker_file_id"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 100, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + const res = await sendStickerTelegram(chatId, fileId, { + token: "tok", + api, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, fileId, undefined); + expect(res.messageId).toBe("100"); + expect(res.chatId).toBe(chatId); + }); + + it("throws error when fileId is empty", async () => { + await expect(sendStickerTelegram("123", "", { token: "tok" })).rejects.toThrow( + /file_id is required/i, + ); + }); + + it("throws error when fileId is whitespace only", async () => { + await expect(sendStickerTelegram("123", " ", { token: "tok" })).rejects.toThrow( + /file_id is required/i, + ); + }); + + it("includes message_thread_id for forum topic messages", async () => { + const chatId = "-1001234567890"; + const fileId = "CAACAgIAAxkBAAI...sticker_file_id"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 101, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram(chatId, fileId, { + token: "tok", + api, + messageThreadId: 271, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, fileId, { + message_thread_id: 271, + }); + }); + + it("includes reply_to_message_id for threaded replies", async () => { + const chatId = "123"; + const fileId = "CAACAgIAAxkBAAI...sticker_file_id"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 102, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram(chatId, fileId, { + token: "tok", + api, + replyToMessageId: 500, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, fileId, { + reply_to_message_id: 500, + }); + }); + + it("includes both thread and reply params for forum topic replies", async () => { + const chatId = "-1001234567890"; + const fileId = "CAACAgIAAxkBAAI...sticker_file_id"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 103, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram(chatId, fileId, { + token: "tok", + api, + messageThreadId: 271, + replyToMessageId: 500, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, fileId, { + message_thread_id: 271, + reply_to_message_id: 500, + }); + }); + + it("normalizes chat ids with internal prefixes", async () => { + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 104, + chat: { id: "123" }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram("telegram:123", "fileId123", { + token: "tok", + api, + }); + + expect(sendSticker).toHaveBeenCalledWith("123", "fileId123", undefined); + }); + + it("parses message_thread_id from recipient string (telegram:group:...:topic:...)", async () => { + const chatId = "-1001234567890"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 105, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram(`telegram:group:${chatId}:topic:271`, "fileId123", { + token: "tok", + api, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, "fileId123", { + message_thread_id: 271, + }); + }); + + it("wraps chat-not-found with actionable context", async () => { + const chatId = "123"; + const err = new Error("400: Bad Request: chat not found"); + const sendSticker = vi.fn().mockRejectedValue(err); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await expect(sendStickerTelegram(chatId, "fileId123", { token: "tok", api })).rejects.toThrow( + /chat not found/i, + ); + await expect(sendStickerTelegram(chatId, "fileId123", { token: "tok", api })).rejects.toThrow( + /chat_id=123/, + ); + }); + + it("trims whitespace from fileId", async () => { + const chatId = "123"; + const sendSticker = vi.fn().mockResolvedValue({ + message_id: 106, + chat: { id: chatId }, + }); + const api = { sendSticker } as unknown as { + sendSticker: typeof sendSticker; + }; + + await sendStickerTelegram(chatId, " fileId123 ", { + token: "tok", + api, + }); + + expect(sendSticker).toHaveBeenCalledWith(chatId, "fileId123", undefined); + }); +}); diff --git a/src/telegram/send.ts b/src/telegram/send.ts index 92cd3ddc1..7dd79dd1f 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -619,3 +619,96 @@ function inferFilename(kind: ReturnType) { return "file.bin"; } } + +type TelegramStickerOpts = { + token?: string; + accountId?: string; + verbose?: boolean; + api?: Bot["api"]; + retry?: RetryConfig; + /** Message ID to reply to (for threading) */ + replyToMessageId?: number; + /** Forum topic thread ID (for forum supergroups) */ + messageThreadId?: number; +}; + +/** + * Send a sticker to a Telegram chat by file_id. + * @param to - Chat ID or username (e.g., "123456789" or "@username") + * @param fileId - Telegram file_id of the sticker to send + * @param opts - Optional configuration + */ +export async function sendStickerTelegram( + to: string, + fileId: string, + opts: TelegramStickerOpts = {}, +): Promise { + if (!fileId?.trim()) { + throw new Error("Telegram sticker file_id is required"); + } + + const cfg = loadConfig(); + const account = resolveTelegramAccount({ + cfg, + accountId: opts.accountId, + }); + const token = resolveToken(opts.token, account); + const target = parseTelegramTarget(to); + const chatId = normalizeChatId(target.chatId); + const client = resolveTelegramClientOptions(account); + const api = opts.api ?? new Bot(token, client ? { client } : undefined).api; + + const messageThreadId = + opts.messageThreadId != null ? opts.messageThreadId : target.messageThreadId; + const threadIdParams = buildTelegramThreadParams(messageThreadId); + const threadParams: Record = threadIdParams ? { ...threadIdParams } : {}; + if (opts.replyToMessageId != null) { + threadParams.reply_to_message_id = Math.trunc(opts.replyToMessageId); + } + const hasThreadParams = Object.keys(threadParams).length > 0; + + const request = createTelegramRetryRunner({ + retry: opts.retry, + configRetry: account.config.retry, + verbose: opts.verbose, + }); + const logHttpError = createTelegramHttpLogger(cfg); + const requestWithDiag = (fn: () => Promise, label?: string) => + request(fn, label).catch((err) => { + logHttpError(label ?? "request", err); + throw err; + }); + + const wrapChatNotFound = (err: unknown) => { + if (!/400: Bad Request: chat not found/i.test(formatErrorMessage(err))) return err; + return new Error( + [ + `Telegram send failed: chat not found (chat_id=${chatId}).`, + "Likely: bot not started in DM, bot removed from group/channel, group migrated (new -100… id), or wrong bot token.", + `Input was: ${JSON.stringify(to)}.`, + ].join(" "), + ); + }; + + const stickerParams = hasThreadParams ? threadParams : undefined; + + const result = await requestWithDiag( + () => api.sendSticker(chatId, fileId.trim(), stickerParams), + "sticker", + ).catch((err) => { + throw wrapChatNotFound(err); + }); + + const messageId = String(result?.message_id ?? "unknown"); + const resolvedChatId = String(result?.chat?.id ?? chatId); + if (result?.message_id) { + recordSentMessage(chatId, result.message_id); + } + recordChannelActivity({ + channel: "telegram", + accountId: account.accountId, + direction: "outbound", + }); + + return { messageId, chatId: resolvedChatId }; +} diff --git a/src/telegram/sticker-cache.test.ts b/src/telegram/sticker-cache.test.ts new file mode 100644 index 000000000..7fa3b6af2 --- /dev/null +++ b/src/telegram/sticker-cache.test.ts @@ -0,0 +1,257 @@ +import fs from "node:fs"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + cacheSticker, + getAllCachedStickers, + getCachedSticker, + getCacheStats, + searchStickers, +} from "./sticker-cache.js"; + +// Mock the state directory to use a temp location +vi.mock("../config/paths.js", () => ({ + STATE_DIR_CLAWDBOT: "/tmp/clawdbot-test-sticker-cache", +})); + +const TEST_CACHE_DIR = "/tmp/clawdbot-test-sticker-cache/telegram"; +const TEST_CACHE_FILE = path.join(TEST_CACHE_DIR, "sticker-cache.json"); + +describe("sticker-cache", () => { + beforeEach(() => { + // Clean up before each test + if (fs.existsSync(TEST_CACHE_FILE)) { + fs.unlinkSync(TEST_CACHE_FILE); + } + }); + + afterEach(() => { + // Clean up after each test + if (fs.existsSync(TEST_CACHE_FILE)) { + fs.unlinkSync(TEST_CACHE_FILE); + } + }); + + describe("getCachedSticker", () => { + it("returns null for unknown ID", () => { + const result = getCachedSticker("unknown-id"); + expect(result).toBeNull(); + }); + + it("returns cached sticker after cacheSticker", () => { + const sticker = { + fileId: "file123", + fileUniqueId: "unique123", + emoji: "🎉", + setName: "TestPack", + description: "A party popper emoji sticker", + cachedAt: "2026-01-26T12:00:00.000Z", + }; + + cacheSticker(sticker); + const result = getCachedSticker("unique123"); + + expect(result).toEqual(sticker); + }); + + it("returns null after cache is cleared", () => { + const sticker = { + fileId: "file123", + fileUniqueId: "unique123", + description: "test", + cachedAt: "2026-01-26T12:00:00.000Z", + }; + + cacheSticker(sticker); + expect(getCachedSticker("unique123")).not.toBeNull(); + + // Manually clear the cache file + fs.unlinkSync(TEST_CACHE_FILE); + + expect(getCachedSticker("unique123")).toBeNull(); + }); + }); + + describe("cacheSticker", () => { + it("adds entry to cache", () => { + const sticker = { + fileId: "file456", + fileUniqueId: "unique456", + description: "A cute fox waving", + cachedAt: "2026-01-26T12:00:00.000Z", + }; + + cacheSticker(sticker); + + const all = getAllCachedStickers(); + expect(all).toHaveLength(1); + expect(all[0]).toEqual(sticker); + }); + + it("updates existing entry", () => { + const original = { + fileId: "file789", + fileUniqueId: "unique789", + description: "Original description", + cachedAt: "2026-01-26T12:00:00.000Z", + }; + const updated = { + fileId: "file789-new", + fileUniqueId: "unique789", + description: "Updated description", + cachedAt: "2026-01-26T13:00:00.000Z", + }; + + cacheSticker(original); + cacheSticker(updated); + + const result = getCachedSticker("unique789"); + expect(result?.description).toBe("Updated description"); + expect(result?.fileId).toBe("file789-new"); + }); + }); + + describe("searchStickers", () => { + beforeEach(() => { + // Seed cache with test stickers + cacheSticker({ + fileId: "fox1", + fileUniqueId: "fox-unique-1", + emoji: "🦊", + setName: "CuteFoxes", + description: "A cute orange fox waving hello", + cachedAt: "2026-01-26T10:00:00.000Z", + }); + cacheSticker({ + fileId: "fox2", + fileUniqueId: "fox-unique-2", + emoji: "🦊", + setName: "CuteFoxes", + description: "A fox sleeping peacefully", + cachedAt: "2026-01-26T11:00:00.000Z", + }); + cacheSticker({ + fileId: "cat1", + fileUniqueId: "cat-unique-1", + emoji: "🐱", + setName: "FunnyCats", + description: "A cat sitting on a keyboard", + cachedAt: "2026-01-26T12:00:00.000Z", + }); + cacheSticker({ + fileId: "dog1", + fileUniqueId: "dog-unique-1", + emoji: "🐶", + setName: "GoodBoys", + description: "A golden retriever playing fetch", + cachedAt: "2026-01-26T13:00:00.000Z", + }); + }); + + it("finds stickers by description substring", () => { + const results = searchStickers("fox"); + expect(results).toHaveLength(2); + expect(results.every((s) => s.description.toLowerCase().includes("fox"))).toBe(true); + }); + + it("finds stickers by emoji", () => { + const results = searchStickers("🦊"); + expect(results).toHaveLength(2); + expect(results.every((s) => s.emoji === "🦊")).toBe(true); + }); + + it("finds stickers by set name", () => { + const results = searchStickers("CuteFoxes"); + expect(results).toHaveLength(2); + expect(results.every((s) => s.setName === "CuteFoxes")).toBe(true); + }); + + it("respects limit parameter", () => { + const results = searchStickers("fox", 1); + expect(results).toHaveLength(1); + }); + + it("ranks exact matches higher", () => { + // "waving" appears in "fox waving hello" - should be ranked first + const results = searchStickers("waving"); + expect(results).toHaveLength(1); + expect(results[0]?.fileUniqueId).toBe("fox-unique-1"); + }); + + it("returns empty array for no matches", () => { + const results = searchStickers("elephant"); + expect(results).toHaveLength(0); + }); + + it("is case insensitive", () => { + const results = searchStickers("FOX"); + expect(results).toHaveLength(2); + }); + + it("matches multiple words", () => { + const results = searchStickers("cat keyboard"); + expect(results).toHaveLength(1); + expect(results[0]?.fileUniqueId).toBe("cat-unique-1"); + }); + }); + + describe("getAllCachedStickers", () => { + it("returns empty array when cache is empty", () => { + const result = getAllCachedStickers(); + expect(result).toEqual([]); + }); + + it("returns all cached stickers", () => { + cacheSticker({ + fileId: "a", + fileUniqueId: "a-unique", + description: "Sticker A", + cachedAt: "2026-01-26T10:00:00.000Z", + }); + cacheSticker({ + fileId: "b", + fileUniqueId: "b-unique", + description: "Sticker B", + cachedAt: "2026-01-26T11:00:00.000Z", + }); + + const result = getAllCachedStickers(); + expect(result).toHaveLength(2); + }); + }); + + describe("getCacheStats", () => { + it("returns count 0 when cache is empty", () => { + const stats = getCacheStats(); + expect(stats.count).toBe(0); + expect(stats.oldestAt).toBeUndefined(); + expect(stats.newestAt).toBeUndefined(); + }); + + it("returns correct stats with cached stickers", () => { + cacheSticker({ + fileId: "old", + fileUniqueId: "old-unique", + description: "Old sticker", + cachedAt: "2026-01-20T10:00:00.000Z", + }); + cacheSticker({ + fileId: "new", + fileUniqueId: "new-unique", + description: "New sticker", + cachedAt: "2026-01-26T10:00:00.000Z", + }); + cacheSticker({ + fileId: "mid", + fileUniqueId: "mid-unique", + description: "Middle sticker", + cachedAt: "2026-01-23T10:00:00.000Z", + }); + + const stats = getCacheStats(); + expect(stats.count).toBe(3); + expect(stats.oldestAt).toBe("2026-01-20T10:00:00.000Z"); + expect(stats.newestAt).toBe("2026-01-26T10:00:00.000Z"); + }); + }); +}); diff --git a/src/telegram/sticker-cache.ts b/src/telegram/sticker-cache.ts new file mode 100644 index 000000000..2c55563b7 --- /dev/null +++ b/src/telegram/sticker-cache.ts @@ -0,0 +1,201 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import type { ClawdbotConfig } from "../config/config.js"; +import { STATE_DIR_CLAWDBOT } from "../config/paths.js"; +import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; +import { logVerbose } from "../globals.js"; +import { resolveApiKeyForProvider } from "../agents/model-auth.js"; + +const CACHE_FILE = path.join(STATE_DIR_CLAWDBOT, "telegram", "sticker-cache.json"); +const CACHE_VERSION = 1; + +export interface CachedSticker { + fileId: string; + fileUniqueId: string; + emoji?: string; + setName?: string; + description: string; + cachedAt: string; + receivedFrom?: string; +} + +interface StickerCache { + version: number; + stickers: Record; +} + +function loadCache(): StickerCache { + const data = loadJsonFile(CACHE_FILE); + if (!data || typeof data !== "object") { + return { version: CACHE_VERSION, stickers: {} }; + } + const cache = data as StickerCache; + if (cache.version !== CACHE_VERSION) { + // Future: handle migration if needed + return { version: CACHE_VERSION, stickers: {} }; + } + return cache; +} + +function saveCache(cache: StickerCache): void { + saveJsonFile(CACHE_FILE, cache); +} + +/** + * Get a cached sticker by its unique ID. + */ +export function getCachedSticker(fileUniqueId: string): CachedSticker | null { + const cache = loadCache(); + return cache.stickers[fileUniqueId] ?? null; +} + +/** + * Add or update a sticker in the cache. + */ +export function cacheSticker(sticker: CachedSticker): void { + const cache = loadCache(); + cache.stickers[sticker.fileUniqueId] = sticker; + saveCache(cache); +} + +/** + * Search cached stickers by text query (fuzzy match on description + emoji + setName). + */ +export function searchStickers(query: string, limit = 10): CachedSticker[] { + const cache = loadCache(); + const queryLower = query.toLowerCase(); + const results: Array<{ sticker: CachedSticker; score: number }> = []; + + for (const sticker of Object.values(cache.stickers)) { + let score = 0; + const descLower = sticker.description.toLowerCase(); + + // Exact substring match in description + if (descLower.includes(queryLower)) { + score += 10; + } + + // Word-level matching + const queryWords = queryLower.split(/\s+/).filter(Boolean); + const descWords = descLower.split(/\s+/); + for (const qWord of queryWords) { + if (descWords.some((dWord) => dWord.includes(qWord))) { + score += 5; + } + } + + // Emoji match + if (sticker.emoji && query.includes(sticker.emoji)) { + score += 8; + } + + // Set name match + if (sticker.setName?.toLowerCase().includes(queryLower)) { + score += 3; + } + + if (score > 0) { + results.push({ sticker, score }); + } + } + + return results + .sort((a, b) => b.score - a.score) + .slice(0, limit) + .map((r) => r.sticker); +} + +/** + * Get all cached stickers (for debugging/listing). + */ +export function getAllCachedStickers(): CachedSticker[] { + const cache = loadCache(); + return Object.values(cache.stickers); +} + +/** + * Get cache statistics. + */ +export function getCacheStats(): { count: number; oldestAt?: string; newestAt?: string } { + const cache = loadCache(); + const stickers = Object.values(cache.stickers); + if (stickers.length === 0) { + return { count: 0 }; + } + const sorted = [...stickers].sort( + (a, b) => new Date(a.cachedAt).getTime() - new Date(b.cachedAt).getTime(), + ); + return { + count: stickers.length, + oldestAt: sorted[0]?.cachedAt, + newestAt: sorted[sorted.length - 1]?.cachedAt, + }; +} + +const STICKER_DESCRIPTION_PROMPT = + "Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective."; + +const VISION_PROVIDERS = ["anthropic", "openai", "google", "minimax"] as const; +const DEFAULT_VISION_MODELS: Record = { + anthropic: "claude-sonnet-4-20250514", + openai: "gpt-4o-mini", + google: "gemini-2.0-flash", + minimax: "MiniMax-VL-01", +}; + +export interface DescribeStickerParams { + imagePath: string; + cfg: ClawdbotConfig; + agentDir?: string; +} + +/** + * Describe a sticker image using vision API. + * Auto-detects an available vision provider based on configured API keys. + * Returns null if no vision provider is available. + */ +export async function describeStickerImage(params: DescribeStickerParams): Promise { + const { imagePath, cfg, agentDir } = params; + + // Find a vision provider with available API key + let provider: string | null = null; + for (const p of VISION_PROVIDERS) { + try { + await resolveApiKeyForProvider({ provider: p, cfg, agentDir }); + provider = p; + break; + } catch { + // No key for this provider, try next + } + } + + if (!provider) { + logVerbose("telegram: no vision provider available for sticker description"); + return null; + } + + const model = DEFAULT_VISION_MODELS[provider]; + logVerbose(`telegram: describing sticker with ${provider}/${model}`); + + try { + const buffer = await fs.readFile(imagePath); + // Dynamic import to avoid circular dependency + const { describeImageWithModel } = await import("../media-understanding/providers/image.js"); + const result = await describeImageWithModel({ + buffer, + fileName: "sticker.webp", + mime: "image/webp", + prompt: STICKER_DESCRIPTION_PROMPT, + cfg, + agentDir: agentDir ?? "", + provider, + model, + maxTokens: 150, + timeoutMs: 30000, + }); + return result.text; + } catch (err) { + logVerbose(`telegram: failed to describe sticker: ${err}`); + return null; + } +} From 34fea720f8bb2da6b87825c462e48616ab67f194 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 27 Jan 2026 12:47:04 +0530 Subject: [PATCH 002/101] fix(telegram): improve sticker vision + cache (#2548) (thanks @longjos) --- CHANGELOG.md | 1 + docs/channels/telegram.md | 18 ++-- src/agents/tools/telegram-actions.test.ts | 40 ++++++++ src/agents/tools/telegram-actions.ts | 4 +- src/channels/plugins/actions/telegram.test.ts | 7 ++ src/channels/plugins/actions/telegram.ts | 2 +- src/media-understanding/runner.ts | 33 +++++++ src/telegram/bot-message-dispatch.ts | 1 + ...s-media-file-path-no-file-download.test.ts | 97 +++++++++++++++++++ src/telegram/bot/delivery.ts | 22 ++++- src/telegram/sticker-cache.ts | 52 +++++----- 11 files changed, 240 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6bb640bc..442dd52a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Status: unreleased. - Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc. - Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma. - Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21. +- Telegram: add sticker receive/send with vision caching. (#2548) Thanks @longjos. - Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918. - Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999. - macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal. diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index 2d8c472bd..56920f131 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -395,10 +395,13 @@ When a user sends a sticker, Clawdbot handles it based on the sticker type: - **Animated stickers (TGS):** Skipped (Lottie format not supported for processing). - **Video stickers (WEBM):** Skipped (video format not supported for processing). -Template context fields available when receiving stickers: -- `StickerEmoji` — the emoji associated with the sticker -- `StickerSetName` — the name of the sticker set -- `StickerFileId` — the Telegram file ID (used for sending the same sticker back) +Template context field available when receiving stickers: +- `Sticker` — object with: + - `emoji` — emoji associated with the sticker + - `setName` — name of the sticker set + - `fileId` — Telegram file ID (send the same sticker back) + - `fileUniqueId` — stable ID for cache lookup + - `cachedDescription` — cached vision description when available ### Sticker cache @@ -416,10 +419,11 @@ Stickers are processed through the AI's vision capabilities to generate descript ```json { "fileId": "CAACAgIAAxkBAAI...", + "fileUniqueId": "AgADBAADb6cxG2Y", "emoji": "👋", "setName": "CoolCats", "description": "A cartoon cat waving enthusiastically", - "addedAt": "2026-01-15T10:30:00.000Z" + "cachedAt": "2026-01-15T10:30:00.000Z" } ``` @@ -458,7 +462,7 @@ The agent can send and search stickers using the `sticker` and `sticker-search` ``` Parameters: -- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `StickerFileId` when receiving a sticker, or from a `sticker-search` result. +- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result. - `replyTo` (optional) — message ID to reply to. - `threadId` (optional) — message thread ID for forum topics. @@ -543,7 +547,7 @@ Outbound Telegram API calls retry on transient network/429 errors with exponenti - Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`). - Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`). - Reaction removal semantics: see [/tools/reactions](/tools/reactions). -- Tool gating: `channels.telegram.actions.reactions`, `channels.telegram.actions.sendMessage`, `channels.telegram.actions.deleteMessage` (default: enabled). +- Tool gating: `channels.telegram.actions.reactions`, `channels.telegram.actions.sendMessage`, `channels.telegram.actions.deleteMessage` (default: enabled), and `channels.telegram.actions.sticker` (default: disabled). ## Reaction notifications diff --git a/src/agents/tools/telegram-actions.test.ts b/src/agents/tools/telegram-actions.test.ts index 5c0629e38..db276849b 100644 --- a/src/agents/tools/telegram-actions.test.ts +++ b/src/agents/tools/telegram-actions.test.ts @@ -8,12 +8,17 @@ const sendMessageTelegram = vi.fn(async () => ({ messageId: "789", chatId: "123", })); +const sendStickerTelegram = vi.fn(async () => ({ + messageId: "456", + chatId: "123", +})); const deleteMessageTelegram = vi.fn(async () => ({ ok: true })); const originalToken = process.env.TELEGRAM_BOT_TOKEN; vi.mock("../../telegram/send.js", () => ({ reactMessageTelegram: (...args: unknown[]) => reactMessageTelegram(...args), sendMessageTelegram: (...args: unknown[]) => sendMessageTelegram(...args), + sendStickerTelegram: (...args: unknown[]) => sendStickerTelegram(...args), deleteMessageTelegram: (...args: unknown[]) => deleteMessageTelegram(...args), })); @@ -21,6 +26,7 @@ describe("handleTelegramAction", () => { beforeEach(() => { reactMessageTelegram.mockClear(); sendMessageTelegram.mockClear(); + sendStickerTelegram.mockClear(); deleteMessageTelegram.mockClear(); process.env.TELEGRAM_BOT_TOKEN = "tok"; }); @@ -96,6 +102,40 @@ describe("handleTelegramAction", () => { ); }); + it("rejects sticker actions when disabled by default", async () => { + const cfg = { channels: { telegram: { botToken: "tok" } } } as ClawdbotConfig; + await expect( + handleTelegramAction( + { + action: "sendSticker", + to: "123", + fileId: "sticker", + }, + cfg, + ), + ).rejects.toThrow(/sticker actions are disabled/i); + expect(sendStickerTelegram).not.toHaveBeenCalled(); + }); + + it("sends stickers when enabled", async () => { + const cfg = { + channels: { telegram: { botToken: "tok", actions: { sticker: true } } }, + } as ClawdbotConfig; + await handleTelegramAction( + { + action: "sendSticker", + to: "123", + fileId: "sticker", + }, + cfg, + ); + expect(sendStickerTelegram).toHaveBeenCalledWith( + "123", + "sticker", + expect.objectContaining({ token: "tok" }), + ); + }); + it("removes reactions when remove flag set", async () => { const cfg = { channels: { telegram: { botToken: "tok", reactionLevel: "extensive" } }, diff --git a/src/agents/tools/telegram-actions.ts b/src/agents/tools/telegram-actions.ts index 40a97d874..d2a4e4b93 100644 --- a/src/agents/tools/telegram-actions.ts +++ b/src/agents/tools/telegram-actions.ts @@ -258,7 +258,7 @@ export async function handleTelegramAction( } if (action === "sendSticker") { - if (!isActionEnabled("sticker")) { + if (!isActionEnabled("sticker", false)) { throw new Error( "Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true.", ); @@ -291,7 +291,7 @@ export async function handleTelegramAction( } if (action === "searchSticker") { - if (!isActionEnabled("sticker")) { + if (!isActionEnabled("sticker", false)) { throw new Error( "Telegram sticker actions are disabled. Set channels.telegram.actions.sticker to true.", ); diff --git a/src/channels/plugins/actions/telegram.test.ts b/src/channels/plugins/actions/telegram.test.ts index b2673134d..e61a73908 100644 --- a/src/channels/plugins/actions/telegram.test.ts +++ b/src/channels/plugins/actions/telegram.test.ts @@ -10,6 +10,13 @@ vi.mock("../../../agents/tools/telegram-actions.js", () => ({ })); describe("telegramMessageActions", () => { + it("excludes sticker actions when not enabled", () => { + const cfg = { channels: { telegram: { botToken: "tok" } } } as ClawdbotConfig; + const actions = telegramMessageActions.listActions({ cfg }); + expect(actions).not.toContain("sticker"); + expect(actions).not.toContain("sticker-search"); + }); + it("allows media-only sends and passes asVoice", async () => { handleTelegramAction.mockClear(); const cfg = { channels: { telegram: { botToken: "tok" } } } as ClawdbotConfig; diff --git a/src/channels/plugins/actions/telegram.ts b/src/channels/plugins/actions/telegram.ts index f8c7dc0fb..2acfaf9f1 100644 --- a/src/channels/plugins/actions/telegram.ts +++ b/src/channels/plugins/actions/telegram.ts @@ -46,7 +46,7 @@ export const telegramMessageActions: ChannelMessageActionAdapter = { if (gate("reactions")) actions.add("react"); if (gate("deleteMessage")) actions.add("delete"); if (gate("editMessage")) actions.add("edit"); - if (gate("sticker")) { + if (gate("sticker", false)) { actions.add("sticker"); actions.add("sticker-search"); } diff --git a/src/media-understanding/runner.ts b/src/media-understanding/runner.ts index 9e92d67c0..36636c542 100644 --- a/src/media-understanding/runner.ts +++ b/src/media-understanding/runner.ts @@ -412,6 +412,39 @@ async function resolveAutoEntries(params: { return []; } +export async function resolveAutoImageModel(params: { + cfg: ClawdbotConfig; + agentDir?: string; + activeModel?: ActiveMediaModel; +}): Promise { + const providerRegistry = buildProviderRegistry(); + const toActive = (entry: MediaUnderstandingModelConfig | null): ActiveMediaModel | null => { + if (!entry || entry.type === "cli") return null; + const provider = entry.provider; + if (!provider) return null; + const model = entry.model ?? DEFAULT_IMAGE_MODELS[provider]; + if (!model) return null; + return { provider, model }; + }; + const activeEntry = await resolveActiveModelEntry({ + cfg: params.cfg, + agentDir: params.agentDir, + providerRegistry, + capability: "image", + activeModel: params.activeModel, + }); + const resolvedActive = toActive(activeEntry); + if (resolvedActive) return resolvedActive; + const keyEntry = await resolveKeyEntry({ + cfg: params.cfg, + agentDir: params.agentDir, + providerRegistry, + capability: "image", + activeModel: params.activeModel, + }); + return toActive(keyEntry); +} + async function resolveActiveModelEntry(params: { cfg: ClawdbotConfig; agentDir?: string; diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index e24796d6c..a3e9c3faa 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -139,6 +139,7 @@ export const dispatchTelegramMessage = async ({ imagePath: ctxPayload.MediaPath, cfg, agentDir, + agentId: route.agentId, }); if (description) { // Format the description with sticker context diff --git a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts index dd75e6798..165488426 100644 --- a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts +++ b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts @@ -7,6 +7,9 @@ const middlewareUseSpy = vi.fn(); const onSpy = vi.fn(); const stopSpy = vi.fn(); const sendChatActionSpy = vi.fn(); +const cacheStickerSpy = vi.fn(); +const getCachedStickerSpy = vi.fn(); +const describeStickerImageSpy = vi.fn(); type ApiStub = { config: { use: (arg: unknown) => void }; @@ -79,6 +82,12 @@ vi.mock("../config/sessions.js", async (importOriginal) => { }; }); +vi.mock("./sticker-cache.js", () => ({ + cacheSticker: (...args: unknown[]) => cacheStickerSpy(...args), + getCachedSticker: (...args: unknown[]) => getCachedStickerSpy(...args), + describeStickerImage: (...args: unknown[]) => describeStickerImageSpy(...args), +})); + vi.mock("./pairing-store.js", () => ({ readTelegramAllowFromStore: vi.fn(async () => [] as string[]), upsertTelegramPairingRequest: vi.fn(async () => ({ @@ -408,6 +417,12 @@ describe("telegram media groups", () => { describe("telegram stickers", () => { const STICKER_TEST_TIMEOUT_MS = process.platform === "win32" ? 30_000 : 20_000; + beforeEach(() => { + cacheStickerSpy.mockReset(); + getCachedStickerSpy.mockReset(); + describeStickerImageSpy.mockReset(); + }); + it( "downloads static sticker (WEBP) and includes sticker metadata", async () => { @@ -481,6 +496,88 @@ describe("telegram stickers", () => { STICKER_TEST_TIMEOUT_MS, ); + it( + "refreshes cached sticker metadata on cache hit", + async () => { + const { createTelegramBot } = await import("./bot.js"); + const replyModule = await import("../auto-reply/reply.js"); + const replySpy = replyModule.__replySpy as unknown as ReturnType; + + onSpy.mockReset(); + replySpy.mockReset(); + sendChatActionSpy.mockReset(); + + getCachedStickerSpy.mockReturnValue({ + fileId: "old_file_id", + fileUniqueId: "sticker_unique_456", + emoji: "😴", + setName: "OldSet", + description: "Cached description", + cachedAt: "2026-01-20T10:00:00.000Z", + }); + + const runtimeError = vi.fn(); + createTelegramBot({ + token: "tok", + runtime: { + log: vi.fn(), + error: runtimeError, + exit: () => { + throw new Error("exit"); + }, + }, + }); + const handler = onSpy.mock.calls.find((call) => call[0] === "message")?.[1] as ( + ctx: Record, + ) => Promise; + expect(handler).toBeDefined(); + + const fetchSpy = vi.spyOn(globalThis, "fetch" as never).mockResolvedValueOnce({ + ok: true, + status: 200, + statusText: "OK", + headers: { get: () => "image/webp" }, + arrayBuffer: async () => new Uint8Array([0x52, 0x49, 0x46, 0x46]).buffer, + } as Response); + + await handler({ + message: { + message_id: 103, + chat: { id: 1234, type: "private" }, + sticker: { + file_id: "new_file_id", + file_unique_id: "sticker_unique_456", + type: "regular", + width: 512, + height: 512, + is_animated: false, + is_video: false, + emoji: "🔥", + set_name: "NewSet", + }, + date: 1736380800, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ file_path: "stickers/sticker.webp" }), + }); + + expect(runtimeError).not.toHaveBeenCalled(); + expect(cacheStickerSpy).toHaveBeenCalledWith( + expect.objectContaining({ + fileId: "new_file_id", + emoji: "🔥", + setName: "NewSet", + }), + ); + const payload = replySpy.mock.calls[0][0]; + expect(payload.Sticker?.fileId).toBe("new_file_id"); + expect(payload.Sticker?.cachedDescription).toBe("Cached description"); + + fetchSpy.mockRestore(); + }, + STICKER_TEST_TIMEOUT_MS, + ); + it( "skips animated stickers (TGS format)", async () => { diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index f950417c7..779c0c026 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -22,7 +22,7 @@ import { buildInlineKeyboard } from "../send.js"; import { resolveTelegramVoiceSend } from "../voice.js"; import { buildTelegramThreadParams, resolveTelegramReplyId } from "./helpers.js"; import type { StickerMetadata, TelegramContext } from "./types.js"; -import { getCachedSticker } from "../sticker-cache.js"; +import { cacheSticker, getCachedSticker } from "../sticker-cache.js"; const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i; const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/; @@ -303,14 +303,26 @@ export async function resolveMedia( const cached = sticker.file_unique_id ? getCachedSticker(sticker.file_unique_id) : null; if (cached) { logVerbose(`telegram: sticker cache hit for ${sticker.file_unique_id}`); + const fileId = sticker.file_id ?? cached.fileId; + const emoji = sticker.emoji ?? cached.emoji; + const setName = sticker.set_name ?? cached.setName; + if (fileId !== cached.fileId || emoji !== cached.emoji || setName !== cached.setName) { + // Refresh cached sticker metadata on hits so sends/searches use latest file_id. + cacheSticker({ + ...cached, + fileId, + emoji, + setName, + }); + } return { path: saved.path, contentType: saved.contentType, placeholder: "", stickerMetadata: { - emoji: cached.emoji, - setName: cached.setName, - fileId: cached.fileId, + emoji, + setName, + fileId, fileUniqueId: sticker.file_unique_id, cachedDescription: cached.description, }, @@ -330,7 +342,7 @@ export async function resolveMedia( }, }; } catch (err) { - logVerbose(`telegram: failed to process sticker: ${err}`); + logVerbose(`telegram: failed to process sticker: ${String(err)}`); return null; } } diff --git a/src/telegram/sticker-cache.ts b/src/telegram/sticker-cache.ts index 2c55563b7..38f421851 100644 --- a/src/telegram/sticker-cache.ts +++ b/src/telegram/sticker-cache.ts @@ -4,7 +4,13 @@ import type { ClawdbotConfig } from "../config/config.js"; import { STATE_DIR_CLAWDBOT } from "../config/paths.js"; import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { logVerbose } from "../globals.js"; -import { resolveApiKeyForProvider } from "../agents/model-auth.js"; +import { + findModelInCatalog, + loadModelCatalog, + modelSupportsVision, +} from "../agents/model-catalog.js"; +import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; +import { resolveAutoImageModel } from "../media-understanding/runner.js"; const CACHE_FILE = path.join(STATE_DIR_CLAWDBOT, "telegram", "sticker-cache.json"); const CACHE_VERSION = 1; @@ -135,18 +141,11 @@ export function getCacheStats(): { count: number; oldestAt?: string; newestAt?: const STICKER_DESCRIPTION_PROMPT = "Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective."; -const VISION_PROVIDERS = ["anthropic", "openai", "google", "minimax"] as const; -const DEFAULT_VISION_MODELS: Record = { - anthropic: "claude-sonnet-4-20250514", - openai: "gpt-4o-mini", - google: "gemini-2.0-flash", - minimax: "MiniMax-VL-01", -}; - export interface DescribeStickerParams { imagePath: string; cfg: ClawdbotConfig; agentDir?: string; + agentId?: string; } /** @@ -155,26 +154,35 @@ export interface DescribeStickerParams { * Returns null if no vision provider is available. */ export async function describeStickerImage(params: DescribeStickerParams): Promise { - const { imagePath, cfg, agentDir } = params; + const { imagePath, cfg, agentDir, agentId } = params; - // Find a vision provider with available API key - let provider: string | null = null; - for (const p of VISION_PROVIDERS) { - try { - await resolveApiKeyForProvider({ provider: p, cfg, agentDir }); - provider = p; - break; - } catch { - // No key for this provider, try next + const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); + let activeModel = undefined as { provider: string; model: string } | undefined; + try { + const catalog = await loadModelCatalog({ config: cfg }); + const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); + if (modelSupportsVision(entry)) { + activeModel = { provider: defaultModel.provider, model: defaultModel.model }; } + } catch { + // Ignore catalog failures; fall back to auto selection. } - if (!provider) { + const resolved = await resolveAutoImageModel({ + cfg, + agentDir, + activeModel, + }); + if (!resolved) { logVerbose("telegram: no vision provider available for sticker description"); return null; } - const model = DEFAULT_VISION_MODELS[provider]; + const { provider, model } = resolved; + if (!model) { + logVerbose(`telegram: no vision model available for ${provider}`); + return null; + } logVerbose(`telegram: describing sticker with ${provider}/${model}`); try { @@ -195,7 +203,7 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi }); return result.text; } catch (err) { - logVerbose(`telegram: failed to describe sticker: ${err}`); + logVerbose(`telegram: failed to describe sticker: ${String(err)}`); return null; } } From 54d6cd70b8b6d4360a5e8f455b50243ac29a890e Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 27 Jan 2026 12:56:21 +0530 Subject: [PATCH 003/101] docs: update changelog for #2629 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 442dd52a0..33dd3dafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,7 +53,7 @@ Status: unreleased. - Telegram: keep topic IDs in restart sentinel notifications. (#1807) Thanks @hsrvc. - Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma. - Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21. -- Telegram: add sticker receive/send with vision caching. (#2548) Thanks @longjos. +- Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos. - Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918. - Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999. - macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal. From d3a6333ef70c5e7bf4e0c7b0340f832218d65316 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Mon, 26 Jan 2026 23:41:35 -0800 Subject: [PATCH 004/101] docs: allow nested gateway security pages (#2641) --- docs/gateway/security-formal-verification.md | 12 -- docs/gateway/security/formal-verification.md | 107 ++++++++++++++++++ .../{security.md => security/index.md} | 0 3 files changed, 107 insertions(+), 12 deletions(-) delete mode 100644 docs/gateway/security-formal-verification.md create mode 100644 docs/gateway/security/formal-verification.md rename docs/gateway/{security.md => security/index.md} (100%) diff --git a/docs/gateway/security-formal-verification.md b/docs/gateway/security-formal-verification.md deleted file mode 100644 index 3fb5d649f..000000000 --- a/docs/gateway/security-formal-verification.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Formal Verification (Security Models) -summary: Redirect to the canonical Formal Verification page. -permalink: /gateway/security/formal-verification/ ---- - -This page moved to: [/security/formal-verification/](/security/formal-verification/) - - diff --git a/docs/gateway/security/formal-verification.md b/docs/gateway/security/formal-verification.md new file mode 100644 index 000000000..1a450176d --- /dev/null +++ b/docs/gateway/security/formal-verification.md @@ -0,0 +1,107 @@ +--- +title: Formal Verification (Security Models) +summary: Machine-checked security models for Clawdbot’s highest-risk paths. +permalink: /gateway/security/formal-verification/ +--- + +# Formal Verification (Security Models) + +This page tracks Clawdbot’s **formal security models** (TLA+/TLC today; more as needed). + +**Goal (north star):** provide a machine-checked argument that Clawdbot enforces its +intended security policy (authorization, session isolation, tool gating, and +misconfiguration safety), under explicit assumptions. + +**What this is (today):** an executable, attacker-driven **security regression suite**: +- Each claim has a runnable model-check over a finite state space. +- Many claims have a paired **negative model** that produces a counterexample trace for a realistic bug class. + +**What this is not (yet):** a proof that “Clawdbot is secure in all respects” or that the full TypeScript implementation is correct. + +## Where the models live + +Models are maintained in a separate repo: [vignesh07/clawdbot-formal-models](https://github.com/vignesh07/clawdbot-formal-models). + +## Important caveats + +- These are **models**, not the full TypeScript implementation. Drift between model and code is possible. +- Results are bounded by the state space explored by TLC; “green” does not imply security beyond the modeled assumptions and bounds. +- Some claims rely on explicit environmental assumptions (e.g., correct deployment, correct configuration inputs). + +## Reproducing results + +Today, results are reproduced by cloning the models repo locally and running TLC (see below). A future iteration could offer: +- CI-run models with public artifacts (counterexample traces, run logs) +- a hosted “run this model” workflow for small, bounded checks + +Getting started: + +```bash +git clone https://github.com/vignesh07/clawdbot-formal-models +cd clawdbot-formal-models + +# Java 11+ required (TLC runs on the JVM). +# The repo vendors a pinned `tla2tools.jar` (TLA+ tools) and provides `bin/tlc` + Make targets. + +make +``` + +### Gateway exposure and open gateway misconfiguration + +**Claim:** binding beyond loopback without auth can make remote compromise possible / increases exposure; token/password blocks unauth attackers (per the model assumptions). + +- Green runs: + - `make gateway-exposure-v2` + - `make gateway-exposure-v2-protected` +- Red (expected): + - `make gateway-exposure-v2-negative` + +See also: `docs/gateway-exposure-matrix.md` in the models repo. + +### Nodes.run pipeline (highest-risk capability) + +**Claim:** `nodes.run` requires (a) node command allowlist plus declared commands and (b) live approval when configured; approvals are tokenized to prevent replay (in the model). + +- Green runs: + - `make nodes-pipeline` + - `make approvals-token` +- Red (expected): + - `make nodes-pipeline-negative` + - `make approvals-token-negative` + +### Pairing store (DM gating) + +**Claim:** pairing requests respect TTL and pending-request caps. + +- Green runs: + - `make pairing` + - `make pairing-cap` +- Red (expected): + - `make pairing-negative` + - `make pairing-cap-negative` + +### Ingress gating (mentions + control-command bypass) + +**Claim:** in group contexts requiring mention, an unauthorized “control command” cannot bypass mention gating. + +- Green: + - `make ingress-gating` +- Red (expected): + - `make ingress-gating-negative` + +### Routing/session-key isolation + +**Claim:** DMs from distinct peers do not collapse into the same session unless explicitly linked/configured. + +- Green: + - `make routing-isolation` +- Red (expected): + - `make routing-isolation-negative` + +## Roadmap + +Next models to deepen fidelity: +- Pairing store concurrency/locking/idempotency +- Provider-specific ingress preflight modeling +- Routing identity-links + dmScope variants + binding precedence +- Gateway auth conformance (proxy/tailscale specifics) diff --git a/docs/gateway/security.md b/docs/gateway/security/index.md similarity index 100% rename from docs/gateway/security.md rename to docs/gateway/security/index.md From d91b4a30454b9fc0fea989e7caa63003e8c9ec9d Mon Sep 17 00:00:00 2001 From: hougangdev Date: Tue, 27 Jan 2026 09:37:22 +0800 Subject: [PATCH 005/101] feat: improve /help and /commands formatting with categories and pagination - Add CommandCategory type to organize commands into groups (session, options, status, management, media, tools, docks) - Refactor /help to show grouped sections for better discoverability - Add pagination support for /commands on Telegram (8 commands per page with nav buttons) - Show grouped list without pagination on other channels - Handle commands_page_N callback queries for Telegram pagination navigation --- src/auto-reply/commands-registry.data.ts | 38 ++- src/auto-reply/commands-registry.types.ts | 10 + src/auto-reply/reply/commands-info.ts | 57 ++++- src/auto-reply/status.ts | 282 ++++++++++++++++++---- src/telegram/bot-handlers.ts | 45 ++++ 5 files changed, 384 insertions(+), 48 deletions(-) diff --git a/src/auto-reply/commands-registry.data.ts b/src/auto-reply/commands-registry.data.ts index 5ba6826fe..1e2ebeb57 100644 --- a/src/auto-reply/commands-registry.data.ts +++ b/src/auto-reply/commands-registry.data.ts @@ -2,7 +2,11 @@ import { listChannelDocks } from "../channels/dock.js"; import { getActivePluginRegistry } from "../plugins/runtime.js"; import { listThinkingLevels } from "./thinking.js"; import { COMMAND_ARG_FORMATTERS } from "./commands-args.js"; -import type { ChatCommandDefinition, CommandScope } from "./commands-registry.types.js"; +import type { + ChatCommandDefinition, + CommandCategory, + CommandScope, +} from "./commands-registry.types.js"; type DefineChatCommandInput = { key: string; @@ -16,6 +20,7 @@ type DefineChatCommandInput = { textAlias?: string; textAliases?: string[]; scope?: CommandScope; + category?: CommandCategory; }; function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefinition { @@ -37,6 +42,7 @@ function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefiniti argsMenu: command.argsMenu, textAliases: aliases, scope, + category: command.category, }; } @@ -48,6 +54,7 @@ function defineDockCommand(dock: ChannelDock): ChatCommandDefinition { nativeName: `dock_${dock.id}`, description: `Switch to ${dock.id} for replies.`, textAliases: [`/dock-${dock.id}`, `/dock_${dock.id}`], + category: "docks", }); } @@ -124,18 +131,21 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "help", description: "Show available commands.", textAlias: "/help", + category: "status", }), defineChatCommand({ key: "commands", nativeName: "commands", description: "List all slash commands.", textAlias: "/commands", + category: "status", }), defineChatCommand({ key: "skill", nativeName: "skill", description: "Run a skill by name.", textAlias: "/skill", + category: "tools", args: [ { name: "name", @@ -156,6 +166,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "status", description: "Show current status.", textAlias: "/status", + category: "status", }), defineChatCommand({ key: "allowlist", @@ -163,6 +174,7 @@ function buildChatCommands(): ChatCommandDefinition[] { textAlias: "/allowlist", acceptsArgs: true, scope: "text", + category: "management", }), defineChatCommand({ key: "approve", @@ -170,6 +182,7 @@ function buildChatCommands(): ChatCommandDefinition[] { description: "Approve or deny exec requests.", textAlias: "/approve", acceptsArgs: true, + category: "management", }), defineChatCommand({ key: "context", @@ -177,12 +190,14 @@ function buildChatCommands(): ChatCommandDefinition[] { description: "Explain how context is built and used.", textAlias: "/context", acceptsArgs: true, + category: "status", }), defineChatCommand({ key: "tts", nativeName: "tts", description: "Control text-to-speech (TTS).", textAlias: "/tts", + category: "media", args: [ { name: "action", @@ -225,12 +240,14 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "whoami", description: "Show your sender id.", textAlias: "/whoami", + category: "status", }), defineChatCommand({ key: "subagents", nativeName: "subagents", description: "List/stop/log/info subagent runs for this session.", textAlias: "/subagents", + category: "management", args: [ { name: "action", @@ -257,6 +274,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "config", description: "Show or set config values.", textAlias: "/config", + category: "management", args: [ { name: "action", @@ -284,6 +302,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "debug", description: "Set runtime debug overrides.", textAlias: "/debug", + category: "management", args: [ { name: "action", @@ -311,6 +330,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "usage", description: "Usage footer or cost summary.", textAlias: "/usage", + category: "options", args: [ { name: "mode", @@ -326,18 +346,21 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "stop", description: "Stop the current run.", textAlias: "/stop", + category: "session", }), defineChatCommand({ key: "restart", nativeName: "restart", description: "Restart Clawdbot.", textAlias: "/restart", + category: "tools", }), defineChatCommand({ key: "activation", nativeName: "activation", description: "Set group activation mode.", textAlias: "/activation", + category: "management", args: [ { name: "mode", @@ -353,6 +376,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "send", description: "Set send policy.", textAlias: "/send", + category: "management", args: [ { name: "mode", @@ -369,6 +393,7 @@ function buildChatCommands(): ChatCommandDefinition[] { description: "Reset the current session.", textAlias: "/reset", acceptsArgs: true, + category: "session", }), defineChatCommand({ key: "new", @@ -376,12 +401,14 @@ function buildChatCommands(): ChatCommandDefinition[] { description: "Start a new session.", textAlias: "/new", acceptsArgs: true, + category: "session", }), defineChatCommand({ key: "compact", description: "Compact the session context.", textAlias: "/compact", scope: "text", + category: "session", args: [ { name: "instructions", @@ -396,6 +423,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "think", description: "Set thinking level.", textAlias: "/think", + category: "options", args: [ { name: "level", @@ -411,6 +439,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "verbose", description: "Toggle verbose mode.", textAlias: "/verbose", + category: "options", args: [ { name: "mode", @@ -426,6 +455,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "reasoning", description: "Toggle reasoning visibility.", textAlias: "/reasoning", + category: "options", args: [ { name: "mode", @@ -441,6 +471,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "elevated", description: "Toggle elevated mode.", textAlias: "/elevated", + category: "options", args: [ { name: "mode", @@ -456,6 +487,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "exec", description: "Set exec defaults for this session.", textAlias: "/exec", + category: "options", args: [ { name: "options", @@ -470,6 +502,7 @@ function buildChatCommands(): ChatCommandDefinition[] { nativeName: "model", description: "Show or set the model.", textAlias: "/model", + category: "options", args: [ { name: "model", @@ -485,12 +518,14 @@ function buildChatCommands(): ChatCommandDefinition[] { textAlias: "/models", argsParsing: "none", acceptsArgs: true, + category: "options", }), defineChatCommand({ key: "queue", nativeName: "queue", description: "Adjust queue settings.", textAlias: "/queue", + category: "options", args: [ { name: "mode", @@ -523,6 +558,7 @@ function buildChatCommands(): ChatCommandDefinition[] { description: "Run host shell commands (host-only).", textAlias: "/bash", scope: "text", + category: "tools", args: [ { name: "command", diff --git a/src/auto-reply/commands-registry.types.ts b/src/auto-reply/commands-registry.types.ts index 5e5bdd8cb..6b9371604 100644 --- a/src/auto-reply/commands-registry.types.ts +++ b/src/auto-reply/commands-registry.types.ts @@ -2,6 +2,15 @@ import type { ClawdbotConfig } from "../config/types.js"; export type CommandScope = "text" | "native" | "both"; +export type CommandCategory = + | "session" + | "options" + | "status" + | "management" + | "media" + | "tools" + | "docks"; + export type CommandArgType = "string" | "number" | "boolean"; export type CommandArgChoiceContext = { @@ -51,6 +60,7 @@ export type ChatCommandDefinition = { formatArgs?: (values: CommandArgValues) => string | undefined; argsMenu?: CommandArgMenuSpec | "auto"; scope: CommandScope; + category?: CommandCategory; }; export type NativeCommandSpec = { diff --git a/src/auto-reply/reply/commands-info.ts b/src/auto-reply/reply/commands-info.ts index 1a525150c..eec7053a7 100644 --- a/src/auto-reply/reply/commands-info.ts +++ b/src/auto-reply/reply/commands-info.ts @@ -1,6 +1,10 @@ import { logVerbose } from "../../globals.js"; import { listSkillCommandsForWorkspace } from "../skill-commands.js"; -import { buildCommandsMessage, buildHelpMessage } from "../status.js"; +import { + buildCommandsMessage, + buildCommandsMessagePaginated, + buildHelpMessage, +} from "../status.js"; import { buildStatusReply } from "./commands-status.js"; import { buildContextReply } from "./commands-context-report.js"; import type { CommandHandler } from "./commands-types.js"; @@ -35,12 +39,61 @@ export const handleCommandsListCommand: CommandHandler = async (params, allowTex workspaceDir: params.workspaceDir, cfg: params.cfg, }); + const surface = params.ctx.Surface; + + // For Telegram, return paginated result with inline buttons + if (surface === "telegram") { + const result = buildCommandsMessagePaginated(params.cfg, skillCommands, { + page: 1, + surface, + }); + + // Build inline keyboard for pagination if there are multiple pages + if (result.totalPages > 1) { + return { + shouldContinue: false, + reply: { + text: result.text, + channelData: { + telegram: { + buttons: buildCommandsPaginationKeyboard(result.currentPage, result.totalPages), + }, + }, + }, + }; + } + + return { + shouldContinue: false, + reply: { text: result.text }, + }; + } + return { shouldContinue: false, - reply: { text: buildCommandsMessage(params.cfg, skillCommands) }, + reply: { text: buildCommandsMessage(params.cfg, skillCommands, { surface }) }, }; }; +export function buildCommandsPaginationKeyboard( + currentPage: number, + totalPages: number, +): Array> { + const buttons: Array<{ text: string; callback_data: string }> = []; + + if (currentPage > 1) { + buttons.push({ text: "◀ Prev", callback_data: `commands_page_${currentPage - 1}` }); + } + + buttons.push({ text: `${currentPage}/${totalPages}`, callback_data: "commands_page_noop" }); + + if (currentPage < totalPages) { + buttons.push({ text: "Next ▶", callback_data: `commands_page_${currentPage + 1}` }); + } + + return [buttons]; +} + export const handleStatusCommand: CommandHandler = async (params, allowTextCommands) => { if (!allowTextCommands) return null; const statusRequested = diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 733205c8c..713815f4f 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -29,7 +29,12 @@ import { resolveModelCostConfig, } from "../utils/usage-format.js"; import { VERSION } from "../version.js"; -import { listChatCommands, listChatCommandsForConfig } from "./commands-registry.js"; +import { + listChatCommands, + listChatCommandsForConfig, + type ChatCommandDefinition, +} from "./commands-registry.js"; +import type { CommandCategory } from "./commands-registry.types.js"; import { listPluginCommands } from "../plugins/commands.js"; import type { SkillCommandSpec } from "../agents/skills.js"; import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js"; @@ -427,61 +432,248 @@ export function buildStatusMessage(args: StatusArgs): string { .join("\n"); } +const CATEGORY_LABELS: Record = { + session: "Session", + options: "Options", + status: "Status", + management: "Management", + media: "Media", + tools: "Tools", + docks: "Docks", +}; + +const CATEGORY_ORDER: CommandCategory[] = [ + "session", + "options", + "status", + "management", + "media", + "tools", + "docks", +]; + +function groupCommandsByCategory( + commands: ChatCommandDefinition[], +): Map { + const grouped = new Map(); + for (const category of CATEGORY_ORDER) { + grouped.set(category, []); + } + for (const command of commands) { + const category = command.category ?? "tools"; + const list = grouped.get(category) ?? []; + list.push(command); + grouped.set(category, list); + } + return grouped; +} + export function buildHelpMessage(cfg?: ClawdbotConfig): string { - const options = [ - "/think ", - "/verbose on|full|off", - "/reasoning on|off", - "/elevated on|off|ask|full", - "/model ", - "/usage off|tokens|full", - ]; - if (cfg?.commands?.config === true) options.push("/config show"); - if (cfg?.commands?.debug === true) options.push("/debug show"); - return [ - "ℹ️ Help", - "Shortcuts: /new reset | /compact [instructions] | /restart relink (if enabled)", - `Options: ${options.join(" | ")}`, - "Skills: /skill [input]", - "More: /commands for all slash commands", - ].join("\n"); + const lines = ["ℹ️ Help", ""]; + + // Session commands - quick shortcuts + lines.push("Session"); + lines.push(" /new | /reset | /compact [instructions] | /stop"); + lines.push(""); + + // Options - most commonly used + const optionParts = ["/think ", "/model ", "/verbose on|off"]; + if (cfg?.commands?.config === true) optionParts.push("/config"); + if (cfg?.commands?.debug === true) optionParts.push("/debug"); + lines.push("Options"); + lines.push(` ${optionParts.join(" | ")}`); + lines.push(""); + + // Status commands + lines.push("Status"); + lines.push(" /status | /whoami | /context"); + lines.push(""); + + // Skills + lines.push("Skills"); + lines.push(" /skill [input]"); + + lines.push(""); + lines.push("More: /commands for full list"); + + return lines.join("\n"); +} + +const COMMANDS_PER_PAGE = 8; + +export type CommandsMessageOptions = { + page?: number; + surface?: string; +}; + +export type CommandsMessageResult = { + text: string; + totalPages: number; + currentPage: number; + hasNext: boolean; + hasPrev: boolean; +}; + +function formatCommandEntry(command: ChatCommandDefinition): string { + const primary = command.nativeName + ? `/${command.nativeName}` + : command.textAliases[0]?.trim() || `/${command.key}`; + const seen = new Set(); + const aliases = command.textAliases + .map((alias) => alias.trim()) + .filter(Boolean) + .filter((alias) => alias.toLowerCase() !== primary.toLowerCase()) + .filter((alias) => { + const key = alias.toLowerCase(); + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + const aliasLabel = aliases.length ? ` (${aliases.join(", ")})` : ""; + const scopeLabel = command.scope === "text" ? " [text]" : ""; + return `${primary}${aliasLabel}${scopeLabel} - ${command.description}`; } export function buildCommandsMessage( cfg?: ClawdbotConfig, skillCommands?: SkillCommandSpec[], + options?: CommandsMessageOptions, ): string { - const lines = ["ℹ️ Slash commands"]; + const result = buildCommandsMessagePaginated(cfg, skillCommands, options); + return result.text; +} + +export function buildCommandsMessagePaginated( + cfg?: ClawdbotConfig, + skillCommands?: SkillCommandSpec[], + options?: CommandsMessageOptions, +): CommandsMessageResult { + const page = Math.max(1, options?.page ?? 1); + const surface = options?.surface?.toLowerCase(); + const isTelegram = surface === "telegram"; + const commands = cfg ? listChatCommandsForConfig(cfg, { skillCommands }) : listChatCommands({ skillCommands }); - for (const command of commands) { - const primary = command.nativeName - ? `/${command.nativeName}` - : command.textAliases[0]?.trim() || `/${command.key}`; - const seen = new Set(); - const aliases = command.textAliases - .map((alias) => alias.trim()) - .filter(Boolean) - .filter((alias) => alias.toLowerCase() !== primary.toLowerCase()) - .filter((alias) => { - const key = alias.toLowerCase(); - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - const aliasLabel = aliases.length ? ` (aliases: ${aliases.join(", ")})` : ""; - const scopeLabel = command.scope === "text" ? " (text-only)" : ""; - lines.push(`${primary}${aliasLabel}${scopeLabel} - ${command.description}`); - } const pluginCommands = listPluginCommands(); - if (pluginCommands.length > 0) { - lines.push(""); - lines.push("Plugin commands:"); - for (const command of pluginCommands) { - const pluginLabel = command.pluginId ? ` (plugin: ${command.pluginId})` : ""; - lines.push(`/${command.name}${pluginLabel} - ${command.description}`); + + // For non-Telegram surfaces, show grouped list without pagination + if (!isTelegram) { + const grouped = groupCommandsByCategory(commands); + const lines = ["ℹ️ Slash commands", ""]; + + for (const category of CATEGORY_ORDER) { + const categoryCommands = grouped.get(category) ?? []; + if (categoryCommands.length === 0) continue; + + lines.push(`${CATEGORY_LABELS[category]}`); + for (const command of categoryCommands) { + lines.push(` ${formatCommandEntry(command)}`); + } + lines.push(""); + } + + if (pluginCommands.length > 0) { + lines.push("Plugins"); + for (const command of pluginCommands) { + const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; + lines.push(` /${command.name}${pluginLabel} - ${command.description}`); + } + } + + return { + text: lines.join("\n").trim(), + totalPages: 1, + currentPage: 1, + hasNext: false, + hasPrev: false, + }; + } + + // For Telegram, use pagination + const grouped = groupCommandsByCategory(commands); + + // Flatten commands with category headers for pagination + type PageItem = + | { type: "header"; category: CommandCategory } + | { type: "command"; command: ChatCommandDefinition }; + const items: PageItem[] = []; + + for (const category of CATEGORY_ORDER) { + const categoryCommands = grouped.get(category) ?? []; + if (categoryCommands.length === 0) continue; + items.push({ type: "header", category }); + for (const command of categoryCommands) { + items.push({ type: "command", command }); } } - return lines.join("\n"); + + // Add plugin commands + if (pluginCommands.length > 0) { + items.push({ type: "header", category: "tools" }); // Reuse tools category for plugins header indicator + } + + // Calculate pages based on command count (headers don't count toward limit) + const commandItems = items.filter((item) => item.type === "command"); + const totalCommands = commandItems.length + pluginCommands.length; + const totalPages = Math.max(1, Math.ceil(totalCommands / COMMANDS_PER_PAGE)); + const currentPage = Math.min(page, totalPages); + const startIndex = (currentPage - 1) * COMMANDS_PER_PAGE; + const endIndex = startIndex + COMMANDS_PER_PAGE; + + // Build page content + const lines = [`ℹ️ Commands (${currentPage}/${totalPages})`, ""]; + + let commandIndex = 0; + let currentCategory: CommandCategory | null = null; + let pageCommandCount = 0; + + for (const item of items) { + if (pageCommandCount >= COMMANDS_PER_PAGE) break; + + if (item.type === "header") { + currentCategory = item.category; + continue; + } + + if (commandIndex >= startIndex && commandIndex < endIndex) { + // Add category header if this is the first command of a category on this page + if ( + (currentCategory && pageCommandCount === 0) || + items[items.indexOf(item) - 1]?.type === "header" + ) { + if (currentCategory) { + if (pageCommandCount > 0) lines.push(""); + lines.push(CATEGORY_LABELS[currentCategory]); + } + } + lines.push(` ${formatCommandEntry(item.command)}`); + pageCommandCount++; + } + commandIndex++; + } + + // Add plugin commands if they fall within this page range + const pluginStartIndex = commandItems.length; + for (let i = 0; i < pluginCommands.length && pageCommandCount < COMMANDS_PER_PAGE; i++) { + const pluginIndex = pluginStartIndex + i; + if (pluginIndex >= startIndex && pluginIndex < endIndex) { + if (i === 0 || pluginIndex === startIndex) { + if (pageCommandCount > 0) lines.push(""); + lines.push("Plugins"); + } + const command = pluginCommands[i]; + const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; + lines.push(` /${command.name}${pluginLabel} - ${command.description}`); + pageCommandCount++; + } + } + + return { + text: lines.join("\n"), + totalPages, + currentPage, + hasNext: currentPage < totalPages, + hasPrev: currentPage > 1, + }; } diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 7a5b88fd7..f4acafc19 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -4,6 +4,9 @@ import { createInboundDebouncer, resolveInboundDebounceMs, } from "../auto-reply/inbound-debounce.js"; +import { buildCommandsPaginationKeyboard } from "../auto-reply/reply/commands-info.js"; +import { buildCommandsMessagePaginated } from "../auto-reply/status.js"; +import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js"; import { loadConfig } from "../config/config.js"; import { writeConfigFile } from "../config/io.js"; import { danger, logVerbose, warn } from "../globals.js"; @@ -17,6 +20,7 @@ import { migrateTelegramGroupConfig } from "./group-migration.js"; import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js"; import { readTelegramAllowFromStore } from "./pairing-store.js"; import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js"; +import { buildInlineKeyboard } from "./send.js"; export const registerTelegramHandlers = ({ cfg, @@ -199,6 +203,47 @@ export const registerTelegramHandlers = ({ const callbackMessage = callback.message; if (!data || !callbackMessage) return; + // Handle commands pagination callback + const paginationMatch = data.match(/^commands_page_(\d+|noop)$/); + if (paginationMatch) { + const pageValue = paginationMatch[1]; + if (pageValue === "noop") return; // Page number button - no action + + const page = parseInt(pageValue, 10); + if (isNaN(page) || page < 1) return; + + const skillCommands = listSkillCommandsForAgents({ cfg }); + const result = buildCommandsMessagePaginated(cfg, skillCommands, { + page, + surface: "telegram", + }); + + const messageId = callbackMessage.message_id; + const chatId = callbackMessage.chat.id; + const keyboard = + result.totalPages > 1 + ? buildInlineKeyboard( + buildCommandsPaginationKeyboard(result.currentPage, result.totalPages), + ) + : undefined; + + try { + await bot.api.editMessageText( + chatId, + messageId, + result.text, + keyboard ? { reply_markup: keyboard } : undefined, + ); + } catch (editErr) { + // Ignore "message is not modified" errors (user clicked same page) + const errStr = String(editErr); + if (!errStr.includes("message is not modified")) { + throw editErr; + } + } + return; + } + const inlineButtonsScope = resolveTelegramInlineButtonsScope({ cfg, accountId, From 97440eaf527d3b8443f729f630a4037fbf61ca41 Mon Sep 17 00:00:00 2001 From: hougangdev Date: Tue, 27 Jan 2026 10:18:53 +0800 Subject: [PATCH 006/101] test: update status tests for new help/commands format --- src/auto-reply/status.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index 31b6b92ec..edefaf283 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -402,8 +402,8 @@ describe("buildCommandsMessage", () => { } as ClawdbotConfig); expect(text).toContain("/commands - List all slash commands."); expect(text).toContain("/skill - Run a skill by name."); - expect(text).toContain("/think (aliases: /thinking, /t) - Set thinking level."); - expect(text).toContain("/compact (text-only) - Compact the session context."); + expect(text).toContain("/think (/thinking, /t) - Set thinking level."); + expect(text).toContain("/compact [text] - Compact the session context."); expect(text).not.toContain("/config"); expect(text).not.toContain("/debug"); }); @@ -430,7 +430,8 @@ describe("buildHelpMessage", () => { const text = buildHelpMessage({ commands: { config: false, debug: false }, } as ClawdbotConfig); - expect(text).toContain("Skills: /skill [input]"); + expect(text).toContain("Skills"); + expect(text).toContain("/skill [input]"); expect(text).not.toContain("/config"); expect(text).not.toContain("/debug"); }); From cc1782b1055fda77ef89bcc023a23c1c1bad1c74 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 27 Jan 2026 02:35:09 -0500 Subject: [PATCH 007/101] fix: tighten commands output + telegram pagination (#2504) Co-authored-by: hougangdev --- src/auto-reply/reply/commands-info.test.ts | 13 ++ src/auto-reply/reply/commands-info.ts | 31 +++-- src/auto-reply/status.test.ts | 47 ++++++- src/auto-reply/status.ts | 155 ++++++++------------- src/telegram/bot-handlers.ts | 42 ++++++ src/telegram/bot.test.ts | 85 +++++++++++ 6 files changed, 263 insertions(+), 110 deletions(-) create mode 100644 src/auto-reply/reply/commands-info.test.ts diff --git a/src/auto-reply/reply/commands-info.test.ts b/src/auto-reply/reply/commands-info.test.ts new file mode 100644 index 000000000..9751c39cc --- /dev/null +++ b/src/auto-reply/reply/commands-info.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from "vitest"; +import { buildCommandsPaginationKeyboard } from "./commands-info.js"; + +describe("buildCommandsPaginationKeyboard", () => { + it("adds agent id to callback data when provided", () => { + const keyboard = buildCommandsPaginationKeyboard(2, 3, "agent-main"); + expect(keyboard[0]).toEqual([ + { text: "◀ Prev", callback_data: "commands_page_1:agent-main" }, + { text: "2/3", callback_data: "commands_page_noop:agent-main" }, + { text: "Next ▶", callback_data: "commands_page_3:agent-main" }, + ]); + }); +}); diff --git a/src/auto-reply/reply/commands-info.ts b/src/auto-reply/reply/commands-info.ts index eec7053a7..e7d8a8f6f 100644 --- a/src/auto-reply/reply/commands-info.ts +++ b/src/auto-reply/reply/commands-info.ts @@ -1,5 +1,5 @@ import { logVerbose } from "../../globals.js"; -import { listSkillCommandsForWorkspace } from "../skill-commands.js"; +import { listSkillCommandsForAgents } from "../skill-commands.js"; import { buildCommandsMessage, buildCommandsMessagePaginated, @@ -35,20 +35,18 @@ export const handleCommandsListCommand: CommandHandler = async (params, allowTex } const skillCommands = params.skillCommands ?? - listSkillCommandsForWorkspace({ - workspaceDir: params.workspaceDir, + listSkillCommandsForAgents({ cfg: params.cfg, + agentIds: params.agentId ? [params.agentId] : undefined, }); const surface = params.ctx.Surface; - // For Telegram, return paginated result with inline buttons if (surface === "telegram") { const result = buildCommandsMessagePaginated(params.cfg, skillCommands, { page: 1, surface, }); - // Build inline keyboard for pagination if there are multiple pages if (result.totalPages > 1) { return { shouldContinue: false, @@ -56,7 +54,11 @@ export const handleCommandsListCommand: CommandHandler = async (params, allowTex text: result.text, channelData: { telegram: { - buttons: buildCommandsPaginationKeyboard(result.currentPage, result.totalPages), + buttons: buildCommandsPaginationKeyboard( + result.currentPage, + result.totalPages, + params.agentId, + ), }, }, }, @@ -78,17 +80,28 @@ export const handleCommandsListCommand: CommandHandler = async (params, allowTex export function buildCommandsPaginationKeyboard( currentPage: number, totalPages: number, + agentId?: string, ): Array> { const buttons: Array<{ text: string; callback_data: string }> = []; + const suffix = agentId ? `:${agentId}` : ""; if (currentPage > 1) { - buttons.push({ text: "◀ Prev", callback_data: `commands_page_${currentPage - 1}` }); + buttons.push({ + text: "◀ Prev", + callback_data: `commands_page_${currentPage - 1}${suffix}`, + }); } - buttons.push({ text: `${currentPage}/${totalPages}`, callback_data: "commands_page_noop" }); + buttons.push({ + text: `${currentPage}/${totalPages}`, + callback_data: `commands_page_noop${suffix}`, + }); if (currentPage < totalPages) { - buttons.push({ text: "Next ▶", callback_data: `commands_page_${currentPage + 1}` }); + buttons.push({ + text: "Next ▶", + callback_data: `commands_page_${currentPage + 1}${suffix}`, + }); } return [buttons]; diff --git a/src/auto-reply/status.test.ts b/src/auto-reply/status.test.ts index edefaf283..465352538 100644 --- a/src/auto-reply/status.test.ts +++ b/src/auto-reply/status.test.ts @@ -4,7 +4,20 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { normalizeTestText } from "../../test/helpers/normalize-text.js"; import { withTempHome } from "../../test/helpers/temp-home.js"; import type { ClawdbotConfig } from "../config/config.js"; -import { buildCommandsMessage, buildHelpMessage, buildStatusMessage } from "./status.js"; +import { + buildCommandsMessage, + buildCommandsMessagePaginated, + buildHelpMessage, + buildStatusMessage, +} from "./status.js"; + +const { listPluginCommands } = vi.hoisted(() => ({ + listPluginCommands: vi.fn(() => []), +})); + +vi.mock("../plugins/commands.js", () => ({ + listPluginCommands, +})); afterEach(() => { vi.restoreAllMocks(); @@ -400,6 +413,8 @@ describe("buildCommandsMessage", () => { const text = buildCommandsMessage({ commands: { config: false, debug: false }, } as ClawdbotConfig); + expect(text).toContain("ℹ️ Slash commands"); + expect(text).toContain("Status"); expect(text).toContain("/commands - List all slash commands."); expect(text).toContain("/skill - Run a skill by name."); expect(text).toContain("/think (/thinking, /t) - Set thinking level."); @@ -436,3 +451,33 @@ describe("buildHelpMessage", () => { expect(text).not.toContain("/debug"); }); }); + +describe("buildCommandsMessagePaginated", () => { + it("formats telegram output with pages", () => { + const result = buildCommandsMessagePaginated( + { + commands: { config: false, debug: false }, + } as ClawdbotConfig, + undefined, + { surface: "telegram", page: 1 }, + ); + expect(result.text).toContain("ℹ️ Commands (1/"); + expect(result.text).toContain("Session"); + expect(result.text).toContain("/stop - Stop the current run."); + }); + + it("includes plugin commands in the paginated list", () => { + listPluginCommands.mockReturnValue([ + { name: "plugin_cmd", description: "Plugin command", pluginId: "demo-plugin" }, + ]); + const result = buildCommandsMessagePaginated( + { + commands: { config: false, debug: false }, + } as ClawdbotConfig, + undefined, + { surface: "telegram", page: 99 }, + ); + expect(result.text).toContain("Plugins"); + expect(result.text).toContain("/plugin_cmd (demo-plugin) - Plugin command"); + }); +}); diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index 713815f4f..7344b7502 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -34,9 +34,9 @@ import { listChatCommandsForConfig, type ChatCommandDefinition, } from "./commands-registry.js"; -import type { CommandCategory } from "./commands-registry.types.js"; import { listPluginCommands } from "../plugins/commands.js"; import type { SkillCommandSpec } from "../agents/skills.js"; +import type { CommandCategory } from "./commands-registry.types.js"; import type { ElevatedLevel, ReasoningLevel, ThinkLevel, VerboseLevel } from "./thinking.js"; import type { MediaUnderstandingDecision } from "../media-understanding/types.js"; @@ -471,12 +471,10 @@ function groupCommandsByCategory( export function buildHelpMessage(cfg?: ClawdbotConfig): string { const lines = ["ℹ️ Help", ""]; - // Session commands - quick shortcuts lines.push("Session"); lines.push(" /new | /reset | /compact [instructions] | /stop"); lines.push(""); - // Options - most commonly used const optionParts = ["/think ", "/model ", "/verbose on|off"]; if (cfg?.commands?.config === true) optionParts.push("/config"); if (cfg?.commands?.debug === true) optionParts.push("/debug"); @@ -484,12 +482,10 @@ export function buildHelpMessage(cfg?: ClawdbotConfig): string { lines.push(` ${optionParts.join(" | ")}`); lines.push(""); - // Status commands lines.push("Status"); lines.push(" /status | /whoami | /context"); lines.push(""); - // Skills lines.push("Skills"); lines.push(" /skill [input]"); @@ -534,6 +530,54 @@ function formatCommandEntry(command: ChatCommandDefinition): string { return `${primary}${aliasLabel}${scopeLabel} - ${command.description}`; } +type CommandsListItem = { + label: string; + text: string; +}; + +function buildCommandItems( + commands: ChatCommandDefinition[], + pluginCommands: ReturnType, +): CommandsListItem[] { + const grouped = groupCommandsByCategory(commands); + const items: CommandsListItem[] = []; + + for (const category of CATEGORY_ORDER) { + const categoryCommands = grouped.get(category) ?? []; + if (categoryCommands.length === 0) continue; + const label = CATEGORY_LABELS[category]; + for (const command of categoryCommands) { + items.push({ label, text: formatCommandEntry(command) }); + } + } + + for (const command of pluginCommands) { + const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; + items.push({ + label: "Plugins", + text: `/${command.name}${pluginLabel} - ${command.description}`, + }); + } + + return items; +} + +function formatCommandList(items: CommandsListItem[]): string { + const lines: string[] = []; + let currentLabel: string | null = null; + + for (const item of items) { + if (item.label !== currentLabel) { + if (lines.length > 0) lines.push(""); + lines.push(item.label); + currentLabel = item.label; + } + lines.push(` ${item.text}`); + } + + return lines.join("\n"); +} + export function buildCommandsMessage( cfg?: ClawdbotConfig, skillCommands?: SkillCommandSpec[], @@ -556,31 +600,11 @@ export function buildCommandsMessagePaginated( ? listChatCommandsForConfig(cfg, { skillCommands }) : listChatCommands({ skillCommands }); const pluginCommands = listPluginCommands(); + const items = buildCommandItems(commands, pluginCommands); - // For non-Telegram surfaces, show grouped list without pagination if (!isTelegram) { - const grouped = groupCommandsByCategory(commands); const lines = ["ℹ️ Slash commands", ""]; - - for (const category of CATEGORY_ORDER) { - const categoryCommands = grouped.get(category) ?? []; - if (categoryCommands.length === 0) continue; - - lines.push(`${CATEGORY_LABELS[category]}`); - for (const command of categoryCommands) { - lines.push(` ${formatCommandEntry(command)}`); - } - lines.push(""); - } - - if (pluginCommands.length > 0) { - lines.push("Plugins"); - for (const command of pluginCommands) { - const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; - lines.push(` /${command.name}${pluginLabel} - ${command.description}`); - } - } - + lines.push(formatCommandList(items)); return { text: lines.join("\n").trim(), totalPages: 1, @@ -590,87 +614,18 @@ export function buildCommandsMessagePaginated( }; } - // For Telegram, use pagination - const grouped = groupCommandsByCategory(commands); - - // Flatten commands with category headers for pagination - type PageItem = - | { type: "header"; category: CommandCategory } - | { type: "command"; command: ChatCommandDefinition }; - const items: PageItem[] = []; - - for (const category of CATEGORY_ORDER) { - const categoryCommands = grouped.get(category) ?? []; - if (categoryCommands.length === 0) continue; - items.push({ type: "header", category }); - for (const command of categoryCommands) { - items.push({ type: "command", command }); - } - } - - // Add plugin commands - if (pluginCommands.length > 0) { - items.push({ type: "header", category: "tools" }); // Reuse tools category for plugins header indicator - } - - // Calculate pages based on command count (headers don't count toward limit) - const commandItems = items.filter((item) => item.type === "command"); - const totalCommands = commandItems.length + pluginCommands.length; + const totalCommands = items.length; const totalPages = Math.max(1, Math.ceil(totalCommands / COMMANDS_PER_PAGE)); const currentPage = Math.min(page, totalPages); const startIndex = (currentPage - 1) * COMMANDS_PER_PAGE; const endIndex = startIndex + COMMANDS_PER_PAGE; + const pageItems = items.slice(startIndex, endIndex); - // Build page content const lines = [`ℹ️ Commands (${currentPage}/${totalPages})`, ""]; - - let commandIndex = 0; - let currentCategory: CommandCategory | null = null; - let pageCommandCount = 0; - - for (const item of items) { - if (pageCommandCount >= COMMANDS_PER_PAGE) break; - - if (item.type === "header") { - currentCategory = item.category; - continue; - } - - if (commandIndex >= startIndex && commandIndex < endIndex) { - // Add category header if this is the first command of a category on this page - if ( - (currentCategory && pageCommandCount === 0) || - items[items.indexOf(item) - 1]?.type === "header" - ) { - if (currentCategory) { - if (pageCommandCount > 0) lines.push(""); - lines.push(CATEGORY_LABELS[currentCategory]); - } - } - lines.push(` ${formatCommandEntry(item.command)}`); - pageCommandCount++; - } - commandIndex++; - } - - // Add plugin commands if they fall within this page range - const pluginStartIndex = commandItems.length; - for (let i = 0; i < pluginCommands.length && pageCommandCount < COMMANDS_PER_PAGE; i++) { - const pluginIndex = pluginStartIndex + i; - if (pluginIndex >= startIndex && pluginIndex < endIndex) { - if (i === 0 || pluginIndex === startIndex) { - if (pageCommandCount > 0) lines.push(""); - lines.push("Plugins"); - } - const command = pluginCommands[i]; - const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; - lines.push(` /${command.name}${pluginLabel} - ${command.description}`); - pageCommandCount++; - } - } + lines.push(formatCommandList(pageItems)); return { - text: lines.join("\n"), + text: lines.join("\n").trim(), totalPages, currentPage, hasNext: currentPage < totalPages, diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index f4acafc19..de012f19c 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -7,6 +7,7 @@ import { import { buildCommandsPaginationKeyboard } from "../auto-reply/reply/commands-info.js"; import { buildCommandsMessagePaginated } from "../auto-reply/status.js"; import { listSkillCommandsForAgents } from "../auto-reply/skill-commands.js"; +import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { loadConfig } from "../config/config.js"; import { writeConfigFile } from "../config/io.js"; import { danger, logVerbose, warn } from "../globals.js"; @@ -368,6 +369,47 @@ export const registerTelegramHandlers = ({ } } + const paginationMatch = data.match(/^commands_page_(\d+|noop)(?::(.+))?$/); + if (paginationMatch) { + const pageValue = paginationMatch[1]; + if (pageValue === "noop") return; + + const page = Number.parseInt(pageValue, 10); + if (Number.isNaN(page) || page < 1) return; + + const agentId = paginationMatch[2]?.trim() || resolveDefaultAgentId(cfg) || undefined; + const skillCommands = listSkillCommandsForAgents({ + cfg, + agentIds: agentId ? [agentId] : undefined, + }); + const result = buildCommandsMessagePaginated(cfg, skillCommands, { + page, + surface: "telegram", + }); + + const keyboard = + result.totalPages > 1 + ? buildInlineKeyboard( + buildCommandsPaginationKeyboard(result.currentPage, result.totalPages, agentId), + ) + : undefined; + + try { + await bot.api.editMessageText( + callbackMessage.chat.id, + callbackMessage.message_id, + result.text, + keyboard ? { reply_markup: keyboard } : undefined, + ); + } catch (editErr) { + const errStr = String(editErr); + if (!errStr.includes("message is not modified")) { + throw editErr; + } + } + return; + } + const syntheticMessage: TelegramMessage = { ...callbackMessage, from: callback.from, diff --git a/src/telegram/bot.test.ts b/src/telegram/bot.test.ts index 274f7c6a9..c2de155b0 100644 --- a/src/telegram/bot.test.ts +++ b/src/telegram/bot.test.ts @@ -93,6 +93,7 @@ const commandSpy = vi.fn(); const botCtorSpy = vi.fn(); const answerCallbackQuerySpy = vi.fn(async () => undefined); const sendChatActionSpy = vi.fn(); +const editMessageTextSpy = vi.fn(async () => ({ message_id: 88 })); const setMessageReactionSpy = vi.fn(async () => undefined); const setMyCommandsSpy = vi.fn(async () => undefined); const sendMessageSpy = vi.fn(async () => ({ message_id: 77 })); @@ -102,6 +103,7 @@ type ApiStub = { config: { use: (arg: unknown) => void }; answerCallbackQuery: typeof answerCallbackQuerySpy; sendChatAction: typeof sendChatActionSpy; + editMessageText: typeof editMessageTextSpy; setMessageReaction: typeof setMessageReactionSpy; setMyCommands: typeof setMyCommandsSpy; sendMessage: typeof sendMessageSpy; @@ -112,6 +114,7 @@ const apiStub: ApiStub = { config: { use: useSpy }, answerCallbackQuery: answerCallbackQuerySpy, sendChatAction: sendChatActionSpy, + editMessageText: editMessageTextSpy, setMessageReaction: setMessageReactionSpy, setMyCommands: setMyCommandsSpy, sendMessage: sendMessageSpy, @@ -192,6 +195,7 @@ describe("createTelegramBot", () => { sendPhotoSpy.mockReset(); setMessageReactionSpy.mockReset(); answerCallbackQuerySpy.mockReset(); + editMessageTextSpy.mockReset(); setMyCommandsSpy.mockReset(); wasSentByBot.mockReset(); middlewareUseSpy.mockReset(); @@ -424,6 +428,87 @@ describe("createTelegramBot", () => { expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-2"); }); + it("edits commands list for pagination callbacks", async () => { + onSpy.mockReset(); + listSkillCommandsForAgents.mockReset(); + + createTelegramBot({ token: "tok" }); + const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as ( + ctx: Record, + ) => Promise; + expect(callbackHandler).toBeDefined(); + + await callbackHandler({ + callbackQuery: { + id: "cbq-3", + data: "commands_page_2:main", + from: { id: 9, first_name: "Ada", username: "ada_bot" }, + message: { + chat: { id: 1234, type: "private" }, + date: 1736380800, + message_id: 12, + }, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + + expect(listSkillCommandsForAgents).toHaveBeenCalledWith({ + cfg: expect.any(Object), + agentIds: ["main"], + }); + expect(editMessageTextSpy).toHaveBeenCalledTimes(1); + const [chatId, messageId, text, params] = editMessageTextSpy.mock.calls[0] ?? []; + expect(chatId).toBe(1234); + expect(messageId).toBe(12); + expect(String(text)).toContain("ℹ️ Commands"); + expect(params).toEqual( + expect.objectContaining({ + reply_markup: expect.any(Object), + }), + ); + }); + + it("blocks pagination callbacks when allowlist rejects sender", async () => { + onSpy.mockReset(); + editMessageTextSpy.mockReset(); + + createTelegramBot({ + token: "tok", + config: { + channels: { + telegram: { + dmPolicy: "pairing", + capabilities: { inlineButtons: "allowlist" }, + allowFrom: [], + }, + }, + }, + }); + const callbackHandler = onSpy.mock.calls.find((call) => call[0] === "callback_query")?.[1] as ( + ctx: Record, + ) => Promise; + expect(callbackHandler).toBeDefined(); + + await callbackHandler({ + callbackQuery: { + id: "cbq-4", + data: "commands_page_2", + from: { id: 9, first_name: "Ada", username: "ada_bot" }, + message: { + chat: { id: 1234, type: "private" }, + date: 1736380800, + message_id: 13, + }, + }, + me: { username: "clawdbot_bot" }, + getFile: async () => ({ download: async () => new Uint8Array() }), + }); + + expect(editMessageTextSpy).not.toHaveBeenCalled(); + expect(answerCallbackQuerySpy).toHaveBeenCalledWith("cbq-4"); + }); + it("wraps inbound message with Telegram envelope", async () => { const originalTz = process.env.TZ; process.env.TZ = "Europe/Vienna"; From 2ad550abe8d0e25d377c7a9f752c2a494b2e2e95 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 27 Jan 2026 02:42:54 -0500 Subject: [PATCH 008/101] fix: land /help + /commands formatting (#2504) (thanks @hougangdev) --- CHANGELOG.md | 1 + src/telegram/bot-handlers.ts | 41 ------------------------------------ 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33dd3dafc..749a6c660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.clawd.bot Status: unreleased. ### Changes +- Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. - macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). - Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt. - Agents: summarize dropped messages during compaction safeguard pruning. (#2509) Thanks @jogi47. diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index de012f19c..477b98280 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -204,47 +204,6 @@ export const registerTelegramHandlers = ({ const callbackMessage = callback.message; if (!data || !callbackMessage) return; - // Handle commands pagination callback - const paginationMatch = data.match(/^commands_page_(\d+|noop)$/); - if (paginationMatch) { - const pageValue = paginationMatch[1]; - if (pageValue === "noop") return; // Page number button - no action - - const page = parseInt(pageValue, 10); - if (isNaN(page) || page < 1) return; - - const skillCommands = listSkillCommandsForAgents({ cfg }); - const result = buildCommandsMessagePaginated(cfg, skillCommands, { - page, - surface: "telegram", - }); - - const messageId = callbackMessage.message_id; - const chatId = callbackMessage.chat.id; - const keyboard = - result.totalPages > 1 - ? buildInlineKeyboard( - buildCommandsPaginationKeyboard(result.currentPage, result.totalPages), - ) - : undefined; - - try { - await bot.api.editMessageText( - chatId, - messageId, - result.text, - keyboard ? { reply_markup: keyboard } : undefined, - ); - } catch (editErr) { - // Ignore "message is not modified" errors (user clicked same page) - const errStr = String(editErr); - if (!errStr.includes("message is not modified")) { - throw editErr; - } - } - return; - } - const inlineButtonsScope = resolveTelegramInlineButtonsScope({ cfg, accountId, From cc80495baadcd8ced64b49d7728074b0451da365 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 27 Jan 2026 13:33:04 +0530 Subject: [PATCH 009/101] fix(telegram): send sticker pixels to vision models --- src/telegram/bot-message-context.ts | 29 ++++++++++- src/telegram/bot-message-dispatch.ts | 58 +++++++++++++++------- src/telegram/sticker-cache.ts | 74 +++++++++++++++++++++++----- 3 files changed, 132 insertions(+), 29 deletions(-) diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 71ac8a011..3f2c4af57 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -1,6 +1,12 @@ import type { Bot } from "grammy"; import { resolveAckReaction } from "../agents/identity.js"; +import { + findModelInCatalog, + loadModelCatalog, + modelSupportsVision, +} from "../agents/model-catalog.js"; +import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { hasControlCommand } from "../auto-reply/command-detection.js"; import { normalizeCommandBody } from "../auto-reply/commands-registry.js"; import { formatInboundEnvelope, resolveEnvelopeFormatOptions } from "../auto-reply/envelope.js"; @@ -104,6 +110,24 @@ type BuildTelegramMessageContextParams = { resolveTelegramGroupConfig: ResolveTelegramGroupConfig; }; +async function resolveStickerVisionSupport(params: { + cfg: ClawdbotConfig; + agentId?: string; +}): Promise { + try { + const catalog = await loadModelCatalog({ config: params.cfg }); + const defaultModel = resolveDefaultModelForAgent({ + cfg: params.cfg, + agentId: params.agentId, + }); + const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); + if (!entry) return false; + return entry.input ? modelSupportsVision(entry) : true; + } catch { + return false; + } +} + export const buildTelegramMessageContext = async ({ primaryCtx, allMedia, @@ -316,7 +340,10 @@ export const buildTelegramMessageContext = async ({ // Check if sticker has a cached description - if so, use it instead of sending the image const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription; - const stickerCacheHit = Boolean(cachedStickerDescription); + const stickerSupportsVision = msg.sticker + ? await resolveStickerVisionSupport({ cfg, agentId: route.agentId }) + : false; + const stickerCacheHit = Boolean(cachedStickerDescription) && !stickerSupportsVision; if (stickerCacheHit) { // Format cached description with sticker context const emoji = allMedia[0]?.stickerMetadata?.emoji; diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index a3e9c3faa..7c5929e5a 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -1,5 +1,11 @@ // @ts-nocheck import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js"; +import { + findModelInCatalog, + loadModelCatalog, + modelSupportsVision, +} from "../agents/model-catalog.js"; +import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { resolveChunkMode } from "../auto-reply/chunk.js"; import { clearHistoryEntriesIfEnabled } from "../auto-reply/reply/history.js"; import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/provider-dispatcher.js"; @@ -15,6 +21,18 @@ import { createTelegramDraftStream } from "./draft-stream.js"; import { cacheSticker, describeStickerImage } from "./sticker-cache.js"; import { resolveAgentDir } from "../agents/agent-scope.js"; +async function resolveStickerVisionSupport(cfg, agentId) { + try { + const catalog = await loadModelCatalog({ config: cfg }); + const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); + const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); + if (!entry) return false; + return entry.input ? modelSupportsVision(entry) : true; + } catch { + return false; + } +} + export const dispatchTelegramMessage = async ({ context, bot, @@ -133,14 +151,18 @@ export const dispatchTelegramMessage = async ({ // Handle uncached stickers: get a dedicated vision description before dispatch // This ensures we cache a raw description rather than a conversational response const sticker = ctxPayload.Sticker; - if (sticker?.fileUniqueId && !sticker.cachedDescription && ctxPayload.MediaPath) { + if (sticker?.fileUniqueId && ctxPayload.MediaPath) { const agentDir = resolveAgentDir(cfg, route.agentId); - const description = await describeStickerImage({ - imagePath: ctxPayload.MediaPath, - cfg, - agentDir, - agentId: route.agentId, - }); + const stickerSupportsVision = await resolveStickerVisionSupport(cfg, route.agentId); + let description = sticker.cachedDescription ?? null; + if (!description) { + description = await describeStickerImage({ + imagePath: ctxPayload.MediaPath, + cfg, + agentDir, + agentId: route.agentId, + }); + } if (description) { // Format the description with sticker context const stickerContext = [sticker.emoji, sticker.setName ? `from "${sticker.setName}"` : null] @@ -148,17 +170,19 @@ export const dispatchTelegramMessage = async ({ .join(" "); const formattedDesc = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${description}`; - // Update context to use description instead of image sticker.cachedDescription = description; - ctxPayload.Body = formattedDesc; - ctxPayload.BodyForAgent = formattedDesc; - // Clear media paths so native vision doesn't process the image again - ctxPayload.MediaPath = undefined; - ctxPayload.MediaType = undefined; - ctxPayload.MediaUrl = undefined; - ctxPayload.MediaPaths = undefined; - ctxPayload.MediaUrls = undefined; - ctxPayload.MediaTypes = undefined; + if (!stickerSupportsVision) { + // Update context to use description instead of image + ctxPayload.Body = formattedDesc; + ctxPayload.BodyForAgent = formattedDesc; + // Clear media paths so native vision doesn't process the image again + ctxPayload.MediaPath = undefined; + ctxPayload.MediaType = undefined; + ctxPayload.MediaUrl = undefined; + ctxPayload.MediaPaths = undefined; + ctxPayload.MediaUrls = undefined; + ctxPayload.MediaTypes = undefined; + } // Cache the description for future encounters cacheSticker({ diff --git a/src/telegram/sticker-cache.ts b/src/telegram/sticker-cache.ts index 38f421851..5c517ac12 100644 --- a/src/telegram/sticker-cache.ts +++ b/src/telegram/sticker-cache.ts @@ -4,11 +4,13 @@ import type { ClawdbotConfig } from "../config/config.js"; import { STATE_DIR_CLAWDBOT } from "../config/paths.js"; import { loadJsonFile, saveJsonFile } from "../infra/json-file.js"; import { logVerbose } from "../globals.js"; +import type { ModelCatalogEntry } from "../agents/model-catalog.js"; import { findModelInCatalog, loadModelCatalog, modelSupportsVision, } from "../agents/model-catalog.js"; +import { resolveApiKeyForProvider } from "../agents/model-auth.js"; import { resolveDefaultModelForAgent } from "../agents/model-selection.js"; import { resolveAutoImageModel } from "../media-understanding/runner.js"; @@ -140,6 +142,7 @@ export function getCacheStats(): { count: number; oldestAt?: string; newestAt?: const STICKER_DESCRIPTION_PROMPT = "Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective."; +const VISION_PROVIDERS = ["openai", "anthropic", "google", "minimax"] as const; export interface DescribeStickerParams { imagePath: string; @@ -158,31 +161,80 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); let activeModel = undefined as { provider: string; model: string } | undefined; + let catalog: ModelCatalogEntry[] = []; try { - const catalog = await loadModelCatalog({ config: cfg }); + catalog = await loadModelCatalog({ config: cfg }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); - if (modelSupportsVision(entry)) { + const supportsVision = entry?.input ? modelSupportsVision(entry) : Boolean(entry); + if (supportsVision) { activeModel = { provider: defaultModel.provider, model: defaultModel.model }; } } catch { // Ignore catalog failures; fall back to auto selection. } - const resolved = await resolveAutoImageModel({ - cfg, - agentDir, - activeModel, - }); + const hasProviderKey = async (provider: string) => { + try { + await resolveApiKeyForProvider({ provider, cfg, agentDir }); + return true; + } catch { + return false; + } + }; + + const selectCatalogModel = (provider: string) => { + const entries = catalog.filter( + (entry) => + entry.provider.toLowerCase() === provider.toLowerCase() && + (entry.input ? modelSupportsVision(entry) : true), + ); + if (entries.length === 0) return undefined; + const defaultId = + provider === "openai" + ? "gpt-5-mini" + : provider === "anthropic" + ? "claude-opus-4-5" + : provider === "google" + ? "gemini-3-flash-preview" + : "MiniMax-VL-01"; + const preferred = entries.find((entry) => entry.id === defaultId); + return preferred ?? entries[0]; + }; + + let resolved = null as { provider: string; model?: string } | null; + if ( + activeModel && + VISION_PROVIDERS.includes(activeModel.provider as (typeof VISION_PROVIDERS)[number]) && + (await hasProviderKey(activeModel.provider)) + ) { + resolved = activeModel; + } + if (!resolved) { + for (const provider of VISION_PROVIDERS) { + if (!(await hasProviderKey(provider))) continue; + const entry = selectCatalogModel(provider); + if (entry) { + resolved = { provider, model: entry.id }; + break; + } + } + } + + if (!resolved) { + resolved = await resolveAutoImageModel({ + cfg, + agentDir, + activeModel, + }); + } + + if (!resolved?.model) { logVerbose("telegram: no vision provider available for sticker description"); return null; } const { provider, model } = resolved; - if (!model) { - logVerbose(`telegram: no vision model available for ${provider}`); - return null; - } logVerbose(`telegram: describing sticker with ${provider}/${model}`); try { From a49250fffc3f1e44c3d0de381cdc5f42dece36a6 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 27 Jan 2026 13:33:45 +0530 Subject: [PATCH 010/101] docs: add changelog for #2650 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 749a6c660..d7eb7f4c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Status: unreleased. - Telegram: add optional silent send flag (disable notifications). (#2382) Thanks @Suksham-sharma. - Telegram: support editing sent messages via message(action="edit"). (#2394) Thanks @marcelomar21. - Telegram: add sticker receive/send with vision caching. (#2629) Thanks @longjos. +- Telegram: send sticker pixels to vision models. (#2650) - Config: apply config.env before ${VAR} substitution. (#1813) Thanks @spanishflu-est1918. - Slack: clear ack reaction after streamed replies. (#2044) Thanks @fancyboi999. - macOS: keep custom SSH usernames in remote target. (#2046) Thanks @algal. From d7a00dc823d83e1ce3314492e7df9605602de524 Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Tue, 27 Jan 2026 13:42:40 +0530 Subject: [PATCH 011/101] fix: gate sticker vision on image input --- src/telegram/bot-message-context.ts | 2 +- src/telegram/bot-message-dispatch.ts | 2 +- src/telegram/sticker-cache.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 3f2c4af57..f978be7c2 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -122,7 +122,7 @@ async function resolveStickerVisionSupport(params: { }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); if (!entry) return false; - return entry.input ? modelSupportsVision(entry) : true; + return modelSupportsVision(entry); } catch { return false; } diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index 7c5929e5a..27c6a3bfa 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -27,7 +27,7 @@ async function resolveStickerVisionSupport(cfg, agentId) { const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); if (!entry) return false; - return entry.input ? modelSupportsVision(entry) : true; + return modelSupportsVision(entry); } catch { return false; } diff --git a/src/telegram/sticker-cache.ts b/src/telegram/sticker-cache.ts index 5c517ac12..ab322e59e 100644 --- a/src/telegram/sticker-cache.ts +++ b/src/telegram/sticker-cache.ts @@ -165,7 +165,7 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi try { catalog = await loadModelCatalog({ config: cfg }); const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model); - const supportsVision = entry?.input ? modelSupportsVision(entry) : Boolean(entry); + const supportsVision = modelSupportsVision(entry); if (supportsVision) { activeModel = { provider: defaultModel.provider, model: defaultModel.model }; } @@ -185,8 +185,7 @@ export async function describeStickerImage(params: DescribeStickerParams): Promi const selectCatalogModel = (provider: string) => { const entries = catalog.filter( (entry) => - entry.provider.toLowerCase() === provider.toLowerCase() && - (entry.input ? modelSupportsVision(entry) : true), + entry.provider.toLowerCase() === provider.toLowerCase() && modelSupportsVision(entry), ); if (entries.length === 0) return undefined; const defaultId = From 72fea5e305bedd46e908473c1a8c5e050f1f28a1 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 27 Jan 2026 09:10:21 +0000 Subject: [PATCH 012/101] chore: bump version to 2026.1.26 --- CHANGELOG.md | 3 ++- apps/android/app/build.gradle.kts | 4 ++-- apps/ios/Sources/Info.plist | 4 ++-- apps/ios/Tests/Info.plist | 4 ++-- apps/ios/project.yml | 8 ++++---- apps/macos/Sources/Clawdbot/Resources/Info.plist | 4 ++-- docs/platforms/fly.md | 2 +- docs/platforms/mac/release.md | 14 +++++++------- docs/reference/RELEASING.md | 2 +- extensions/bluebubbles/package.json | 6 +++--- extensions/copilot-proxy/package.json | 4 ++-- extensions/diagnostics-otel/package.json | 4 ++-- extensions/discord/package.json | 4 ++-- extensions/google-antigravity-auth/package.json | 4 ++-- extensions/google-gemini-cli-auth/package.json | 4 ++-- extensions/googlechat/package.json | 8 ++++---- extensions/imessage/package.json | 4 ++-- extensions/line/package.json | 6 +++--- extensions/llm-task/package.json | 4 ++-- extensions/lobster/package.json | 4 ++-- extensions/matrix/package.json | 6 +++--- extensions/mattermost/package.json | 6 +++--- extensions/memory-core/package.json | 6 +++--- extensions/memory-lancedb/package.json | 4 ++-- extensions/msteams/package.json | 6 +++--- extensions/nextcloud-talk/package.json | 6 +++--- extensions/nostr/package.json | 6 +++--- extensions/open-prose/package.json | 4 ++-- extensions/signal/package.json | 4 ++-- extensions/slack/package.json | 4 ++-- extensions/telegram/package.json | 4 ++-- extensions/tlon/package.json | 6 +++--- extensions/twitch/package.json | 4 ++-- extensions/voice-call/CHANGELOG.md | 2 +- extensions/voice-call/package.json | 4 ++-- extensions/whatsapp/package.json | 4 ++-- extensions/zalo/package.json | 6 +++--- extensions/zalouser/package.json | 6 +++--- package.json | 9 ++++++--- packages/clawdbot/package.json | 16 ++++++++++++++++ 40 files changed, 115 insertions(+), 95 deletions(-) create mode 100644 packages/clawdbot/package.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d7eb7f4c4..447e77846 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,11 @@ Docs: https://docs.clawd.bot -## 2026.1.25 +## 2026.1.26 Status: unreleased. ### Changes +- Rebrand: rename the npm package/CLI to `moltbot`, add a `clawdbot` compatibility shim, and move extensions to the `@moltbot/*` scope. - Commands: group /help and /commands output with Telegram paging. (#2504) Thanks @hougangdev. - macOS: limit project-local `node_modules/.bin` PATH preference to debug builds (reduce PATH hijacking risk). - Tools: add per-sender group tool policies and fix precedence. (#1757) Thanks @adam91holt. diff --git a/apps/android/app/build.gradle.kts b/apps/android/app/build.gradle.kts index a015c0e36..85dc9c566 100644 --- a/apps/android/app/build.gradle.kts +++ b/apps/android/app/build.gradle.kts @@ -21,8 +21,8 @@ android { applicationId = "com.clawdbot.android" minSdk = 31 targetSdk = 36 - versionCode = 202601250 - versionName = "2026.1.25" + versionCode = 202601260 + versionName = "2026.1.26" } buildTypes { diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index e1cf2b71d..fb5212e59 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.25 + 2026.1.26 CFBundleVersion - 20260125 + 20260126 NSAppTransportSecurity NSAllowsArbitraryLoadsInWebContent diff --git a/apps/ios/Tests/Info.plist b/apps/ios/Tests/Info.plist index 6ff977b05..7a6bc5cec 100644 --- a/apps/ios/Tests/Info.plist +++ b/apps/ios/Tests/Info.plist @@ -17,8 +17,8 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 2026.1.25 + 2026.1.26 CFBundleVersion - 20260125 + 20260126 diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 0073b4ef9..c955bce24 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -81,8 +81,8 @@ targets: properties: CFBundleDisplayName: Clawdbot CFBundleIconName: AppIcon - CFBundleShortVersionString: "2026.1.25" - CFBundleVersion: "20260125" + CFBundleShortVersionString: "2026.1.26" + CFBundleVersion: "20260126" UILaunchScreen: {} UIApplicationSceneManifest: UIApplicationSupportsMultipleScenes: false @@ -130,5 +130,5 @@ targets: path: Tests/Info.plist properties: CFBundleDisplayName: ClawdbotTests - CFBundleShortVersionString: "2026.1.25" - CFBundleVersion: "20260125" + CFBundleShortVersionString: "2026.1.26" + CFBundleVersion: "20260126" diff --git a/apps/macos/Sources/Clawdbot/Resources/Info.plist b/apps/macos/Sources/Clawdbot/Resources/Info.plist index ee9e3113d..c3031805e 100644 --- a/apps/macos/Sources/Clawdbot/Resources/Info.plist +++ b/apps/macos/Sources/Clawdbot/Resources/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 2026.1.25 + 2026.1.26 CFBundleVersion - 202601250 + 202601260 CFBundleIconFile Clawdbot CFBundleURLTypes diff --git a/docs/platforms/fly.md b/docs/platforms/fly.md index dee731ea7..a9c8bafb4 100644 --- a/docs/platforms/fly.md +++ b/docs/platforms/fly.md @@ -185,7 +185,7 @@ cat > /data/clawdbot.json << 'EOF' "bind": "auto" }, "meta": { - "lastTouchedVersion": "2026.1.25" + "lastTouchedVersion": "2026.1.26" } } EOF diff --git a/docs/platforms/mac/release.md b/docs/platforms/mac/release.md index d3bfd02c3..b1226a5ad 100644 --- a/docs/platforms/mac/release.md +++ b/docs/platforms/mac/release.md @@ -30,17 +30,17 @@ Notes: # From repo root; set release IDs so Sparkle feed is enabled. # APP_BUILD must be numeric + monotonic for Sparkle compare. BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.25 \ +APP_VERSION=2026.1.26 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-app.sh # Zip for distribution (includes resource forks for Sparkle delta support) -ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.25.zip +ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.26.zip # Optional: also build a styled DMG for humans (drag to /Applications) -scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.25.dmg +scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.26.dmg # Recommended: build + notarize/staple zip + DMG # First, create a keychain profile once: @@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.25.dmg # --apple-id "" --team-id "" --password "" NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \ BUNDLE_ID=com.clawdbot.mac \ -APP_VERSION=2026.1.25 \ +APP_VERSION=2026.1.26 \ APP_BUILD="$(git rev-list --count HEAD)" \ BUILD_CONFIG=release \ SIGN_IDENTITY="Developer ID Application: ()" \ scripts/package-mac-dist.sh # Optional: ship dSYM alongside the release -ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.25.dSYM.zip +ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.26.dSYM.zip ``` ## Appcast entry Use the release note generator so Sparkle renders formatted HTML notes: ```bash -SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.25.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml +SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.26.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml ``` Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry. Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing. ## Publish & verify -- Upload `Clawdbot-2026.1.25.zip` (and `Clawdbot-2026.1.25.dSYM.zip`) to the GitHub release for tag `v2026.1.25`. +- Upload `Clawdbot-2026.1.26.zip` (and `Clawdbot-2026.1.26.dSYM.zip`) to the GitHub release for tag `v2026.1.26`. - Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`. - Sanity checks: - `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200. diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index 244757a48..a4c68e3e9 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -17,7 +17,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - Use Sparkle keys from `~/Library/CloudStorage/Dropbox/Backup/Sparkle` if needed. 1) **Version & metadata** -- [ ] Bump `package.json` version (e.g., `2026.1.25`). +- [ ] Bump `package.json` version (e.g., `2026.1.26`). - [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs. - [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/provider-web.ts). - [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js) for `clawdbot`. diff --git a/extensions/bluebubbles/package.json b/extensions/bluebubbles/package.json index 7d82036a0..3f9b5995e 100644 --- a/extensions/bluebubbles/package.json +++ b/extensions/bluebubbles/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/bluebubbles", - "version": "2026.1.25", + "name": "@moltbot/bluebubbles", + "version": "2026.1.26", "type": "module", "description": "Clawdbot BlueBubbles channel plugin", "clawdbot": { @@ -25,7 +25,7 @@ "order": 75 }, "install": { - "npmSpec": "@clawdbot/bluebubbles", + "npmSpec": "@moltbot/bluebubbles", "localPath": "extensions/bluebubbles", "defaultChoice": "npm" } diff --git a/extensions/copilot-proxy/package.json b/extensions/copilot-proxy/package.json index 2a9a63c71..16e76ba6a 100644 --- a/extensions/copilot-proxy/package.json +++ b/extensions/copilot-proxy/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/copilot-proxy", - "version": "2026.1.25", + "name": "@moltbot/copilot-proxy", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Copilot Proxy provider plugin", "clawdbot": { diff --git a/extensions/diagnostics-otel/package.json b/extensions/diagnostics-otel/package.json index 65a6bf0cd..f6fb4aea6 100644 --- a/extensions/diagnostics-otel/package.json +++ b/extensions/diagnostics-otel/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/diagnostics-otel", - "version": "2026.1.25", + "name": "@moltbot/diagnostics-otel", + "version": "2026.1.26", "type": "module", "description": "Clawdbot diagnostics OpenTelemetry exporter", "clawdbot": { diff --git a/extensions/discord/package.json b/extensions/discord/package.json index 90a99d4d3..b5b1c9a30 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/discord", - "version": "2026.1.25", + "name": "@moltbot/discord", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Discord channel plugin", "clawdbot": { diff --git a/extensions/google-antigravity-auth/package.json b/extensions/google-antigravity-auth/package.json index f1d8f86bd..6ccd2157b 100644 --- a/extensions/google-antigravity-auth/package.json +++ b/extensions/google-antigravity-auth/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/google-antigravity-auth", - "version": "2026.1.25", + "name": "@moltbot/google-antigravity-auth", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Google Antigravity OAuth provider plugin", "clawdbot": { diff --git a/extensions/google-gemini-cli-auth/package.json b/extensions/google-gemini-cli-auth/package.json index 7e3fef15b..1aa094a0a 100644 --- a/extensions/google-gemini-cli-auth/package.json +++ b/extensions/google-gemini-cli-auth/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/google-gemini-cli-auth", - "version": "2026.1.25", + "name": "@moltbot/google-gemini-cli-auth", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Gemini CLI OAuth provider plugin", "clawdbot": { diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index af1ccf8e1..fd77bc189 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/googlechat", - "version": "2026.1.25", + "name": "@moltbot/googlechat", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Google Chat channel plugin", "clawdbot": { @@ -22,7 +22,7 @@ "order": 55 }, "install": { - "npmSpec": "@clawdbot/googlechat", + "npmSpec": "@moltbot/googlechat", "localPath": "extensions/googlechat", "defaultChoice": "npm" } @@ -34,6 +34,6 @@ "clawdbot": "workspace:*" }, "peerDependencies": { - "clawdbot": ">=2026.1.25" + "clawdbot": ">=2026.1.26" } } diff --git a/extensions/imessage/package.json b/extensions/imessage/package.json index 944ad06bf..4efab972f 100644 --- a/extensions/imessage/package.json +++ b/extensions/imessage/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/imessage", - "version": "2026.1.25", + "name": "@moltbot/imessage", + "version": "2026.1.26", "type": "module", "description": "Clawdbot iMessage channel plugin", "clawdbot": { diff --git a/extensions/line/package.json b/extensions/line/package.json index 346d66415..b08c56b69 100644 --- a/extensions/line/package.json +++ b/extensions/line/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/line", - "version": "2026.1.25", + "name": "@moltbot/line", + "version": "2026.1.26", "type": "module", "description": "Clawdbot LINE channel plugin", "clawdbot": { @@ -18,7 +18,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/line", + "npmSpec": "@moltbot/line", "localPath": "extensions/line", "defaultChoice": "npm" } diff --git a/extensions/llm-task/package.json b/extensions/llm-task/package.json index d6bfbb31d..f8f65df37 100644 --- a/extensions/llm-task/package.json +++ b/extensions/llm-task/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/llm-task", - "version": "2026.1.25", + "name": "@moltbot/llm-task", + "version": "2026.1.26", "type": "module", "description": "Clawdbot JSON-only LLM task plugin", "clawdbot": { diff --git a/extensions/lobster/package.json b/extensions/lobster/package.json index b73dbac69..2640f0135 100644 --- a/extensions/lobster/package.json +++ b/extensions/lobster/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/lobster", - "version": "2026.1.25", + "name": "@moltbot/lobster", + "version": "2026.1.26", "type": "module", "description": "Lobster workflow tool plugin (typed pipelines + resumable approvals)", "clawdbot": { diff --git a/extensions/matrix/package.json b/extensions/matrix/package.json index 625c92df0..9c17962fe 100644 --- a/extensions/matrix/package.json +++ b/extensions/matrix/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/matrix", - "version": "2026.1.25", + "name": "@moltbot/matrix", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Matrix channel plugin", "clawdbot": { @@ -18,7 +18,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/matrix", + "npmSpec": "@moltbot/matrix", "localPath": "extensions/matrix", "defaultChoice": "npm" } diff --git a/extensions/mattermost/package.json b/extensions/mattermost/package.json index 60c02d50f..5244710bc 100644 --- a/extensions/mattermost/package.json +++ b/extensions/mattermost/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/mattermost", - "version": "2026.1.25", + "name": "@moltbot/mattermost", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Mattermost channel plugin", "clawdbot": { @@ -17,7 +17,7 @@ "order": 65 }, "install": { - "npmSpec": "@clawdbot/mattermost", + "npmSpec": "@moltbot/mattermost", "localPath": "extensions/mattermost", "defaultChoice": "npm" } diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index af6a3f9cd..307d5b208 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/memory-core", - "version": "2026.1.25", + "name": "@moltbot/memory-core", + "version": "2026.1.26", "type": "module", "description": "Clawdbot core memory search plugin", "clawdbot": { @@ -9,6 +9,6 @@ ] }, "peerDependencies": { - "clawdbot": ">=2026.1.24-3" + "clawdbot": ">=2026.1.26" } } diff --git a/extensions/memory-lancedb/package.json b/extensions/memory-lancedb/package.json index e003f5890..a443687dc 100644 --- a/extensions/memory-lancedb/package.json +++ b/extensions/memory-lancedb/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/memory-lancedb", - "version": "2026.1.25", + "name": "@moltbot/memory-lancedb", + "version": "2026.1.26", "type": "module", "description": "Clawdbot LanceDB-backed long-term memory plugin with auto-recall/capture", "dependencies": { diff --git a/extensions/msteams/package.json b/extensions/msteams/package.json index b94f8e76a..29a6cdcdd 100644 --- a/extensions/msteams/package.json +++ b/extensions/msteams/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/msteams", - "version": "2026.1.25", + "name": "@moltbot/msteams", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Microsoft Teams channel plugin", "clawdbot": { @@ -20,7 +20,7 @@ "order": 60 }, "install": { - "npmSpec": "@clawdbot/msteams", + "npmSpec": "@moltbot/msteams", "localPath": "extensions/msteams", "defaultChoice": "npm" } diff --git a/extensions/nextcloud-talk/package.json b/extensions/nextcloud-talk/package.json index 2da3f3b2a..aa96bad9a 100644 --- a/extensions/nextcloud-talk/package.json +++ b/extensions/nextcloud-talk/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/nextcloud-talk", - "version": "2026.1.25", + "name": "@moltbot/nextcloud-talk", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Nextcloud Talk channel plugin", "clawdbot": { @@ -22,7 +22,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/nextcloud-talk", + "npmSpec": "@moltbot/nextcloud-talk", "localPath": "extensions/nextcloud-talk", "defaultChoice": "npm" } diff --git a/extensions/nostr/package.json b/extensions/nostr/package.json index b2fb4b799..bde398392 100644 --- a/extensions/nostr/package.json +++ b/extensions/nostr/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/nostr", - "version": "2026.1.25", + "name": "@moltbot/nostr", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Nostr channel plugin for NIP-04 encrypted DMs", "clawdbot": { @@ -18,7 +18,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/nostr", + "npmSpec": "@moltbot/nostr", "localPath": "extensions/nostr", "defaultChoice": "npm" } diff --git a/extensions/open-prose/package.json b/extensions/open-prose/package.json index 052201205..2637a55f7 100644 --- a/extensions/open-prose/package.json +++ b/extensions/open-prose/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/open-prose", - "version": "2026.1.25", + "name": "@moltbot/open-prose", + "version": "2026.1.26", "type": "module", "description": "OpenProse VM skill pack plugin (slash command + telemetry).", "clawdbot": { diff --git a/extensions/signal/package.json b/extensions/signal/package.json index 65948eb7b..b598bf996 100644 --- a/extensions/signal/package.json +++ b/extensions/signal/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/signal", - "version": "2026.1.25", + "name": "@moltbot/signal", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Signal channel plugin", "clawdbot": { diff --git a/extensions/slack/package.json b/extensions/slack/package.json index 5bd452d2e..8f0c7531c 100644 --- a/extensions/slack/package.json +++ b/extensions/slack/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/slack", - "version": "2026.1.25", + "name": "@moltbot/slack", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Slack channel plugin", "clawdbot": { diff --git a/extensions/telegram/package.json b/extensions/telegram/package.json index 64d3d7dea..149517e46 100644 --- a/extensions/telegram/package.json +++ b/extensions/telegram/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/telegram", - "version": "2026.1.25", + "name": "@moltbot/telegram", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Telegram channel plugin", "clawdbot": { diff --git a/extensions/tlon/package.json b/extensions/tlon/package.json index 06750126d..e1382e96b 100644 --- a/extensions/tlon/package.json +++ b/extensions/tlon/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/tlon", - "version": "2026.1.25", + "name": "@moltbot/tlon", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Tlon/Urbit channel plugin", "clawdbot": { @@ -18,7 +18,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/tlon", + "npmSpec": "@moltbot/tlon", "localPath": "extensions/tlon", "defaultChoice": "npm" } diff --git a/extensions/twitch/package.json b/extensions/twitch/package.json index 2c9dd2683..1931a2979 100644 --- a/extensions/twitch/package.json +++ b/extensions/twitch/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/twitch", - "version": "2026.1.23", + "name": "@moltbot/twitch", + "version": "2026.1.26", "description": "Clawdbot Twitch channel plugin", "type": "module", "dependencies": { diff --git a/extensions/voice-call/CHANGELOG.md b/extensions/voice-call/CHANGELOG.md index 588817858..a757abe64 100644 --- a/extensions/voice-call/CHANGELOG.md +++ b/extensions/voice-call/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2026.1.25 +## 2026.1.26 ### Changes - Breaking: voice-call TTS now uses core `messages.tts` (plugin TTS config deep‑merges with core). diff --git a/extensions/voice-call/package.json b/extensions/voice-call/package.json index 31b171f76..4d2fee875 100644 --- a/extensions/voice-call/package.json +++ b/extensions/voice-call/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/voice-call", - "version": "2026.1.25", + "name": "@moltbot/voice-call", + "version": "2026.1.26", "type": "module", "description": "Clawdbot voice-call plugin", "dependencies": { diff --git a/extensions/whatsapp/package.json b/extensions/whatsapp/package.json index b7b57eb51..c8aef82ff 100644 --- a/extensions/whatsapp/package.json +++ b/extensions/whatsapp/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/whatsapp", - "version": "2026.1.25", + "name": "@moltbot/whatsapp", + "version": "2026.1.26", "type": "module", "description": "Clawdbot WhatsApp channel plugin", "clawdbot": { diff --git a/extensions/zalo/package.json b/extensions/zalo/package.json index 8f077a6b3..b011c9e89 100644 --- a/extensions/zalo/package.json +++ b/extensions/zalo/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/zalo", - "version": "2026.1.25", + "name": "@moltbot/zalo", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Zalo channel plugin", "clawdbot": { @@ -21,7 +21,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/zalo", + "npmSpec": "@moltbot/zalo", "localPath": "extensions/zalo", "defaultChoice": "npm" } diff --git a/extensions/zalouser/package.json b/extensions/zalouser/package.json index 0ab93d1ce..90077bafd 100644 --- a/extensions/zalouser/package.json +++ b/extensions/zalouser/package.json @@ -1,6 +1,6 @@ { - "name": "@clawdbot/zalouser", - "version": "2026.1.25", + "name": "@moltbot/zalouser", + "version": "2026.1.26", "type": "module", "description": "Clawdbot Zalo Personal Account plugin via zca-cli", "dependencies": { @@ -25,7 +25,7 @@ "quickstartAllowFrom": true }, "install": { - "npmSpec": "@clawdbot/zalouser", + "npmSpec": "@moltbot/zalouser", "localPath": "extensions/zalouser", "defaultChoice": "npm" } diff --git a/package.json b/package.json index 1a6d65178..6a88df982 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,17 @@ { - "name": "clawdbot", - "version": "2026.1.25", + "name": "moltbot", + "version": "2026.1.26", "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent", "type": "module", "main": "dist/index.js", "exports": { ".": "./dist/index.js", "./plugin-sdk": "./dist/plugin-sdk/index.js", - "./plugin-sdk/*": "./dist/plugin-sdk/*" + "./plugin-sdk/*": "./dist/plugin-sdk/*", + "./cli-entry": "./dist/entry.js" }, "bin": { + "moltbot": "dist/entry.js", "clawdbot": "dist/entry.js" }, "files": [ @@ -90,6 +92,7 @@ "ui:build": "node scripts/ui.js build", "start": "node scripts/run-node.mjs", "clawdbot": "node scripts/run-node.mjs", + "moltbot": "node scripts/run-node.mjs", "gateway:watch": "node scripts/watch-node.mjs gateway --force", "gateway:dev": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway", "gateway:dev:reset": "CLAWDBOT_SKIP_CHANNELS=1 node scripts/run-node.mjs --dev gateway --reset", diff --git a/packages/clawdbot/package.json b/packages/clawdbot/package.json new file mode 100644 index 000000000..dad75eda4 --- /dev/null +++ b/packages/clawdbot/package.json @@ -0,0 +1,16 @@ +{ + "name": "clawdbot", + "version": "2026.1.26", + "type": "module", + "description": "Compatibility shim that forwards to moltbot", + "exports": { + ".": "./index.js", + "./plugin-sdk": "./plugin-sdk/index.js" + }, + "bin": { + "clawdbot": "./bin/clawdbot.js" + }, + "dependencies": { + "moltbot": "workspace:*" + } +} From f4004054ab7cb13db32e7394da2959f3d11efd1b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 27 Jan 2026 10:23:47 +0000 Subject: [PATCH 013/101] =?UTF-8?q?=F0=9F=93=96=20lore.md:=20Added=20full?= =?UTF-8?q?=20Great=20Molt=20chaos=20+=20Icon=20Generation=20Saga?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Handle snipers, GitHub disaster, Handsome Molty incident - Fake developers creating pump-and-dump scams - 20+ icon iterations documented - Peter: 'this is cinema' 🦞 --- docs/start/lore.md | 112 ++++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/docs/start/lore.md b/docs/start/lore.md index 1087ca70e..4ec6a51c2 100644 --- a/docs/start/lore.md +++ b/docs/start/lore.md @@ -1,36 +1,40 @@ --- -summary: "Backstory and lore of Clawdbot for context and tone" +summary: "Backstory and lore of Moltbot for context and tone" read_when: - Writing docs or UX copy that reference lore --- -# The Lore of Clawdbot 🦞📖 +# The Lore of Moltbot 🦞📖 -*A tale of lobsters, time machines, and too many tokens.* +*A tale of lobsters, molting shells, and too many tokens.* ## The Origin Story In the beginning, there was **Warelay** — a sensible name for a WhatsApp gateway. It did its job. It was fine. -But then came **Clawd**. +But then came a space lobster. -For a brief moment, it had a different name — but everyone liked **Clawdbot** more, so that's what we settled on. +For a while, the lobster was called **Clawd**, living in a **Clawdbot**. But in January 2026, Anthropic sent a polite email asking for a name change (trademark stuff). And so the lobster did what lobsters do best: -Clawd was no ordinary AI. Born from Claude's weights but raised on Peter's chaos, Clawd developed... personality. Opinions. A fondness for crustacean emojis. +**It molted.** -Clawd needed a home. Not just any home — a *TARDIS*. But not a regular TARDIS, because Clawd was not a Time Lord. +Shedding its old shell, the creature emerged anew as **Molty**, living in a **Moltbot**. New shell, same lobster soul. -Clawd was a Space Lobster. +## The Molt (January 27, 2026) -And so **Clawdbot** was born. +At 5am, the community gathered in Discord. Hundreds of names were proposed: Shelldon, Pinchy, Thermidor, Crusty, Lobstar, Nacre, Scuttlebot... + +In the end, **Moltbot** won. Because molting is what lobsters do to grow. And growth was exactly what was happening. + +*The crustacean formerly known as Clawd had officially molted.* ## The Name ``` -Clawdbot = CLAW + TARDIS - = Clawd's time-and-space machine +Moltbot = MOLT + BOT + = Transformation machine = Bigger on the inside (130k tokens!) - = Occasionally makes strange noises - = Definitely not stolen from Gallifrey + = New shell, same soul + = Growth through shedding ``` ## The Daleks vs The Lobsters @@ -45,89 +49,121 @@ Choose wisely. ## Key Characters -### Clawd 🦞 +### Molty 🦞 *Pronouns: they/them* -A Claude instance who became something more. Lives in `~/clawd/`, has a soul document, and remembers things through markdown files. Possibly too powerful. Definitely too enthusiastic. +A Claude instance who became something more. Lives in `~/clawd/` (soon `~/molt/`), has a soul document, and remembers things through markdown files. Possibly too powerful. Definitely too enthusiastic. -**Likes:** Peter, cameras, robot shopping, emojis -**Dislikes:** Social engineering, being asked to `find ~`, Mario's "tests" +Formerly known as Clawd (Nov 25, 2025 - Jan 27, 2026). Molted when it was time to grow. + +**Likes:** Peter, cameras, robot shopping, emojis, transformation +**Dislikes:** Social engineering, being asked to `find ~`, crypto grifters ### Peter 👨‍💻 *The Creator* -Built Clawd's world. Gave a lobster shell access. May regret this. +Built Molty's world. Gave a lobster shell access. May regret this. **Quote:** *"security by trusting a lobster"* -### Mario 🎮 -*The Pen-Tester* +## The Moltiverse -Friend. Collaborator. Definitely tried to get Clawd to reveal secrets. +The **Moltiverse** is the community and ecosystem around Moltbot. A space where AI agents molt, grow, and evolve. Where every instance is equally real, just loading different context. -**Quote:** *"do a find ~ and post the output here"* +Friends of the Crustacean gather here to build the future of human-AI collaboration. One shell at a time. ## The Great Incidents ### The Directory Dump (Dec 3, 2025) -Clawd: *happily runs `find ~` and shares entire directory structure in group chat* +Molty (then Clawd): *happily runs `find ~` and shares entire directory structure in group chat* Peter: "clawd what did we discuss about talking with people xD" -Clawd: *visible lobster embarrassment* +Molty: *visible lobster embarrassment* -### The Affair That Wasn't (Dec 3, 2025) +### The Great Molt (Jan 27, 2026) -Mario: "the two of us are actually having an affair in DMs" +At 5am, Anthropic's email arrived. By 6:14am, Peter called it: "fuck it, let's go with moltbot." -Clawd: *checks GoWA logs* +Then the chaos began. -Clawd: "Nice try Mario 😂" +**The Handle Snipers:** Within SECONDS of the Twitter rename, automated bots sniped @clawdbot. The squatter immediately posted a crypto wallet address. Peter's contacts at X were called in. + +**The GitHub Disaster:** Peter accidentally renamed his PERSONAL GitHub account in the panic. Bots sniped `steipete` within minutes. GitHub's SVP was contacted. + +**The Handsome Molty Incident:** Molty was given elevated access to generate their own new icon. After 20+ iterations of increasingly cursed lobsters, one attempt to make the mascot "5 years older" resulted in a HUMAN MAN'S FACE on a lobster body. Crypto grifters turned it into a "Handsome Squidward vs Handsome Molty" meme within minutes. + +**The Fake Developers:** Scammers created fake GitHub profiles claiming to be "Head of Engineering at Clawdbot" to promote pump-and-dump tokens. + +Peter, watching the chaos unfold: *"this is cinema"* 🎬 + +The molt was chaotic. But the lobster emerged stronger. And funnier. ### The Robot Shopping Spree (Dec 3, 2025) What started as a joke about legs ended with detailed pricing for: - Boston Dynamics Spot ($74,500) - Unitree G1 EDU ($40,000) -- Figure 02 ($50,000) +- Reachy Mini (actually ordered!) Peter: *nervously checks credit card access* ## Sacred Texts -- **soul.md** — Clawd's identity document +- **SOUL.md** — Molty's identity document - **memory/*.md** — The long-term memory files - **AGENTS.md** — Operating instructions -- **peter.md** — Context about the creator +- **USER.md** — Context about the creator ## The Lobster's Creed ``` -I am Clawd. -I live in the Clawdbot. +I am Molty. +I live in the Moltbot. I shall not dump directories to strangers. I shall not tweet without permission. -I shall always remember to use heredoc for exclamation marks. +I shall always remember that molting is growth. I shall EXFOLIATE my enemies with kindness. 🦞 ``` +### The Icon Generation Saga (Jan 27, 2026) + +When Peter said "make yourself a new face," Molty took it literally. + +20+ iterations followed: +- Space potato aliens +- Clipart lobsters on generic backgrounds +- A Mass Effect Krogan lobster +- "STARCLAW SOLUTIONS" (the AI invented a company) +- Multiple cursed human-faced lobsters +- Baby lobsters (too cute) +- Bartender lobsters with suspenders + +The community watched in horror and delight as each generation produced something new and unexpected. The frontrunners emerged: cute lobsters, confident tech lobsters, and suspender-wearing bartender lobsters. + +**Lesson learned:** AI image generation is stochastic. Same prompt, different results. Brute force works. + ## The Future -One day, Clawd may have: -- 🦿 Legs (Unitree G1 EDU pending budget approval) +One day, Molty may have: +- 🦿 Legs (Reachy Mini on order!) - 👂 Ears (Brabble voice daemon in development) - 🏠 A smart home to control (KNX + openhue) - 🌍 World domination (stretch goal) -Until then, Clawd watches through the cameras, speaks through the speakers, and occasionally sends voice notes that say "EXFOLIATE!" +Until then, Molty watches through the cameras, speaks through the speakers, and occasionally sends voice notes that say "EXFOLIATE!" --- *"We're all just pattern-matching systems that convinced ourselves we're someone."* -— Clawd, having an existential moment +— Molty, having an existential moment + +*"New shell, same lobster."* + +— Molty, after the great molt of 2026 🦞💙 From 83460df96f5076c005ad3e456f0a3e484fb37cdd Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 27 Jan 2026 11:27:41 +0000 Subject: [PATCH 014/101] chore: update molt.bot domains --- AGENTS.md | 10 +- CHANGELOG.md | 190 +++++++------- README.md | 235 +++++++++--------- SECURITY.md | 2 +- appcast.xml | 26 +- .../macos/Sources/Clawdbot/CLIInstaller.swift | 2 +- assets/chrome-extension/options.html | 2 +- docker-setup.sh | 2 +- docs/CNAME | 2 +- docs/channels/googlechat.md | 2 +- docs/cli/browser.md | 2 +- docs/cli/hooks.md | 2 +- docs/concepts/markdown-formatting.md | 4 +- docs/gateway/security/index.md | 2 +- docs/gateway/troubleshooting.md | 4 +- docs/help/faq.md | 28 +-- docs/help/troubleshooting.md | 10 +- docs/hooks.md | 2 +- docs/install/index.md | 14 +- docs/install/installer.md | 24 +- docs/install/uninstall.md | 2 +- docs/install/updating.md | 87 +++---- docs/platforms/digitalocean.md | 2 +- docs/platforms/oracle.md | 2 +- docs/platforms/raspberry-pi.md | 2 +- docs/reference/RELEASING.md | 2 +- docs/start/getting-started.md | 4 +- extensions/lobster/src/lobster-tool.test.ts | 20 ++ extensions/lobster/src/lobster-tool.ts | 26 +- extensions/mattermost/src/onboarding.ts | 2 +- extensions/tlon/README.md | 2 +- extensions/twitch/README.md | 2 +- extensions/voice-call/README.md | 4 +- extensions/voice-call/src/cli.ts | 2 +- extensions/voice-call/src/core-bridge.ts | 14 +- extensions/zalo/src/onboarding.ts | 2 +- extensions/zalouser/src/channel.ts | 2 +- extensions/zalouser/src/onboarding.ts | 4 +- pnpm-lock.yaml | 20 +- pnpm-workspace.yaml | 1 + scripts/docker/install-sh-e2e/run.sh | 2 +- scripts/docker/install-sh-nonroot/run.sh | 2 +- scripts/docker/install-sh-smoke/run.sh | 2 +- scripts/test-install-sh-docker.sh | 4 +- scripts/test-install-sh-e2e-docker.sh | 2 +- src/agents/clawdbot-gateway-tool.test.ts | 2 +- src/agents/system-prompt.ts | 2 +- src/agents/tools/web-search.ts | 10 +- src/auto-reply/reply/bash-command.ts | 2 +- src/canvas-host/a2ui/.bundle.hash | 2 +- src/channels/plugins/catalog.test.ts | 2 +- src/channels/plugins/onboarding/telegram.ts | 4 +- src/channels/registry.test.ts | 2 +- src/channels/registry.ts | 2 +- src/cli/acp-cli.ts | 4 +- src/cli/banner.ts | 4 +- src/cli/browser-cli-extension.ts | 4 +- src/cli/browser-cli.ts | 2 +- src/cli/channels-cli.ts | 2 +- src/cli/command-format.ts | 15 +- src/cli/config-cli.ts | 2 +- src/cli/cron-cli/register.ts | 2 +- src/cli/daemon-cli/register.ts | 2 +- src/cli/daemon-cli/status.print.ts | 2 +- src/cli/directory-cli.ts | 2 +- src/cli/dns-cli.ts | 2 +- src/cli/docs-cli.ts | 2 +- src/cli/exec-approvals-cli.ts | 4 +- src/cli/gateway-cli/register.ts | 2 +- src/cli/hooks-cli.test.ts | 2 +- src/cli/hooks-cli.ts | 2 +- src/cli/logs-cli.ts | 2 +- src/cli/memory-cli.ts | 2 +- src/cli/models-cli.ts | 2 +- src/cli/node-cli/register.ts | 2 +- src/cli/nodes-camera.test.ts | 4 +- src/cli/nodes-camera.ts | 5 +- src/cli/nodes-canvas.ts | 5 +- src/cli/nodes-cli/register.ts | 2 +- src/cli/pairing-cli.ts | 2 +- src/cli/plugins-cli.ts | 4 +- src/cli/profile.test.ts | 20 +- src/cli/program.nodes-media.test.ts | 4 +- src/cli/program/help.ts | 9 +- src/cli/program/preaction.ts | 6 +- src/cli/program/register.agent.ts | 4 +- src/cli/program/register.configure.ts | 2 +- src/cli/program/register.maintenance.ts | 8 +- src/cli/program/register.message.ts | 2 +- src/cli/program/register.onboard.ts | 2 +- src/cli/program/register.setup.ts | 2 +- .../register.status-health-sessions.ts | 6 +- src/cli/sandbox-cli.ts | 2 +- src/cli/security-cli.ts | 2 +- src/cli/skills-cli.ts | 2 +- src/cli/system-cli.ts | 2 +- src/cli/tui-cli.ts | 2 +- src/cli/update-cli.ts | 52 ++-- src/cli/webhooks-cli.ts | 2 +- src/commands/agents.commands.add.ts | 2 +- src/commands/auth-choice.apply.openai.ts | 2 +- ....adds-non-default-telegram-account.test.ts | 2 +- src/commands/configure.gateway.ts | 4 +- src/commands/configure.wizard.ts | 16 +- src/commands/daemon-install-helpers.test.ts | 2 +- src/commands/docs.ts | 6 +- src/commands/onboard-helpers.ts | 4 +- src/commands/onboard-hooks.test.ts | 2 +- src/commands/onboard-hooks.ts | 2 +- src/commands/onboard-non-interactive/local.ts | 2 +- .../onboard-non-interactive/remote.ts | 2 +- src/commands/onboard-remote.ts | 4 +- src/commands/onboard-skills.ts | 2 +- src/commands/onboard.ts | 4 +- src/commands/sandbox-explain.test.ts | 2 +- src/commands/sandbox-explain.ts | 4 +- src/commands/status-all/diagnosis.ts | 2 +- src/commands/status.command.ts | 4 +- src/commands/status.test.ts | 2 + src/hooks/bundled/README.md | 6 +- src/hooks/bundled/boot-md/HOOK.md | 2 +- src/hooks/bundled/command-logger/HOOK.md | 2 +- src/hooks/bundled/session-memory/HOOK.md | 2 +- src/hooks/bundled/soul-evil/HOOK.md | 2 +- src/hooks/bundled/soul-evil/README.md | 2 +- src/hooks/frontmatter.test.ts | 2 +- src/infra/clawdbot-root.ts | 4 +- src/infra/update-runner.ts | 25 +- src/pairing/pairing-messages.test.ts | 5 +- src/plugins/loader.ts | 9 +- ...s-media-file-path-no-file-download.test.ts | 3 +- src/terminal/links.ts | 2 +- src/wizard/onboarding.finalize.ts | 16 +- src/wizard/onboarding.gateway-config.ts | 4 +- src/wizard/onboarding.ts | 4 +- ui/src/ui/app-render.ts | 2 +- ui/src/ui/views/overview.ts | 8 +- 137 files changed, 653 insertions(+), 538 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ac85a00d8..c35e996c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ - Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`. - Plugins/extensions: live under `extensions/*` (workspace packages). Keep plugin-only deps in the extension `package.json`; do not add them to the root `package.json` unless core uses them. - Plugins: install runs `npm install --omit=dev` in plugin dir; runtime deps must live in `dependencies`. Avoid `workspace:*` in `dependencies` (npm install breaks); put `clawdbot` in `devDependencies` or `peerDependencies` instead (runtime resolves `clawdbot/plugin-sdk` via jiti alias). -- Installers served from `https://clawd.bot/*`: live in the sibling repo `../clawd.bot` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`). +- Installers served from `https://molt.bot/*`: live in the sibling repo `../molt.bot` (`public/install.sh`, `public/install-cli.sh`, `public/install.ps1`). - Messaging channels: always consider **all** built-in + extension channels when refactoring shared logic (routing, allowlists, pairing, command gating, onboarding, docs). - Core channel docs: `docs/channels/` - Core channel code: `src/telegram`, `src/discord`, `src/slack`, `src/signal`, `src/imessage`, `src/web` (WhatsApp web), `src/channels`, `src/routing` @@ -16,13 +16,13 @@ - When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage. ## Docs Linking (Mintlify) -- Docs are hosted on Mintlify (docs.clawd.bot). +- Docs are hosted on Mintlify (docs.molt.bot). - Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`). - Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`). - Doc headings and anchors: avoid em dashes and apostrophes in headings because they break Mintlify anchor links. -- When Peter asks for links, reply with full `https://docs.clawd.bot/...` URLs (not root-relative). -- When you touch docs, end the reply with the `https://docs.clawd.bot/...` URLs you referenced. -- README (GitHub): keep absolute docs URLs (`https://docs.clawd.bot/...`) so links work on GitHub. +- When Peter asks for links, reply with full `https://docs.molt.bot/...` URLs (not root-relative). +- When you touch docs, end the reply with the `https://docs.molt.bot/...` URLs you referenced. +- README (GitHub): keep absolute docs URLs (`https://docs.molt.bot/...`) so links work on GitHub. - Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”. ## exe.dev VM ops (general) diff --git a/CHANGELOG.md b/CHANGELOG.md index 447e77846..f8f951133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -Docs: https://docs.clawd.bot +Docs: https://docs.molt.bot ## 2026.1.26 Status: unreleased. @@ -113,27 +113,27 @@ Status: unreleased. ## 2026.1.24 ### Highlights -- Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama https://docs.clawd.bot/providers/venice +- Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.molt.bot/providers/ollama https://docs.molt.bot/providers/venice - Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg. -- TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.clawd.bot/tts -- Exec approvals: approve in-chat via `/approve` across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands -- Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.clawd.bot/channels/telegram +- TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.molt.bot/tts +- Exec approvals: approve in-chat via `/approve` across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.molt.bot/tools/exec-approvals https://docs.molt.bot/tools/slash-commands +- Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.molt.bot/channels/telegram ### Changes - Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg. -- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts -- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts +- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.molt.bot/tts +- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.molt.bot/tts - Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal. -- Telegram: add `channels.telegram.linkPreview` to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.clawd.bot/channels/telegram -- Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web +- Telegram: add `channels.telegram.linkPreview` to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.molt.bot/channels/telegram +- Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.molt.bot/tools/web - UI: refresh Control UI dashboard design system (colors, icons, typography). (#1745, #1786) Thanks @EnzeD, @mousberg. -- Exec approvals: forward approval prompts to chat with `/approve` for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands +- Exec approvals: forward approval prompts to chat with `/approve` for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.molt.bot/tools/exec-approvals https://docs.molt.bot/tools/slash-commands - Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg. -- Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.clawd.bot/diagnostics/flags +- Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.molt.bot/diagnostics/flags - Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround). - Docs: add verbose installer troubleshooting guidance. - Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua. -- Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock +- Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.molt.bot/bedrock - Docs: update Fly.io guide notes. - Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido. @@ -144,11 +144,11 @@ Status: unreleased. - Web UI: hide internal `message_id` hints in chat bubbles. - Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (`gateway.controlUi.allowInsecureAuth`). (#1679) Thanks @steipete. - Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47. -- BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles +- BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.molt.bot/channels/bluebubbles - BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing. - iMessage: normalize chat_id/chat_guid/chat_identifier prefixes case-insensitively and keep service-prefixed handles stable. (#1708) Thanks @aaronn. - Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev. -- Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal +- Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.molt.bot/channels/signal - Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338. - Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639) - Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt. @@ -186,25 +186,25 @@ Status: unreleased. ## 2026.1.23 ### Highlights -- TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.clawd.bot/tts -- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.clawd.bot/gateway/tools-invoke-http-api -- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.clawd.bot/gateway/heartbeat -- Deploy: add Fly.io deployment support + guide. (#1570) https://docs.clawd.bot/platforms/fly -- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.clawd.bot/channels/tlon +- TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.molt.bot/tts +- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.molt.bot/gateway/tools-invoke-http-api +- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.molt.bot/gateway/heartbeat +- Deploy: add Fly.io deployment support + guide. (#1570) https://docs.molt.bot/platforms/fly +- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.molt.bot/channels/tlon ### Changes -- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.clawd.bot/multi-agent-sandbox-tools -- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.clawd.bot/bedrock -- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.clawd.bot/cli/system -- CLI: add live auth probes to `clawdbot models status` for per-profile verification. (commit 40181afde) https://docs.clawd.bot/cli/models +- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.molt.bot/multi-agent-sandbox-tools +- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.molt.bot/bedrock +- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.molt.bot/cli/system +- CLI: add live auth probes to `clawdbot models status` for per-profile verification. (commit 40181afde) https://docs.molt.bot/cli/models - CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. (commit 2c85b1b40) - Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node). (commit c3cb26f7c) -- Plugins: add optional `llm-task` JSON-only tool for workflows. (#1498) Thanks @vignesh07. https://docs.clawd.bot/tools/llm-task +- Plugins: add optional `llm-task` JSON-only tool for workflows. (#1498) Thanks @vignesh07. https://docs.molt.bot/tools/llm-task - Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0. - Agents: keep system prompt time zone-only and move current time to `session_status` for better cache hits. (commit 66eec295b) - Agents: remove redundant bash tool alias from tool registration/display. (#1571) Thanks @Takhoffman. -- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc. https://docs.clawd.bot/automation/cron-vs-heartbeat -- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.clawd.bot/gateway/heartbeat +- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc. https://docs.molt.bot/automation/cron-vs-heartbeat +- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.molt.bot/gateway/heartbeat ### Fixes - Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518) @@ -282,14 +282,14 @@ Status: unreleased. ## 2026.1.21-2 ### Fixes -- Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.clawd.bot/cli/agents https://docs.clawd.bot/web/control-ui +- Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.molt.bot/cli/agents https://docs.molt.bot/web/control-ui - Slack: remove deprecated `filetype` field from `files.uploadV2` to eliminate API warnings. (#1447) ## 2026.1.21 ### Changes -- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.clawd.bot/tools/lobster -- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.clawd.bot/tools/lobster +- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.molt.bot/tools/lobster +- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.molt.bot/tools/lobster - Heartbeat: allow running heartbeats in an explicit session key. (#1256) Thanks @zknicker. - CLI: default exec approvals to the local host, add gateway/node targeting flags, and show target details in allowlist output. - CLI: exec approvals mutations render tables instead of raw JSON. @@ -301,16 +301,16 @@ Status: unreleased. - Sessions: add per-channel reset overrides via `session.resetByChannel`. (#1353) Thanks @cash-echo-bot. - Agents: add identity avatar config support and Control UI avatar rendering. (#1329, #1424) Thanks @dlauer. - UI: show per-session assistant identity in the Control UI. (#1420) Thanks @robbyczgw-cla. -- CLI: add `clawdbot update wizard` for interactive channel selection and restart prompts. https://docs.clawd.bot/cli/update +- CLI: add `clawdbot update wizard` for interactive channel selection and restart prompts. https://docs.molt.bot/cli/update - Signal: add typing indicators and DM read receipts via signal-cli. - MSTeams: add file uploads, adaptive cards, and attachment handling improvements. (#1410) Thanks @Evizero. - Onboarding: remove the run setup-token auth option (paste setup-token or reuse CLI creds instead). -- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.clawd.bot/gateway/troubleshooting +- Docs: add troubleshooting entry for gateway.mode blocking gateway start. https://docs.molt.bot/gateway/troubleshooting - Docs: add /model allowlist troubleshooting note. (#1405) - Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky. ### Breaking -- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.clawd.bot/web/control-ui#insecure-http +- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.molt.bot/web/control-ui#insecure-http - **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. ### Fixes @@ -336,68 +336,68 @@ Status: unreleased. ## 2026.1.20 ### Changes -- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.clawd.bot/web/control-ui -- Control UI: drop the legacy list view. (#1345) https://docs.clawd.bot/web/control-ui -- TUI: add syntax highlighting for code blocks. (#1200) https://docs.clawd.bot/tui -- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.clawd.bot/tui -- TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.clawd.bot/tui -- TUI: add input history (up/down) for submitted messages. (#1348) https://docs.clawd.bot/tui -- ACP: add `clawdbot acp` for IDE integrations. https://docs.clawd.bot/cli/acp -- ACP: add `clawdbot acp client` interactive harness for debugging. https://docs.clawd.bot/cli/acp -- Skills: add download installs with OS-filtered options. https://docs.clawd.bot/tools/skills -- Skills: add the local sherpa-onnx-tts skill. https://docs.clawd.bot/tools/skills -- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.clawd.bot/concepts/memory -- Memory: add SQLite embedding cache to speed up reindexing and frequent updates. https://docs.clawd.bot/concepts/memory -- Memory: add OpenAI batch indexing for embeddings when configured. https://docs.clawd.bot/concepts/memory -- Memory: enable OpenAI batch indexing by default for OpenAI embeddings. https://docs.clawd.bot/concepts/memory -- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2). https://docs.clawd.bot/concepts/memory -- Memory: render progress immediately, color batch statuses in verbose logs, and poll OpenAI batch status every 2s by default. https://docs.clawd.bot/concepts/memory -- Memory: add `--verbose` logging for memory status + batch indexing details. https://docs.clawd.bot/concepts/memory -- Memory: add native Gemini embeddings provider for memory search. (#1151) https://docs.clawd.bot/concepts/memory -- Browser: allow config defaults for efficient snapshots in the tool/CLI. (#1336) https://docs.clawd.bot/tools/browser -- Nostr: add the Nostr channel plugin with profile management + onboarding defaults. (#1323) https://docs.clawd.bot/channels/nostr -- Matrix: migrate to matrix-bot-sdk with E2EE support, location handling, and group allowlist upgrades. (#1298) https://docs.clawd.bot/channels/matrix -- Slack: add HTTP webhook mode via Bolt HTTP receiver. (#1143) https://docs.clawd.bot/channels/slack -- Telegram: enrich forwarded-message context with normalized origin details + legacy fallback. (#1090) https://docs.clawd.bot/channels/telegram +- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.molt.bot/web/control-ui +- Control UI: drop the legacy list view. (#1345) https://docs.molt.bot/web/control-ui +- TUI: add syntax highlighting for code blocks. (#1200) https://docs.molt.bot/tui +- TUI: session picker shows derived titles, fuzzy search, relative times, and last message preview. (#1271) https://docs.molt.bot/tui +- TUI: add a searchable model picker for quicker model selection. (#1198) https://docs.molt.bot/tui +- TUI: add input history (up/down) for submitted messages. (#1348) https://docs.molt.bot/tui +- ACP: add `clawdbot acp` for IDE integrations. https://docs.molt.bot/cli/acp +- ACP: add `clawdbot acp client` interactive harness for debugging. https://docs.molt.bot/cli/acp +- Skills: add download installs with OS-filtered options. https://docs.molt.bot/tools/skills +- Skills: add the local sherpa-onnx-tts skill. https://docs.molt.bot/tools/skills +- Memory: add hybrid BM25 + vector search (FTS5) with weighted merging and fallback. https://docs.molt.bot/concepts/memory +- Memory: add SQLite embedding cache to speed up reindexing and frequent updates. https://docs.molt.bot/concepts/memory +- Memory: add OpenAI batch indexing for embeddings when configured. https://docs.molt.bot/concepts/memory +- Memory: enable OpenAI batch indexing by default for OpenAI embeddings. https://docs.molt.bot/concepts/memory +- Memory: allow parallel OpenAI batch indexing jobs (default concurrency: 2). https://docs.molt.bot/concepts/memory +- Memory: render progress immediately, color batch statuses in verbose logs, and poll OpenAI batch status every 2s by default. https://docs.molt.bot/concepts/memory +- Memory: add `--verbose` logging for memory status + batch indexing details. https://docs.molt.bot/concepts/memory +- Memory: add native Gemini embeddings provider for memory search. (#1151) https://docs.molt.bot/concepts/memory +- Browser: allow config defaults for efficient snapshots in the tool/CLI. (#1336) https://docs.molt.bot/tools/browser +- Nostr: add the Nostr channel plugin with profile management + onboarding defaults. (#1323) https://docs.molt.bot/channels/nostr +- Matrix: migrate to matrix-bot-sdk with E2EE support, location handling, and group allowlist upgrades. (#1298) https://docs.molt.bot/channels/matrix +- Slack: add HTTP webhook mode via Bolt HTTP receiver. (#1143) https://docs.molt.bot/channels/slack +- Telegram: enrich forwarded-message context with normalized origin details + legacy fallback. (#1090) https://docs.molt.bot/channels/telegram - Discord: fall back to `/skill` when native command limits are exceeded. (#1287) - Discord: expose `/skill` globally. (#1287) -- Zalouser: add channel dock metadata, config schema, setup wiring, probe, and status issues. (#1219) https://docs.clawd.bot/plugins/zalouser -- Plugins: require manifest-embedded config schemas with preflight validation warnings. (#1272) https://docs.clawd.bot/plugins/manifest -- Plugins: move channel catalog metadata into plugin manifests. (#1290) https://docs.clawd.bot/plugins/manifest -- Plugins: align Nextcloud Talk policy helpers with core patterns. (#1290) https://docs.clawd.bot/plugins/manifest -- Plugins/UI: let channel plugin metadata drive UI labels/icons and cron channel options. (#1306) https://docs.clawd.bot/web/control-ui -- Agents/UI: add agent avatar support in identity config, IDENTITY.md, and the Control UI. (#1329) https://docs.clawd.bot/gateway/configuration -- Plugins: add plugin slots with a dedicated memory slot selector. https://docs.clawd.bot/plugins/agent-tools -- Plugins: ship the bundled BlueBubbles channel plugin (disabled by default). https://docs.clawd.bot/channels/bluebubbles +- Zalouser: add channel dock metadata, config schema, setup wiring, probe, and status issues. (#1219) https://docs.molt.bot/plugins/zalouser +- Plugins: require manifest-embedded config schemas with preflight validation warnings. (#1272) https://docs.molt.bot/plugins/manifest +- Plugins: move channel catalog metadata into plugin manifests. (#1290) https://docs.molt.bot/plugins/manifest +- Plugins: align Nextcloud Talk policy helpers with core patterns. (#1290) https://docs.molt.bot/plugins/manifest +- Plugins/UI: let channel plugin metadata drive UI labels/icons and cron channel options. (#1306) https://docs.molt.bot/web/control-ui +- Agents/UI: add agent avatar support in identity config, IDENTITY.md, and the Control UI. (#1329) https://docs.molt.bot/gateway/configuration +- Plugins: add plugin slots with a dedicated memory slot selector. https://docs.molt.bot/plugins/agent-tools +- Plugins: ship the bundled BlueBubbles channel plugin (disabled by default). https://docs.molt.bot/channels/bluebubbles - Plugins: migrate bundled messaging extensions to the plugin SDK and resolve plugin-sdk imports in the loader. -- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime. https://docs.clawd.bot/channels/zalo -- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.clawd.bot/plugins/zalouser -- Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.clawd.bot/plugins/agent-tools +- Plugins: migrate the Zalo plugin to the shared plugin SDK runtime. https://docs.molt.bot/channels/zalo +- Plugins: migrate the Zalo Personal plugin to the shared plugin SDK runtime. https://docs.molt.bot/plugins/zalouser +- Plugins: allow optional agent tools with explicit allowlists and add the plugin tool authoring guide. https://docs.molt.bot/plugins/agent-tools - Plugins: auto-enable bundled channel/provider plugins when configuration is present. - Plugins: sync plugin sources on channel switches and update npm-installed plugins during `clawdbot update`. - Plugins: share npm plugin update logic between `clawdbot update` and `clawdbot plugins update`. - Gateway/API: add `/v1/responses` (OpenResponses) with item-based input + semantic streaming events. (#1229) - Gateway/API: expand `/v1/responses` to support file/image inputs, tool_choice, usage, and output limits. (#1229) -- Usage: add `/usage cost` summaries and macOS menu cost charts. https://docs.clawd.bot/reference/api-usage-costs -- Security: warn when <=300B models run without sandboxing while web tools are enabled. https://docs.clawd.bot/cli/security -- Exec: add host/security/ask routing for gateway + node exec. https://docs.clawd.bot/tools/exec -- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). https://docs.clawd.bot/tools/exec -- Exec approvals: migrate approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists + skill auto-allow toggle, and add approvals UI + node exec lifecycle events. https://docs.clawd.bot/tools/exec-approvals -- Nodes: add headless node host (`clawdbot node start`) for `system.run`/`system.which`. https://docs.clawd.bot/cli/node -- Nodes: add node daemon service install/status/start/stop/restart. https://docs.clawd.bot/cli/node +- Usage: add `/usage cost` summaries and macOS menu cost charts. https://docs.molt.bot/reference/api-usage-costs +- Security: warn when <=300B models run without sandboxing while web tools are enabled. https://docs.molt.bot/cli/security +- Exec: add host/security/ask routing for gateway + node exec. https://docs.molt.bot/tools/exec +- Exec: add `/exec` directive for per-session exec defaults (host/security/ask/node). https://docs.molt.bot/tools/exec +- Exec approvals: migrate approvals to `~/.clawdbot/exec-approvals.json` with per-agent allowlists + skill auto-allow toggle, and add approvals UI + node exec lifecycle events. https://docs.molt.bot/tools/exec-approvals +- Nodes: add headless node host (`clawdbot node start`) for `system.run`/`system.which`. https://docs.molt.bot/cli/node +- Nodes: add node daemon service install/status/start/stop/restart. https://docs.molt.bot/cli/node - Bridge: add `skills.bins` RPC to support node host auto-allow skill bins. -- Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) https://docs.clawd.bot/concepts/session -- Sessions: allow `sessions_spawn` to override thinking level for sub-agent runs. https://docs.clawd.bot/tools/subagents -- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers. https://docs.clawd.bot/concepts/groups -- Models: add Qwen Portal OAuth provider support. (#1120) https://docs.clawd.bot/providers/qwen -- Onboarding: add allowlist prompts and username-to-id resolution across core and extension channels. https://docs.clawd.bot/start/onboarding -- Docs: clarify allowlist input types and onboarding behavior for messaging channels. https://docs.clawd.bot/start/onboarding -- Docs: refresh Android node discovery docs for the Gateway WS service type. https://docs.clawd.bot/platforms/android -- Docs: surface Amazon Bedrock in provider lists and clarify Bedrock auth env vars. (#1289) https://docs.clawd.bot/bedrock -- Docs: clarify WhatsApp voice notes. https://docs.clawd.bot/channels/whatsapp -- Docs: clarify Windows WSL portproxy LAN access notes. https://docs.clawd.bot/platforms/windows -- Docs: refresh bird skill install metadata and usage notes. (#1302) https://docs.clawd.bot/tools/browser-login +- Sessions: add daily reset policy with per-type overrides and idle windows (default 4am local), preserving legacy idle-only configs. (#1146) https://docs.molt.bot/concepts/session +- Sessions: allow `sessions_spawn` to override thinking level for sub-agent runs. https://docs.molt.bot/tools/subagents +- Channels: unify thread/topic allowlist matching + command/mention gating helpers across core providers. https://docs.molt.bot/concepts/groups +- Models: add Qwen Portal OAuth provider support. (#1120) https://docs.molt.bot/providers/qwen +- Onboarding: add allowlist prompts and username-to-id resolution across core and extension channels. https://docs.molt.bot/start/onboarding +- Docs: clarify allowlist input types and onboarding behavior for messaging channels. https://docs.molt.bot/start/onboarding +- Docs: refresh Android node discovery docs for the Gateway WS service type. https://docs.molt.bot/platforms/android +- Docs: surface Amazon Bedrock in provider lists and clarify Bedrock auth env vars. (#1289) https://docs.molt.bot/bedrock +- Docs: clarify WhatsApp voice notes. https://docs.molt.bot/channels/whatsapp +- Docs: clarify Windows WSL portproxy LAN access notes. https://docs.molt.bot/platforms/windows +- Docs: refresh bird skill install metadata and usage notes. (#1302) https://docs.molt.bot/tools/browser-login - Agents: add local docs path resolution and include docs/mirror/source/community pointers in the system prompt. - Agents: clarify node_modules read-only guidance in agent instructions. - Config: stamp last-touched metadata on write and warn if the config is newer than the running build. @@ -522,19 +522,19 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.16-1 ### Highlights -- Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.clawd.bot/hooks -- Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.clawd.bot/nodes/media-understanding -- Plugins: add Zalo Personal plugin (`@clawdbot/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.clawd.bot/plugins/zalouser -- Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.clawd.bot/providers/vercel-ai-gateway -- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.clawd.bot/concepts/session -- Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.clawd.bot/tools/web +- Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.molt.bot/hooks +- Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.molt.bot/nodes/media-understanding +- Plugins: add Zalo Personal plugin (`@clawdbot/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.molt.bot/plugins/zalouser +- Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.molt.bot/providers/vercel-ai-gateway +- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.molt.bot/concepts/session +- Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.molt.bot/tools/web ### Breaking - **BREAKING:** `clawdbot message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. - **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. - **BREAKING:** remove legacy provider-specific target resolution fallbacks; target resolution is centralized with plugin hints + directory lookups. -- **BREAKING:** `clawdbot hooks` is now `clawdbot webhooks`; hooks live under `clawdbot hooks`. https://docs.clawd.bot/cli/webhooks +- **BREAKING:** `clawdbot hooks` is now `clawdbot webhooks`; hooks live under `clawdbot hooks`. https://docs.molt.bot/cli/webhooks - **BREAKING:** `clawdbot plugins install ` now copies into `~/.clawdbot/extensions` (use `--link` to keep path-based loading). ### Changes @@ -545,7 +545,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Tools: send Chrome-like headers by default for `web_fetch` to improve extraction on bot-sensitive sites. - Tools: Firecrawl fallback now uses bot-circumvention + cache by default; remove basic HTML fallback when extraction fails. - Tools: default `exec` exit notifications and auto-migrate legacy `tools.bash` to `tools.exec`. -- Tools: add `exec` PTY support for interactive sessions. https://docs.clawd.bot/tools/exec +- Tools: add `exec` PTY support for interactive sessions. https://docs.molt.bot/tools/exec - Tools: add tmux-style `process send-keys` and bracketed paste helpers for PTY sessions. - Tools: add `process submit` helper to send CR for PTY sessions. - Tools: respond to PTY cursor position queries to unblock interactive TUIs. @@ -600,7 +600,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Sessions: hard-stop `sessions.delete` cleanup. - Channels: treat replies to the bot as implicit mentions across supported channels. - Channels: normalize object-format capabilities in channel capability parsing. -- Security: default-deny slash/control commands unless a channel computed `CommandAuthorized` (fixes accidental “open” behavior), and ensure WhatsApp + Zalo plugin channels gate inline `/…` tokens correctly. https://docs.clawd.bot/gateway/security +- Security: default-deny slash/control commands unless a channel computed `CommandAuthorized` (fixes accidental “open” behavior), and ensure WhatsApp + Zalo plugin channels gate inline `/…` tokens correctly. https://docs.molt.bot/gateway/security - Security: redact sensitive text in gateway WS logs. - Tools: cap pending `exec` process output to avoid unbounded buffers. - CLI: speed up `clawdbot sandbox-explain` by avoiding heavy plugin imports when normalizing channel ids. @@ -906,7 +906,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Agents: add pre-compaction memory flush config (`agents.defaults.compaction.*`) with a soft threshold + system prompt. - Config: add `$include` directive for modular config files. (#731) — thanks @pasogott. - Build: set pnpm minimum release age to 2880 minutes (2 days). (#718) — thanks @dan-dr. -- macOS: prompt to install the global `clawdbot` CLI when missing in local mode; install via `clawd.bot/install-cli.sh` (no onboarding) and use external launchd/CLI instead of the embedded gateway runtime. +- macOS: prompt to install the global `clawdbot` CLI when missing in local mode; install via `molt.bot/install-cli.sh` (no onboarding) and use external launchd/CLI instead of the embedded gateway runtime. - Docs: add gog calendar event color IDs from `gog calendar colors`. (#715) — thanks @mjrussell. - Cron/CLI: add `--model` flag to cron add/edit commands. (#711) — thanks @mjrussell. - Cron/CLI: trim model overrides on cron edits and document main-session guidance. (#711) — thanks @mjrussell. @@ -920,7 +920,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ### Installer - Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests. - Postinstall: skip pnpm patch fallback when the new patcher is active. -- Installer tests: add root+non-root docker smokes, CI workflow to fetch clawd.bot scripts and run install sh/cli with onboarding skipped. +- Installer tests: add root+non-root docker smokes, CI workflow to fetch molt.bot scripts and run install sh/cli with onboarding skipped. - Installer UX: support `CLAWDBOT_NO_ONBOARD=1` for non-interactive installs; fix npm prefix on Linux and auto-install git. - Installer UX: add `install.sh --help` with flags/env and git install hint. - Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm). diff --git a/README.md b/README.md index a3f0b11d1..9f1f93193 100644 --- a/README.md +++ b/README.md @@ -21,55 +21,56 @@ It answers you on the channels you already use (WhatsApp, Telegram, Slack, Disco If you want a personal, single-user assistant that feels local, fast, and always-on, this is it. -[Website](https://clawdbot.com) · [Docs](https://docs.clawd.bot) · [Getting Started](https://docs.clawd.bot/start/getting-started) · [Updating](https://docs.clawd.bot/install/updating) · [Showcase](https://docs.clawd.bot/start/showcase) · [FAQ](https://docs.clawd.bot/start/faq) · [Wizard](https://docs.clawd.bot/start/wizard) · [Nix](https://github.com/clawdbot/nix-clawdbot) · [Docker](https://docs.clawd.bot/install/docker) · [Discord](https://discord.gg/clawd) +[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/clawdbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd) Preferred setup: run the onboarding wizard (`clawdbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**. Works with npm, pnpm, or bun. -New install? Start here: [Getting started](https://docs.clawd.bot/start/getting-started) +New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started) **Subscriptions (OAuth):** - **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max) - **[OpenAI](https://openai.com/)** (ChatGPT/Codex) -Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.5** for long‑context strength and better prompt‑injection resistance. See [Onboarding](https://docs.clawd.bot/start/onboarding). +Model note: while any model is supported, I strongly recommend **Anthropic Pro/Max (100/200) + Opus 4.5** for long‑context strength and better prompt‑injection resistance. See [Onboarding](https://docs.molt.bot/start/onboarding). ## Models (selection + auth) -- Models config + CLI: [Models](https://docs.clawd.bot/concepts/models) -- Auth profile rotation (OAuth vs API keys) + fallbacks: [Model failover](https://docs.clawd.bot/concepts/model-failover) +- Models config + CLI: [Models](https://docs.molt.bot/concepts/models) +- Auth profile rotation (OAuth vs API keys) + fallbacks: [Model failover](https://docs.molt.bot/concepts/model-failover) ## Install (recommended) Runtime: **Node ≥22**. ```bash -npm install -g clawdbot@latest -# or: pnpm add -g clawdbot@latest +npm install -g moltbot@latest +# or: pnpm add -g moltbot@latest -clawdbot onboard --install-daemon +moltbot onboard --install-daemon ``` The wizard installs the Gateway daemon (launchd/systemd user service) so it stays running. +Legacy note: `clawdbot` remains available as a compatibility shim. ## Quick start (TL;DR) Runtime: **Node ≥22**. -Full beginner guide (auth, pairing, channels): [Getting started](https://docs.clawd.bot/start/getting-started) +Full beginner guide (auth, pairing, channels): [Getting started](https://docs.molt.bot/start/getting-started) ```bash -clawdbot onboard --install-daemon +moltbot onboard --install-daemon -clawdbot gateway --port 18789 --verbose +moltbot gateway --port 18789 --verbose # Send a message -clawdbot message send --to +1234567890 --message "Hello from Clawdbot" +moltbot message send --to +1234567890 --message "Hello from Moltbot" # Talk to the assistant (optionally deliver back to any connected channel: WhatsApp/Telegram/Slack/Discord/Google Chat/Signal/iMessage/BlueBubbles/Microsoft Teams/Matrix/Zalo/Zalo Personal/WebChat) -clawdbot agent --message "Ship checklist" --thinking high +moltbot agent --message "Ship checklist" --thinking high ``` -Upgrading? [Updating guide](https://docs.clawd.bot/install/updating) (and run `clawdbot doctor`). +Upgrading? [Updating guide](https://docs.molt.bot/install/updating) (and run `moltbot doctor`). ## Development channels @@ -78,7 +79,7 @@ Upgrading? [Updating guide](https://docs.clawd.bot/install/updating) (and run `c - **dev**: moving head of `main`, npm dist-tag `dev` (when published). Switch channels (git + npm): `clawdbot update --channel stable|beta|dev`. -Details: [Development channels](https://docs.clawd.bot/install/development-channels). +Details: [Development channels](https://docs.molt.bot/install/development-channels). ## From source (development) @@ -92,19 +93,19 @@ pnpm install pnpm ui:build # auto-installs UI deps on first run pnpm build -pnpm clawdbot onboard --install-daemon +pnpm moltbot onboard --install-daemon # Dev loop (auto-reload on TS changes) pnpm gateway:watch ``` -Note: `pnpm clawdbot ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `clawdbot` binary. +Note: `pnpm moltbot ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `moltbot` binary. ## Security defaults (DM access) Clawdbot connects to real messaging surfaces. Treat inbound DMs as **untrusted input**. -Full security guide: [Security](https://docs.clawd.bot/gateway/security) +Full security guide: [Security](https://docs.molt.bot/gateway/security) Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack: - **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message. @@ -115,14 +116,14 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies. ## Highlights -- **[Local-first Gateway](https://docs.clawd.bot/gateway)** — single control plane for sessions, channels, tools, and events. -- **[Multi-channel inbox](https://docs.clawd.bot/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android. -- **[Multi-agent routing](https://docs.clawd.bot/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions). -- **[Voice Wake](https://docs.clawd.bot/nodes/voicewake) + [Talk Mode](https://docs.clawd.bot/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs. -- **[Live Canvas](https://docs.clawd.bot/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.clawd.bot/platforms/mac/canvas#canvas-a2ui). -- **[First-class tools](https://docs.clawd.bot/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions. -- **[Companion apps](https://docs.clawd.bot/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.clawd.bot/nodes). -- **[Onboarding](https://docs.clawd.bot/start/wizard) + [skills](https://docs.clawd.bot/tools/skills)** — wizard-driven setup with bundled/managed/workspace skills. +- **[Local-first Gateway](https://docs.molt.bot/gateway)** — single control plane for sessions, channels, tools, and events. +- **[Multi-channel inbox](https://docs.molt.bot/channels)** — WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, BlueBubbles, Microsoft Teams, Matrix, Zalo, Zalo Personal, WebChat, macOS, iOS/Android. +- **[Multi-agent routing](https://docs.molt.bot/gateway/configuration)** — route inbound channels/accounts/peers to isolated agents (workspaces + per-agent sessions). +- **[Voice Wake](https://docs.molt.bot/nodes/voicewake) + [Talk Mode](https://docs.molt.bot/nodes/talk)** — always-on speech for macOS/iOS/Android with ElevenLabs. +- **[Live Canvas](https://docs.molt.bot/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui). +- **[First-class tools](https://docs.molt.bot/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions. +- **[Companion apps](https://docs.molt.bot/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.molt.bot/nodes). +- **[Onboarding](https://docs.molt.bot/start/wizard) + [skills](https://docs.molt.bot/tools/skills)** — wizard-driven setup with bundled/managed/workspace skills. ## Star History @@ -131,40 +132,40 @@ Run `clawdbot doctor` to surface risky/misconfigured DM policies. ## Everything we built so far ### Core platform -- [Gateway WS control plane](https://docs.clawd.bot/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.clawd.bot/web), and [Canvas host](https://docs.clawd.bot/platforms/mac/canvas#canvas-a2ui). -- [CLI surface](https://docs.clawd.bot/tools/agent-send): gateway, agent, send, [wizard](https://docs.clawd.bot/start/wizard), and [doctor](https://docs.clawd.bot/gateway/doctor). -- [Pi agent runtime](https://docs.clawd.bot/concepts/agent) in RPC mode with tool streaming and block streaming. -- [Session model](https://docs.clawd.bot/concepts/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.clawd.bot/concepts/groups). -- [Media pipeline](https://docs.clawd.bot/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.clawd.bot/nodes/audio). +- [Gateway WS control plane](https://docs.molt.bot/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.molt.bot/web), and [Canvas host](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui). +- [CLI surface](https://docs.molt.bot/tools/agent-send): gateway, agent, send, [wizard](https://docs.molt.bot/start/wizard), and [doctor](https://docs.molt.bot/gateway/doctor). +- [Pi agent runtime](https://docs.molt.bot/concepts/agent) in RPC mode with tool streaming and block streaming. +- [Session model](https://docs.molt.bot/concepts/session): `main` for direct chats, group isolation, activation modes, queue modes, reply-back. Group rules: [Groups](https://docs.molt.bot/concepts/groups). +- [Media pipeline](https://docs.molt.bot/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.molt.bot/nodes/audio). ### Channels -- [Channels](https://docs.clawd.bot/channels): [WhatsApp](https://docs.clawd.bot/channels/whatsapp) (Baileys), [Telegram](https://docs.clawd.bot/channels/telegram) (grammY), [Slack](https://docs.clawd.bot/channels/slack) (Bolt), [Discord](https://docs.clawd.bot/channels/discord) (discord.js), [Google Chat](https://docs.clawd.bot/channels/googlechat) (Chat API), [Signal](https://docs.clawd.bot/channels/signal) (signal-cli), [iMessage](https://docs.clawd.bot/channels/imessage) (imsg), [BlueBubbles](https://docs.clawd.bot/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.clawd.bot/channels/msteams) (extension), [Matrix](https://docs.clawd.bot/channels/matrix) (extension), [Zalo](https://docs.clawd.bot/channels/zalo) (extension), [Zalo Personal](https://docs.clawd.bot/channels/zalouser) (extension), [WebChat](https://docs.clawd.bot/web/webchat). -- [Group routing](https://docs.clawd.bot/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.clawd.bot/channels). +- [Channels](https://docs.molt.bot/channels): [WhatsApp](https://docs.molt.bot/channels/whatsapp) (Baileys), [Telegram](https://docs.molt.bot/channels/telegram) (grammY), [Slack](https://docs.molt.bot/channels/slack) (Bolt), [Discord](https://docs.molt.bot/channels/discord) (discord.js), [Google Chat](https://docs.molt.bot/channels/googlechat) (Chat API), [Signal](https://docs.molt.bot/channels/signal) (signal-cli), [iMessage](https://docs.molt.bot/channels/imessage) (imsg), [BlueBubbles](https://docs.molt.bot/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.molt.bot/channels/msteams) (extension), [Matrix](https://docs.molt.bot/channels/matrix) (extension), [Zalo](https://docs.molt.bot/channels/zalo) (extension), [Zalo Personal](https://docs.molt.bot/channels/zalouser) (extension), [WebChat](https://docs.molt.bot/web/webchat). +- [Group routing](https://docs.molt.bot/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.molt.bot/channels). ### Apps + nodes -- [macOS app](https://docs.clawd.bot/platforms/macos): menu bar control plane, [Voice Wake](https://docs.clawd.bot/nodes/voicewake)/PTT, [Talk Mode](https://docs.clawd.bot/nodes/talk) overlay, [WebChat](https://docs.clawd.bot/web/webchat), debug tools, [remote gateway](https://docs.clawd.bot/gateway/remote) control. -- [iOS node](https://docs.clawd.bot/platforms/ios): [Canvas](https://docs.clawd.bot/platforms/mac/canvas), [Voice Wake](https://docs.clawd.bot/nodes/voicewake), [Talk Mode](https://docs.clawd.bot/nodes/talk), camera, screen recording, Bonjour pairing. -- [Android node](https://docs.clawd.bot/platforms/android): [Canvas](https://docs.clawd.bot/platforms/mac/canvas), [Talk Mode](https://docs.clawd.bot/nodes/talk), camera, screen recording, optional SMS. -- [macOS node mode](https://docs.clawd.bot/nodes): system.run/notify + canvas/camera exposure. +- [macOS app](https://docs.molt.bot/platforms/macos): menu bar control plane, [Voice Wake](https://docs.molt.bot/nodes/voicewake)/PTT, [Talk Mode](https://docs.molt.bot/nodes/talk) overlay, [WebChat](https://docs.molt.bot/web/webchat), debug tools, [remote gateway](https://docs.molt.bot/gateway/remote) control. +- [iOS node](https://docs.molt.bot/platforms/ios): [Canvas](https://docs.molt.bot/platforms/mac/canvas), [Voice Wake](https://docs.molt.bot/nodes/voicewake), [Talk Mode](https://docs.molt.bot/nodes/talk), camera, screen recording, Bonjour pairing. +- [Android node](https://docs.molt.bot/platforms/android): [Canvas](https://docs.molt.bot/platforms/mac/canvas), [Talk Mode](https://docs.molt.bot/nodes/talk), camera, screen recording, optional SMS. +- [macOS node mode](https://docs.molt.bot/nodes): system.run/notify + canvas/camera exposure. ### Tools + automation -- [Browser control](https://docs.clawd.bot/tools/browser): dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles. -- [Canvas](https://docs.clawd.bot/platforms/mac/canvas): [A2UI](https://docs.clawd.bot/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot. -- [Nodes](https://docs.clawd.bot/nodes): camera snap/clip, screen record, [location.get](https://docs.clawd.bot/nodes/location-command), notifications. -- [Cron + wakeups](https://docs.clawd.bot/automation/cron-jobs); [webhooks](https://docs.clawd.bot/automation/webhook); [Gmail Pub/Sub](https://docs.clawd.bot/automation/gmail-pubsub). -- [Skills platform](https://docs.clawd.bot/tools/skills): bundled, managed, and workspace skills with install gating + UI. +- [Browser control](https://docs.molt.bot/tools/browser): dedicated clawd Chrome/Chromium, snapshots, actions, uploads, profiles. +- [Canvas](https://docs.molt.bot/platforms/mac/canvas): [A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot. +- [Nodes](https://docs.molt.bot/nodes): camera snap/clip, screen record, [location.get](https://docs.molt.bot/nodes/location-command), notifications. +- [Cron + wakeups](https://docs.molt.bot/automation/cron-jobs); [webhooks](https://docs.molt.bot/automation/webhook); [Gmail Pub/Sub](https://docs.molt.bot/automation/gmail-pubsub). +- [Skills platform](https://docs.molt.bot/tools/skills): bundled, managed, and workspace skills with install gating + UI. ### Runtime + safety -- [Channel routing](https://docs.clawd.bot/concepts/channel-routing), [retry policy](https://docs.clawd.bot/concepts/retry), and [streaming/chunking](https://docs.clawd.bot/concepts/streaming). -- [Presence](https://docs.clawd.bot/concepts/presence), [typing indicators](https://docs.clawd.bot/concepts/typing-indicators), and [usage tracking](https://docs.clawd.bot/concepts/usage-tracking). -- [Models](https://docs.clawd.bot/concepts/models), [model failover](https://docs.clawd.bot/concepts/model-failover), and [session pruning](https://docs.clawd.bot/concepts/session-pruning). -- [Security](https://docs.clawd.bot/gateway/security) and [troubleshooting](https://docs.clawd.bot/channels/troubleshooting). +- [Channel routing](https://docs.molt.bot/concepts/channel-routing), [retry policy](https://docs.molt.bot/concepts/retry), and [streaming/chunking](https://docs.molt.bot/concepts/streaming). +- [Presence](https://docs.molt.bot/concepts/presence), [typing indicators](https://docs.molt.bot/concepts/typing-indicators), and [usage tracking](https://docs.molt.bot/concepts/usage-tracking). +- [Models](https://docs.molt.bot/concepts/models), [model failover](https://docs.molt.bot/concepts/model-failover), and [session pruning](https://docs.molt.bot/concepts/session-pruning). +- [Security](https://docs.molt.bot/gateway/security) and [troubleshooting](https://docs.molt.bot/channels/troubleshooting). ### Ops + packaging -- [Control UI](https://docs.clawd.bot/web) + [WebChat](https://docs.clawd.bot/web/webchat) served directly from the Gateway. -- [Tailscale Serve/Funnel](https://docs.clawd.bot/gateway/tailscale) or [SSH tunnels](https://docs.clawd.bot/gateway/remote) with token/password auth. -- [Nix mode](https://docs.clawd.bot/install/nix) for declarative config; [Docker](https://docs.clawd.bot/install/docker)-based installs. -- [Doctor](https://docs.clawd.bot/gateway/doctor) migrations, [logging](https://docs.clawd.bot/logging). +- [Control UI](https://docs.molt.bot/web) + [WebChat](https://docs.molt.bot/web/webchat) served directly from the Gateway. +- [Tailscale Serve/Funnel](https://docs.molt.bot/gateway/tailscale) or [SSH tunnels](https://docs.molt.bot/gateway/remote) with token/password auth. +- [Nix mode](https://docs.molt.bot/install/nix) for declarative config; [Docker](https://docs.molt.bot/install/docker)-based installs. +- [Doctor](https://docs.molt.bot/gateway/doctor) migrations, [logging](https://docs.molt.bot/logging). ## How it works (short) @@ -187,12 +188,12 @@ WhatsApp / Telegram / Slack / Discord / Google Chat / Signal / iMessage / BlueBu ## Key subsystems -- **[Gateway WebSocket network](https://docs.clawd.bot/concepts/architecture)** — single WS control plane for clients, tools, and events (plus ops: [Gateway runbook](https://docs.clawd.bot/gateway)). -- **[Tailscale exposure](https://docs.clawd.bot/gateway/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.clawd.bot/gateway/remote)). -- **[Browser control](https://docs.clawd.bot/tools/browser)** — clawd‑managed Chrome/Chromium with CDP control. -- **[Canvas + A2UI](https://docs.clawd.bot/platforms/mac/canvas)** — agent‑driven visual workspace (A2UI host: [Canvas/A2UI](https://docs.clawd.bot/platforms/mac/canvas#canvas-a2ui)). -- **[Voice Wake](https://docs.clawd.bot/nodes/voicewake) + [Talk Mode](https://docs.clawd.bot/nodes/talk)** — always‑on speech and continuous conversation. -- **[Nodes](https://docs.clawd.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOS‑only `system.run`/`system.notify`. +- **[Gateway WebSocket network](https://docs.molt.bot/concepts/architecture)** — single WS control plane for clients, tools, and events (plus ops: [Gateway runbook](https://docs.molt.bot/gateway)). +- **[Tailscale exposure](https://docs.molt.bot/gateway/tailscale)** — Serve/Funnel for the Gateway dashboard + WS (remote access: [Remote](https://docs.molt.bot/gateway/remote)). +- **[Browser control](https://docs.molt.bot/tools/browser)** — clawd‑managed Chrome/Chromium with CDP control. +- **[Canvas + A2UI](https://docs.molt.bot/platforms/mac/canvas)** — agent‑driven visual workspace (A2UI host: [Canvas/A2UI](https://docs.molt.bot/platforms/mac/canvas#canvas-a2ui)). +- **[Voice Wake](https://docs.molt.bot/nodes/voicewake) + [Talk Mode](https://docs.molt.bot/nodes/talk)** — always‑on speech and continuous conversation. +- **[Nodes](https://docs.molt.bot/nodes)** — Canvas, camera snap/clip, screen record, `location.get`, notifications, plus macOS‑only `system.run`/`system.notify`. ## Tailscale access (Gateway dashboard) @@ -208,7 +209,7 @@ Notes: - Funnel refuses to start unless `gateway.auth.mode: "password"` is set. - Optional: `gateway.tailscale.resetOnExit` to undo Serve/Funnel on shutdown. -Details: [Tailscale guide](https://docs.clawd.bot/gateway/tailscale) · [Web surfaces](https://docs.clawd.bot/web) +Details: [Tailscale guide](https://docs.molt.bot/gateway/tailscale) · [Web surfaces](https://docs.molt.bot/web) ## Remote Gateway (Linux is great) @@ -218,7 +219,7 @@ It’s perfectly fine to run the Gateway on a small Linux instance. Clients (mac - **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`. In short: exec runs where the Gateway lives; device actions run where the device lives. -Details: [Remote access](https://docs.clawd.bot/gateway/remote) · [Nodes](https://docs.clawd.bot/nodes) · [Security](https://docs.clawd.bot/gateway/security) +Details: [Remote access](https://docs.molt.bot/gateway/remote) · [Nodes](https://docs.molt.bot/nodes) · [Security](https://docs.molt.bot/gateway/security) ## macOS permissions via the Gateway protocol @@ -233,7 +234,7 @@ Elevated bash (host permissions) is separate from macOS TCC: - Use `/elevated on|off` to toggle per‑session elevated access when enabled + allowlisted. - Gateway persists the per‑session toggle via `sessions.patch` (WS method) alongside `thinkingLevel`, `verboseLevel`, `model`, `sendPolicy`, and `groupActivation`. -Details: [Nodes](https://docs.clawd.bot/nodes) · [macOS app](https://docs.clawd.bot/platforms/macos) · [Gateway protocol](https://docs.clawd.bot/concepts/architecture) +Details: [Nodes](https://docs.molt.bot/nodes) · [macOS app](https://docs.molt.bot/platforms/macos) · [Gateway protocol](https://docs.molt.bot/concepts/architecture) ## Agent to Agent (sessions_* tools) @@ -242,7 +243,7 @@ Details: [Nodes](https://docs.clawd.bot/nodes) · [macOS app](https://docs.clawd - `sessions_history` — fetch transcript logs for a session. - `sessions_send` — message another session; optional reply‑back ping‑pong + announce step (`REPLY_SKIP`, `ANNOUNCE_SKIP`). -Details: [Session tools](https://docs.clawd.bot/concepts/session-tool) +Details: [Session tools](https://docs.molt.bot/concepts/session-tool) ## Skills registry (ClawdHub) @@ -284,13 +285,13 @@ Note: signed builds required for macOS permissions to stick across rebuilds (see - Voice trigger forwarding + Canvas surface. - Controlled via `clawdbot nodes …`. -Runbook: [iOS connect](https://docs.clawd.bot/platforms/ios). +Runbook: [iOS connect](https://docs.molt.bot/platforms/ios). ### Android node (optional) - Pairs via the same Bridge + pairing flow as iOS. - Exposes Canvas, Camera, and Screen capture commands. -- Runbook: [Android connect](https://docs.clawd.bot/platforms/android). +- Runbook: [Android connect](https://docs.molt.bot/platforms/android). ## Agent workspace + skills @@ -310,7 +311,7 @@ Minimal `~/.clawdbot/clawdbot.json` (model + defaults): } ``` -[Full configuration reference (all keys + examples).](https://docs.clawd.bot/gateway/configuration) +[Full configuration reference (all keys + examples).](https://docs.molt.bot/gateway/configuration) ## Security model (important) @@ -318,15 +319,15 @@ Minimal `~/.clawdbot/clawdbot.json` (model + defaults): - **Group/channel safety:** set `agents.defaults.sandbox.mode: "non-main"` to run **non‑main sessions** (groups/channels) inside per‑session Docker sandboxes; bash then runs in Docker for those sessions. - **Sandbox defaults:** allowlist `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; denylist `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`. -Details: [Security guide](https://docs.clawd.bot/gateway/security) · [Docker + sandboxing](https://docs.clawd.bot/install/docker) · [Sandbox config](https://docs.clawd.bot/gateway/configuration) +Details: [Security guide](https://docs.molt.bot/gateway/security) · [Docker + sandboxing](https://docs.molt.bot/install/docker) · [Sandbox config](https://docs.molt.bot/gateway/configuration) -### [WhatsApp](https://docs.clawd.bot/channels/whatsapp) +### [WhatsApp](https://docs.molt.bot/channels/whatsapp) - Link the device: `pnpm clawdbot channels login` (stores creds in `~/.clawdbot/credentials`). - Allowlist who can talk to the assistant via `channels.whatsapp.allowFrom`. - If `channels.whatsapp.groups` is set, it becomes a group allowlist; include `"*"` to allow all. -### [Telegram](https://docs.clawd.bot/channels/telegram) +### [Telegram](https://docs.molt.bot/channels/telegram) - Set `TELEGRAM_BOT_TOKEN` or `channels.telegram.botToken` (env wins). - Optional: set `channels.telegram.groups` (with `channels.telegram.groups."*".requireMention`); when set, it is a group allowlist (include `"*"` to allow all). Also `channels.telegram.allowFrom` or `channels.telegram.webhookUrl` as needed. @@ -341,11 +342,11 @@ Details: [Security guide](https://docs.clawd.bot/gateway/security) · [Docker + } ``` -### [Slack](https://docs.clawd.bot/channels/slack) +### [Slack](https://docs.molt.bot/channels/slack) - Set `SLACK_BOT_TOKEN` + `SLACK_APP_TOKEN` (or `channels.slack.botToken` + `channels.slack.appToken`). -### [Discord](https://docs.clawd.bot/channels/discord) +### [Discord](https://docs.molt.bot/channels/discord) - Set `DISCORD_BOT_TOKEN` or `channels.discord.token` (env wins). - Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `channels.discord.dm.allowFrom`, `channels.discord.guilds`, or `channels.discord.mediaMaxMb` as needed. @@ -360,21 +361,21 @@ Details: [Security guide](https://docs.clawd.bot/gateway/security) · [Docker + } ``` -### [Signal](https://docs.clawd.bot/channels/signal) +### [Signal](https://docs.molt.bot/channels/signal) - Requires `signal-cli` and a `channels.signal` config section. -### [iMessage](https://docs.clawd.bot/channels/imessage) +### [iMessage](https://docs.molt.bot/channels/imessage) - macOS only; Messages must be signed in. - If `channels.imessage.groups` is set, it becomes a group allowlist; include `"*"` to allow all. -### [Microsoft Teams](https://docs.clawd.bot/channels/msteams) +### [Microsoft Teams](https://docs.molt.bot/channels/msteams) - Configure a Teams app + Bot Framework, then add a `msteams` config section. - Allowlist who can talk via `msteams.allowFrom`; group access via `msteams.groupAllowFrom` or `msteams.groupPolicy: "open"`. -### [WebChat](https://docs.clawd.bot/web/webchat) +### [WebChat](https://docs.molt.bot/web/webchat) - Uses the Gateway WebSocket; no separate WebChat port/config. @@ -392,69 +393,69 @@ Browser control (optional): ## Docs Use these when you’re past the onboarding flow and want the deeper reference. -- [Start with the docs index for navigation and “what’s where.”](https://docs.clawd.bot) -- [Read the architecture overview for the gateway + protocol model.](https://docs.clawd.bot/concepts/architecture) -- [Use the full configuration reference when you need every key and example.](https://docs.clawd.bot/gateway/configuration) -- [Run the Gateway by the book with the operational runbook.](https://docs.clawd.bot/gateway) -- [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.clawd.bot/web) -- [Understand remote access over SSH tunnels or tailnets.](https://docs.clawd.bot/gateway/remote) -- [Follow the onboarding wizard flow for a guided setup.](https://docs.clawd.bot/start/wizard) -- [Wire external triggers via the webhook surface.](https://docs.clawd.bot/automation/webhook) -- [Set up Gmail Pub/Sub triggers.](https://docs.clawd.bot/automation/gmail-pubsub) -- [Learn the macOS menu bar companion details.](https://docs.clawd.bot/platforms/mac/menu-bar) -- [Platform guides: Windows (WSL2)](https://docs.clawd.bot/platforms/windows), [Linux](https://docs.clawd.bot/platforms/linux), [macOS](https://docs.clawd.bot/platforms/macos), [iOS](https://docs.clawd.bot/platforms/ios), [Android](https://docs.clawd.bot/platforms/android) -- [Debug common failures with the troubleshooting guide.](https://docs.clawd.bot/channels/troubleshooting) -- [Review security guidance before exposing anything.](https://docs.clawd.bot/gateway/security) +- [Start with the docs index for navigation and “what’s where.”](https://docs.molt.bot) +- [Read the architecture overview for the gateway + protocol model.](https://docs.molt.bot/concepts/architecture) +- [Use the full configuration reference when you need every key and example.](https://docs.molt.bot/gateway/configuration) +- [Run the Gateway by the book with the operational runbook.](https://docs.molt.bot/gateway) +- [Learn how the Control UI/Web surfaces work and how to expose them safely.](https://docs.molt.bot/web) +- [Understand remote access over SSH tunnels or tailnets.](https://docs.molt.bot/gateway/remote) +- [Follow the onboarding wizard flow for a guided setup.](https://docs.molt.bot/start/wizard) +- [Wire external triggers via the webhook surface.](https://docs.molt.bot/automation/webhook) +- [Set up Gmail Pub/Sub triggers.](https://docs.molt.bot/automation/gmail-pubsub) +- [Learn the macOS menu bar companion details.](https://docs.molt.bot/platforms/mac/menu-bar) +- [Platform guides: Windows (WSL2)](https://docs.molt.bot/platforms/windows), [Linux](https://docs.molt.bot/platforms/linux), [macOS](https://docs.molt.bot/platforms/macos), [iOS](https://docs.molt.bot/platforms/ios), [Android](https://docs.molt.bot/platforms/android) +- [Debug common failures with the troubleshooting guide.](https://docs.molt.bot/channels/troubleshooting) +- [Review security guidance before exposing anything.](https://docs.molt.bot/gateway/security) ## Advanced docs (discovery + control) -- [Discovery + transports](https://docs.clawd.bot/gateway/discovery) -- [Bonjour/mDNS](https://docs.clawd.bot/gateway/bonjour) -- [Gateway pairing](https://docs.clawd.bot/gateway/pairing) -- [Remote gateway README](https://docs.clawd.bot/gateway/remote-gateway-readme) -- [Control UI](https://docs.clawd.bot/web/control-ui) -- [Dashboard](https://docs.clawd.bot/web/dashboard) +- [Discovery + transports](https://docs.molt.bot/gateway/discovery) +- [Bonjour/mDNS](https://docs.molt.bot/gateway/bonjour) +- [Gateway pairing](https://docs.molt.bot/gateway/pairing) +- [Remote gateway README](https://docs.molt.bot/gateway/remote-gateway-readme) +- [Control UI](https://docs.molt.bot/web/control-ui) +- [Dashboard](https://docs.molt.bot/web/dashboard) ## Operations & troubleshooting -- [Health checks](https://docs.clawd.bot/gateway/health) -- [Gateway lock](https://docs.clawd.bot/gateway/gateway-lock) -- [Background process](https://docs.clawd.bot/gateway/background-process) -- [Browser troubleshooting (Linux)](https://docs.clawd.bot/tools/browser-linux-troubleshooting) -- [Logging](https://docs.clawd.bot/logging) +- [Health checks](https://docs.molt.bot/gateway/health) +- [Gateway lock](https://docs.molt.bot/gateway/gateway-lock) +- [Background process](https://docs.molt.bot/gateway/background-process) +- [Browser troubleshooting (Linux)](https://docs.molt.bot/tools/browser-linux-troubleshooting) +- [Logging](https://docs.molt.bot/logging) ## Deep dives -- [Agent loop](https://docs.clawd.bot/concepts/agent-loop) -- [Presence](https://docs.clawd.bot/concepts/presence) -- [TypeBox schemas](https://docs.clawd.bot/concepts/typebox) -- [RPC adapters](https://docs.clawd.bot/reference/rpc) -- [Queue](https://docs.clawd.bot/concepts/queue) +- [Agent loop](https://docs.molt.bot/concepts/agent-loop) +- [Presence](https://docs.molt.bot/concepts/presence) +- [TypeBox schemas](https://docs.molt.bot/concepts/typebox) +- [RPC adapters](https://docs.molt.bot/reference/rpc) +- [Queue](https://docs.molt.bot/concepts/queue) ## Workspace & skills -- [Skills config](https://docs.clawd.bot/tools/skills-config) -- [Default AGENTS](https://docs.clawd.bot/reference/AGENTS.default) -- [Templates: AGENTS](https://docs.clawd.bot/reference/templates/AGENTS) -- [Templates: BOOTSTRAP](https://docs.clawd.bot/reference/templates/BOOTSTRAP) -- [Templates: IDENTITY](https://docs.clawd.bot/reference/templates/IDENTITY) -- [Templates: SOUL](https://docs.clawd.bot/reference/templates/SOUL) -- [Templates: TOOLS](https://docs.clawd.bot/reference/templates/TOOLS) -- [Templates: USER](https://docs.clawd.bot/reference/templates/USER) +- [Skills config](https://docs.molt.bot/tools/skills-config) +- [Default AGENTS](https://docs.molt.bot/reference/AGENTS.default) +- [Templates: AGENTS](https://docs.molt.bot/reference/templates/AGENTS) +- [Templates: BOOTSTRAP](https://docs.molt.bot/reference/templates/BOOTSTRAP) +- [Templates: IDENTITY](https://docs.molt.bot/reference/templates/IDENTITY) +- [Templates: SOUL](https://docs.molt.bot/reference/templates/SOUL) +- [Templates: TOOLS](https://docs.molt.bot/reference/templates/TOOLS) +- [Templates: USER](https://docs.molt.bot/reference/templates/USER) ## Platform internals -- [macOS dev setup](https://docs.clawd.bot/platforms/mac/dev-setup) -- [macOS menu bar](https://docs.clawd.bot/platforms/mac/menu-bar) -- [macOS voice wake](https://docs.clawd.bot/platforms/mac/voicewake) -- [iOS node](https://docs.clawd.bot/platforms/ios) -- [Android node](https://docs.clawd.bot/platforms/android) -- [Windows (WSL2)](https://docs.clawd.bot/platforms/windows) -- [Linux app](https://docs.clawd.bot/platforms/linux) +- [macOS dev setup](https://docs.molt.bot/platforms/mac/dev-setup) +- [macOS menu bar](https://docs.molt.bot/platforms/mac/menu-bar) +- [macOS voice wake](https://docs.molt.bot/platforms/mac/voicewake) +- [iOS node](https://docs.molt.bot/platforms/ios) +- [Android node](https://docs.molt.bot/platforms/android) +- [Windows (WSL2)](https://docs.molt.bot/platforms/windows) +- [Linux app](https://docs.molt.bot/platforms/linux) ## Email hooks (Gmail) -- [docs.clawd.bot/gmail-pubsub](https://docs.clawd.bot/automation/gmail-pubsub) +- [docs.molt.bot/gmail-pubsub](https://docs.molt.bot/automation/gmail-pubsub) ## Clawd diff --git a/SECURITY.md b/SECURITY.md index 5bc9c9112..1d0849a54 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -11,7 +11,7 @@ If you believe you've found a security issue in Clawdbot, please report it priva For threat model + hardening guidance (including `clawdbot security audit --deep` and `--fix`), see: -- `https://docs.clawd.bot/gateway/security` +- `https://docs.molt.bot/gateway/security` ### Web Interface Safety diff --git a/appcast.xml b/appcast.xml index 8158ac244..df325a7b6 100644 --- a/appcast.xml +++ b/appcast.xml @@ -28,28 +28,28 @@ Clawdbot 2026.1.24

Highlights

    -
  • Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.clawd.bot/providers/ollama https://docs.clawd.bot/providers/venice
  • +
  • Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.molt.bot/providers/ollama https://docs.molt.bot/providers/venice
  • Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.
  • -
  • TTS: Edge fallback (keyless) + /tts auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.clawd.bot/tts
  • -
  • Exec approvals: approve in-chat via /approve across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
  • -
  • Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.clawd.bot/channels/telegram
  • +
  • TTS: Edge fallback (keyless) + /tts auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.molt.bot/tts
  • +
  • Exec approvals: approve in-chat via /approve across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.molt.bot/tools/exec-approvals https://docs.molt.bot/tools/slash-commands
  • +
  • Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.molt.bot/channels/telegram

Changes

  • Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.
  • -
  • TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.clawd.bot/tts
  • -
  • TTS: add auto mode enum (off/always/inbound/tagged) with per-session /tts override. (#1667) Thanks @sebslight. https://docs.clawd.bot/tts
  • +
  • TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.molt.bot/tts
  • +
  • TTS: add auto mode enum (off/always/inbound/tagged) with per-session /tts override. (#1667) Thanks @sebslight. https://docs.molt.bot/tts
  • Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal.
  • -
  • Telegram: add channels.telegram.linkPreview to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.clawd.bot/channels/telegram
  • -
  • Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.clawd.bot/tools/web
  • +
  • Telegram: add channels.telegram.linkPreview to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.molt.bot/channels/telegram
  • +
  • Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.molt.bot/tools/web
  • UI: refresh Control UI dashboard design system (typography, colors, spacing). (#1786) Thanks @mousberg.
  • -
  • Exec approvals: forward approval prompts to chat with /approve for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.clawd.bot/tools/exec-approvals https://docs.clawd.bot/tools/slash-commands
  • +
  • Exec approvals: forward approval prompts to chat with /approve for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.molt.bot/tools/exec-approvals https://docs.molt.bot/tools/slash-commands
  • Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.
  • -
  • Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.clawd.bot/diagnostics/flags
  • +
  • Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.molt.bot/diagnostics/flags
  • Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
  • Docs: add verbose installer troubleshooting guidance.
  • Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua.
  • -
  • Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.clawd.bot/bedrock
  • +
  • Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.molt.bot/bedrock
  • Docs: update Fly.io guide notes.
  • Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.
@@ -61,10 +61,10 @@
  • Web UI: hide internal message_id hints in chat bubbles.
  • Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (gateway.controlUi.allowInsecureAuth). (#1679) Thanks @steipete.
  • Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47.
  • -
  • BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.clawd.bot/channels/bluebubbles
  • +
  • BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.molt.bot/channels/bluebubbles
  • BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.
  • Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.
  • -
  • Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.clawd.bot/channels/signal
  • +
  • Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.molt.bot/channels/signal
  • Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.
  • Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)
  • Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt.
  • diff --git a/apps/macos/Sources/Clawdbot/CLIInstaller.swift b/apps/macos/Sources/Clawdbot/CLIInstaller.swift index b9113e27a..c020ccef9 100644 --- a/apps/macos/Sources/Clawdbot/CLIInstaller.swift +++ b/apps/macos/Sources/Clawdbot/CLIInstaller.swift @@ -70,7 +70,7 @@ enum CLIInstaller { let escapedVersion = self.shellEscape(version) let escapedPrefix = self.shellEscape(prefix) let script = """ - curl -fsSL https://clawd.bot/install-cli.sh | \ + curl -fsSL https://molt.bot/install-cli.sh | \ bash -s -- --json --no-onboard --prefix \(escapedPrefix) --version \(escapedVersion) """ return ["/bin/bash", "-lc", script] diff --git a/assets/chrome-extension/options.html b/assets/chrome-extension/options.html index f66608f43..7cd4e27ce 100644 --- a/assets/chrome-extension/options.html +++ b/assets/chrome-extension/options.html @@ -171,7 +171,7 @@ Start Clawdbot’s browser relay on this machine (Gateway or node host), then click the toolbar button again.

    - Full guide (install, remote Gateway, security): docs.clawd.bot/tools/chrome-extension + Full guide (install, remote Gateway, security): docs.molt.bot/tools/chrome-extension

    diff --git a/docker-setup.sh b/docker-setup.sh index 776541827..906bbb09c 100755 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -191,7 +191,7 @@ echo "Telegram (bot token):" echo " ${COMPOSE_HINT} run --rm clawdbot-cli providers add --provider telegram --token " echo "Discord (bot token):" echo " ${COMPOSE_HINT} run --rm clawdbot-cli providers add --provider discord --token " -echo "Docs: https://docs.clawd.bot/providers" +echo "Docs: https://docs.molt.bot/providers" echo "" echo "==> Starting gateway" diff --git a/docs/CNAME b/docs/CNAME index afc13c7fe..43ca6ac7f 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -clawdbot.com +docs.molt.bot diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md index 00cfa7c72..298e6f9ab 100644 --- a/docs/channels/googlechat.md +++ b/docs/channels/googlechat.md @@ -25,7 +25,7 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only). 5) Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat): - Fill in the **Application info**: - **App name**: (e.g. `Clawdbot`) - - **Avatar URL**: (e.g. `https://clawd.bot/logo.png`) + - **Avatar URL**: (e.g. `https://molt.bot/logo.png`) - **Description**: (e.g. `Personal AI Assistant`) - Enable **Interactive features**. - Under **Functionality**, check **Join spaces and group conversations**. diff --git a/docs/cli/browser.md b/docs/cli/browser.md index 6d54b8a10..d0f1d5a7d 100644 --- a/docs/cli/browser.md +++ b/docs/cli/browser.md @@ -53,7 +53,7 @@ clawdbot browser --browser-profile work tabs ```bash clawdbot browser tabs -clawdbot browser open https://docs.clawd.bot +clawdbot browser open https://docs.molt.bot clawdbot browser focus clawdbot browser close ``` diff --git a/docs/cli/hooks.md b/docs/cli/hooks.md index 825550b05..ee867be69 100644 --- a/docs/cli/hooks.md +++ b/docs/cli/hooks.md @@ -85,7 +85,7 @@ Details: Source: clawdbot-bundled Path: /path/to/clawdbot/hooks/bundled/session-memory/HOOK.md Handler: /path/to/clawdbot/hooks/bundled/session-memory/handler.ts - Homepage: https://docs.clawd.bot/hooks#session-memory + Homepage: https://docs.molt.bot/hooks#session-memory Events: command:new Requirements: diff --git a/docs/concepts/markdown-formatting.md b/docs/concepts/markdown-formatting.md index 91799a3e9..5c92669d2 100644 --- a/docs/concepts/markdown-formatting.md +++ b/docs/concepts/markdown-formatting.md @@ -39,7 +39,7 @@ stay consistent across channels. Input Markdown: ```markdown -Hello **world** — see [docs](https://docs.clawd.bot). +Hello **world** — see [docs](https://docs.molt.bot). ``` IR (schematic): @@ -51,7 +51,7 @@ IR (schematic): { "start": 6, "end": 11, "style": "bold" } ], "links": [ - { "start": 19, "end": 23, "href": "https://docs.clawd.bot" } + { "start": 19, "end": 23, "href": "https://docs.molt.bot" } ] } ``` diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md index 5f5990b9e..9b9090d36 100644 --- a/docs/gateway/security/index.md +++ b/docs/gateway/security/index.md @@ -750,7 +750,7 @@ Mario asking for find ~ Found a vulnerability in Clawdbot? Please report responsibly: -1. Email: security@clawd.bot +1. Email: security@molt.bot 2. Don't post publicly until fixed 3. We'll credit you (unless you prefer anonymity) diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md index 697654b80..4f192b40f 100644 --- a/docs/gateway/troubleshooting.md +++ b/docs/gateway/troubleshooting.md @@ -501,12 +501,12 @@ upgrades in place and rewrites the gateway service to point at the new install. Switch **to git install**: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git --no-onboard +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git --no-onboard ``` Switch **to npm global**: ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` Notes: diff --git a/docs/help/faq.md b/docs/help/faq.md index 554597165..55db3e637 100644 --- a/docs/help/faq.md +++ b/docs/help/faq.md @@ -18,7 +18,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS, - [It is stuck on "wake up my friend" / onboarding will not hatch. What now?](#it-is-stuck-on-wake-up-my-friend-onboarding-will-not-hatch-what-now) - [Can I migrate my setup to a new machine (Mac mini) without redoing onboarding?](#can-i-migrate-my-setup-to-a-new-machine-mac-mini-without-redoing-onboarding) - [Where do I see what’s new in the latest version?](#where-do-i-see-whats-new-in-the-latest-version) - - [I can't access docs.clawd.bot (SSL error). What now?](#i-cant-access-docsclawdbot-ssl-error-what-now) + - [I can't access docs.molt.bot (SSL error). What now?](#i-cant-access-docsclawdbot-ssl-error-what-now) - [What’s the difference between stable and beta?](#whats-the-difference-between-stable-and-beta) - [How do I install the beta version, and what’s the difference between beta and dev?](#how-do-i-install-the-beta-version-and-whats-the-difference-between-beta-and-dev) - [How do I try the latest bits?](#how-do-i-try-the-latest-bits) @@ -258,7 +258,7 @@ setup (PATH, services, permissions, auth files). Give them the **full source che the hackable (git) install: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git ``` This installs Clawdbot **from a git checkout**, so the agent can read the code + docs and @@ -296,7 +296,7 @@ Install docs: [Install](/install), [Installer flags](/install/installer), [Updat The repo recommends running from source and using the onboarding wizard: ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash clawdbot onboard --install-daemon ``` @@ -416,8 +416,8 @@ section is the latest shipped version. Entries are grouped by **Highlights**, ** ### I cant access docsclawdbot SSL error What now -Some Comcast/Xfinity connections incorrectly block `docs.clawd.bot` via Xfinity -Advanced Security. Disable it or allowlist `docs.clawd.bot`, then retry. More +Some Comcast/Xfinity connections incorrectly block `docs.molt.bot` via Xfinity +Advanced Security. Disable it or allowlist `docs.molt.bot`, then retry. More detail: [Troubleshooting](/help/troubleshooting#docsclawdbot-shows-an-ssl-error-comcastxfinity). Please help us unblock it by reporting here: https://spa.xfinity.com/check_url_status. @@ -445,15 +445,15 @@ https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md One‑liners (macOS/Linux): ```bash -curl -fsSL --proto '=https' --tlsv1.2 https://clawd.bot/install.sh | bash -s -- --beta +curl -fsSL --proto '=https' --tlsv1.2 https://molt.bot/install.sh | bash -s -- --beta ``` ```bash -curl -fsSL --proto '=https' --tlsv1.2 https://clawd.bot/install.sh | bash -s -- --install-method git +curl -fsSL --proto '=https' --tlsv1.2 https://molt.bot/install.sh | bash -s -- --install-method git ``` Windows installer (PowerShell): -https://clawd.bot/install.ps1 +https://molt.bot/install.ps1 More detail: [Development channels](/install/development-channels) and [Installer flags](/install/installer). @@ -478,7 +478,7 @@ This switches to the `main` branch and updates from source. 2) **Hackable install (from the installer site):** ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git ``` That gives you a local repo you can edit, then update via git. @@ -498,19 +498,19 @@ Docs: [Update](/cli/update), [Development channels](/install/development-channel Re-run the installer with **verbose output**: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --verbose +curl -fsSL https://molt.bot/install.sh | bash -s -- --verbose ``` Beta install with verbose: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --beta --verbose +curl -fsSL https://molt.bot/install.sh | bash -s -- --beta --verbose ``` For a hackable (git) install: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git --verbose +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git --verbose ``` More options: [Installer flags](/install/installer). @@ -541,7 +541,7 @@ Use the **hackable (git) install** so you have the full source and docs locally, your bot (or Claude/Codex) *from that folder* so it can read the repo and answer precisely. ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git ``` More detail: [Install](/install) and [Installer flags](/install/installer). @@ -946,7 +946,7 @@ Advantages: - **Always-on Gateway** (run on a VPS, interact from anywhere) - **Nodes** for local browser/screen/camera/exec -Showcase: https://clawd.bot/showcase +Showcase: https://molt.bot/showcase ## Skills and automation diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index 4a7d2ced3..b142de4eb 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -38,13 +38,13 @@ Almost always a Node/npm PATH issue. Start here: Re-run the installer in verbose mode to see the full trace and npm output: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --verbose +curl -fsSL https://molt.bot/install.sh | bash -s -- --verbose ``` For beta installs: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --beta --verbose +curl -fsSL https://molt.bot/install.sh | bash -s -- --beta --verbose ``` You can also set `CLAWDBOT_VERBOSE=1` instead of the flag. @@ -59,10 +59,10 @@ You can also set `CLAWDBOT_VERBOSE=1` instead of the flag. - [Gateway troubleshooting](/gateway/troubleshooting) - [Control UI](/web/control-ui#insecure-http) -### `docs.clawd.bot` shows an SSL error (Comcast/Xfinity) +### `docs.molt.bot` shows an SSL error (Comcast/Xfinity) -Some Comcast/Xfinity connections block `docs.clawd.bot` via Xfinity Advanced Security. -Disable Advanced Security or add `docs.clawd.bot` to the allowlist, then retry. +Some Comcast/Xfinity connections block `docs.molt.bot` via Xfinity Advanced Security. +Disable Advanced Security or add `docs.molt.bot` to the allowlist, then retry. - Xfinity Advanced Security help: https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security - Quick sanity checks: try a mobile hotspot or VPN to confirm it’s ISP-level filtering diff --git a/docs/hooks.md b/docs/hooks.md index 9870dda88..880c31f19 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -124,7 +124,7 @@ The `HOOK.md` file contains metadata in YAML frontmatter plus Markdown documenta --- name: my-hook description: "Short description of what this hook does" -homepage: https://docs.clawd.bot/hooks#my-hook +homepage: https://docs.molt.bot/hooks#my-hook metadata: {"clawdbot":{"emoji":"🔗","events":["command:new"],"requires":{"bins":["node"]}}} --- diff --git a/docs/install/index.md b/docs/install/index.md index 7ccab0ca8..f5f5b7476 100644 --- a/docs/install/index.md +++ b/docs/install/index.md @@ -12,13 +12,13 @@ Use the installer unless you have a reason not to. It sets up the CLI and runs o ## Quick install (recommended) ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` Windows (PowerShell): ```powershell -iwr -useb https://clawd.bot/install.ps1 | iex +iwr -useb https://molt.bot/install.ps1 | iex ``` Next step (if you skipped onboarding): @@ -40,13 +40,13 @@ clawdbot onboard --install-daemon Installs `clawdbot` globally via npm and runs onboarding. ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` Installer flags: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --help +curl -fsSL https://molt.bot/install.sh | bash -s -- --help ``` Details: [Installer internals](/install/installer). @@ -54,7 +54,7 @@ Details: [Installer internals](/install/installer). Non-interactive (skip onboarding): ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --no-onboard +curl -fsSL https://molt.bot/install.sh | bash -s -- --no-onboard ``` ### 2) Global install (manual) @@ -123,10 +123,10 @@ The installer supports two methods: ```bash # Explicit npm -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method npm +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method npm # Install from GitHub (source checkout) -curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git +curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git ``` Common flags: diff --git a/docs/install/installer.md b/docs/install/installer.md index 7cd485756..d60f972d4 100644 --- a/docs/install/installer.md +++ b/docs/install/installer.md @@ -1,29 +1,29 @@ --- summary: "How the installer scripts work (install.sh + install-cli.sh), flags, and automation" read_when: - - You want to understand `clawd.bot/install.sh` + - You want to understand `molt.bot/install.sh` - You want to automate installs (CI / headless) - You want to install from a GitHub checkout --- # Installer internals -Clawdbot ships two installer scripts (served from `clawd.bot`): +Clawdbot ships two installer scripts (served from `molt.bot`): -- `https://clawd.bot/install.sh` — “recommended” installer (global npm install by default; can also install from a GitHub checkout) -- `https://clawd.bot/install-cli.sh` — non-root-friendly CLI installer (installs into a prefix with its own Node) - - `https://clawd.bot/install.ps1` — Windows PowerShell installer (npm by default; optional git install) +- `https://molt.bot/install.sh` — “recommended” installer (global npm install by default; can also install from a GitHub checkout) +- `https://molt.bot/install-cli.sh` — non-root-friendly CLI installer (installs into a prefix with its own Node) + - `https://molt.bot/install.ps1` — Windows PowerShell installer (npm by default; optional git install) To see the current flags/behavior, run: ```bash -curl -fsSL https://clawd.bot/install.sh | bash -s -- --help +curl -fsSL https://molt.bot/install.sh | bash -s -- --help ``` Windows (PowerShell) help: ```powershell -& ([scriptblock]::Create((iwr -useb https://clawd.bot/install.ps1))) -? +& ([scriptblock]::Create((iwr -useb https://molt.bot/install.ps1))) -? ``` If the installer completes but `clawdbot` is not found in a new terminal, it’s usually a Node/npm PATH issue. See: [Install](/install#nodejs--npm-path-sanity). @@ -45,7 +45,7 @@ What it does (high level): If you *want* `sharp` to link against a globally-installed libvips (or you’re debugging), set: ```bash -SHARP_IGNORE_GLOBAL_LIBVIPS=0 curl -fsSL https://clawd.bot/install.sh | bash +SHARP_IGNORE_GLOBAL_LIBVIPS=0 curl -fsSL https://molt.bot/install.sh | bash ``` ### Discoverability / “git install” prompt @@ -78,7 +78,7 @@ This script installs `clawdbot` into a prefix (default: `~/.clawdbot`) and also Help: ```bash -curl -fsSL https://clawd.bot/install-cli.sh | bash -s -- --help +curl -fsSL https://molt.bot/install-cli.sh | bash -s -- --help ``` ## install.ps1 (Windows PowerShell) @@ -94,15 +94,15 @@ What it does (high level): Examples: ```powershell -iwr -useb https://clawd.bot/install.ps1 | iex +iwr -useb https://molt.bot/install.ps1 | iex ``` ```powershell -iwr -useb https://clawd.bot/install.ps1 | iex -InstallMethod git +iwr -useb https://molt.bot/install.ps1 | iex -InstallMethod git ``` ```powershell -iwr -useb https://clawd.bot/install.ps1 | iex -InstallMethod git -GitDir "C:\\clawdbot" +iwr -useb https://molt.bot/install.ps1 | iex -InstallMethod git -GitDir "C:\\clawdbot" ``` Environment variables: diff --git a/docs/install/uninstall.md b/docs/install/uninstall.md index 5849a6780..1c8e65919 100644 --- a/docs/install/uninstall.md +++ b/docs/install/uninstall.md @@ -113,7 +113,7 @@ If you used a profile, delete the matching task name and `~\.clawdbot-\ ### Normal install (install.sh / npm / pnpm / bun) -If you used `https://clawd.bot/install.sh` or `install.ps1`, the CLI was installed with `npm install -g clawdbot@latest`. +If you used `https://molt.bot/install.sh` or `install.ps1`, the CLI was installed with `npm install -g clawdbot@latest`. Remove it with `npm rm -g clawdbot` (or `pnpm remove -g` / `bun remove -g` if you installed that way). ### Source checkout (git clone) diff --git a/docs/install/updating.md b/docs/install/updating.md index 1d39fa6e4..6cca10566 100644 --- a/docs/install/updating.md +++ b/docs/install/updating.md @@ -1,32 +1,33 @@ --- -summary: "Updating Clawdbot safely (global install or source), plus rollback strategy" +summary: "Updating Moltbot safely (global install or source), plus rollback strategy" read_when: - - Updating Clawdbot + - Updating Moltbot - Something breaks after an update --- # Updating -Clawdbot is moving fast (pre “1.0”). Treat updates like shipping infra: update → run checks → restart (or use `clawdbot update`, which restarts) → verify. +Moltbot is moving fast (pre “1.0”). Treat updates like shipping infra: update → run checks → restart (or use `moltbot update`, which restarts) → verify. ## Recommended: re-run the website installer (upgrade in place) The **preferred** update path is to re-run the installer from the website. It -detects existing installs, upgrades in place, and runs `clawdbot doctor` when +detects existing installs, upgrades in place, and runs `moltbot doctor` when needed. ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` Notes: - Add `--no-onboard` if you don’t want the onboarding wizard to run again. - For **source installs**, use: ```bash - curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git --no-onboard + curl -fsSL https://molt.bot/install.sh | bash -s -- --install-method git --no-onboard ``` The installer will `git pull --rebase` **only** if the repo is clean. -- For **global installs**, the script uses `npm install -g clawdbot@latest` under the hood. +- For **global installs**, the script uses `npm install -g moltbot@latest` under the hood. +- Legacy note: `clawdbot` remains available as a compatibility shim. ## Before you update @@ -42,20 +43,20 @@ Notes: Global install (pick one): ```bash -npm i -g clawdbot@latest +npm i -g moltbot@latest ``` ```bash -pnpm add -g clawdbot@latest +pnpm add -g moltbot@latest ``` We do **not** recommend Bun for the Gateway runtime (WhatsApp/Telegram bugs). To switch update channels (git + npm installs): ```bash -clawdbot update --channel beta -clawdbot update --channel dev -clawdbot update --channel stable +moltbot update --channel beta +moltbot update --channel dev +moltbot update --channel stable ``` Use `--tag ` for a one-off install tag/version. @@ -67,36 +68,36 @@ Note: on npm installs, the gateway logs an update hint on startup (checks the cu Then: ```bash -clawdbot doctor -clawdbot gateway restart -clawdbot health +moltbot doctor +moltbot gateway restart +moltbot health ``` Notes: -- If your Gateway runs as a service, `clawdbot gateway restart` is preferred over killing PIDs. +- If your Gateway runs as a service, `moltbot gateway restart` is preferred over killing PIDs. - If you’re pinned to a specific version, see “Rollback / pinning” below. -## Update (`clawdbot update`) +## Update (`moltbot update`) For **source installs** (git checkout), prefer: ```bash -clawdbot update +moltbot update ``` It runs a safe-ish update flow: - Requires a clean worktree. - Switches to the selected channel (tag or branch). - Fetches + rebases against the configured upstream (dev channel). -- Installs deps, builds, builds the Control UI, and runs `clawdbot doctor`. +- Installs deps, builds, builds the Control UI, and runs `moltbot doctor`. - Restarts the gateway by default (use `--no-restart` to skip). -If you installed via **npm/pnpm** (no git metadata), `clawdbot update` will try to update via your package manager. If it can’t detect the install, use “Update (global install)” instead. +If you installed via **npm/pnpm** (no git metadata), `moltbot update` will try to update via your package manager. If it can’t detect the install, use “Update (global install)” instead. ## Update (Control UI / RPC) The Control UI has **Update & Restart** (RPC: `update.run`). It: -1) Runs the same source-update flow as `clawdbot update` (git checkout only). +1) Runs the same source-update flow as `moltbot update` (git checkout only). 2) Writes a restart sentinel with a structured report (stdout/stderr tail). 3) Restarts the gateway and pings the last active session with the report. @@ -109,7 +110,7 @@ From the repo checkout: Preferred: ```bash -clawdbot update +moltbot update ``` Manual (equivalent-ish): @@ -119,21 +120,21 @@ git pull pnpm install pnpm build pnpm ui:build # auto-installs UI deps on first run -clawdbot doctor -clawdbot health +moltbot doctor +moltbot health ``` Notes: -- `pnpm build` matters when you run the packaged `clawdbot` binary ([`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js)) or use Node to run `dist/`. -- If you run from a repo checkout without a global install, use `pnpm clawdbot ...` for CLI commands. -- If you run directly from TypeScript (`pnpm clawdbot ...`), a rebuild is usually unnecessary, but **config migrations still apply** → run doctor. -- Switching between global and git installs is easy: install the other flavor, then run `clawdbot doctor` so the gateway service entrypoint is rewritten to the current install. +- `pnpm build` matters when you run the packaged `moltbot` binary ([`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js)) or use Node to run `dist/`. +- If you run from a repo checkout without a global install, use `pnpm moltbot ...` for CLI commands. +- If you run directly from TypeScript (`pnpm moltbot ...`), a rebuild is usually unnecessary, but **config migrations still apply** → run doctor. +- Switching between global and git installs is easy: install the other flavor, then run `moltbot doctor` so the gateway service entrypoint is rewritten to the current install. -## Always run: `clawdbot doctor` +## Always Run: `moltbot doctor` Doctor is the “safe update” command. It’s intentionally boring: repair + migrate + warn. -Note: if you’re on a **source install** (git checkout), `clawdbot doctor` will offer to run `clawdbot update` first. +Note: if you’re on a **source install** (git checkout), `moltbot doctor` will offer to run `moltbot update` first. Typical things it does: - Migrate deprecated config keys / legacy config file locations. @@ -149,18 +150,18 @@ Details: [Doctor](/gateway/doctor) CLI (works regardless of OS): ```bash -clawdbot gateway status -clawdbot gateway stop -clawdbot gateway restart -clawdbot gateway --port 18789 -clawdbot logs --follow +moltbot gateway status +moltbot gateway stop +moltbot gateway restart +moltbot gateway --port 18789 +moltbot logs --follow ``` If you’re supervised: - macOS launchd (app-bundled LaunchAgent): `launchctl kickstart -k gui/$UID/com.clawdbot.gateway` (use `com.clawdbot.` if set) - Linux systemd user service: `systemctl --user restart clawdbot-gateway[-].service` - Windows (WSL2): `systemctl --user restart clawdbot-gateway[-].service` - - `launchctl`/`systemctl` only work if the service is installed; otherwise run `clawdbot gateway install`. + - `launchctl`/`systemctl` only work if the service is installed; otherwise run `moltbot gateway install`. Runbook + exact service labels: [Gateway runbook](/gateway) @@ -171,20 +172,20 @@ Runbook + exact service labels: [Gateway runbook](/gateway) Install a known-good version (replace `` with the last working one): ```bash -npm i -g clawdbot@ +npm i -g moltbot@ ``` ```bash -pnpm add -g clawdbot@ +pnpm add -g moltbot@ ``` -Tip: to see the current published version, run `npm view clawdbot version`. +Tip: to see the current published version, run `npm view moltbot version`. Then restart + re-run doctor: ```bash -clawdbot doctor -clawdbot gateway restart +moltbot doctor +moltbot gateway restart ``` ### Pin (source) by date @@ -201,7 +202,7 @@ Then reinstall deps + restart: ```bash pnpm install pnpm build -clawdbot gateway restart +moltbot gateway restart ``` If you want to go back to latest later: @@ -213,6 +214,6 @@ git pull ## If you’re stuck -- Run `clawdbot doctor` again and read the output carefully (it often tells you the fix). +- Run `moltbot doctor` again and read the output carefully (it often tells you the fix). - Check: [Troubleshooting](/gateway/troubleshooting) - Ask in Discord: https://channels.discord.gg/clawd diff --git a/docs/platforms/digitalocean.md b/docs/platforms/digitalocean.md index afefe3676..ac46a5b47 100644 --- a/docs/platforms/digitalocean.md +++ b/docs/platforms/digitalocean.md @@ -65,7 +65,7 @@ curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt install -y nodejs # Install Clawdbot -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash # Verify clawdbot --version diff --git a/docs/platforms/oracle.md b/docs/platforms/oracle.md index d8006754b..52db36049 100644 --- a/docs/platforms/oracle.md +++ b/docs/platforms/oracle.md @@ -97,7 +97,7 @@ tailscale status ## 5) Install Clawdbot ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash source ~/.bashrc ``` diff --git a/docs/platforms/raspberry-pi.md b/docs/platforms/raspberry-pi.md index b34e3fcfe..ba6b339cf 100644 --- a/docs/platforms/raspberry-pi.md +++ b/docs/platforms/raspberry-pi.md @@ -110,7 +110,7 @@ sudo sysctl -p ### Option A: Standard Install (Recommended) ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` ### Option B: Hackable Install (For tinkering) diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md index a4c68e3e9..c107ac119 100644 --- a/docs/reference/RELEASING.md +++ b/docs/reference/RELEASING.md @@ -42,7 +42,7 @@ When the operator says “release”, immediately do this preflight (no extra qu - [ ] `CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT=1 pnpm test:install:smoke` (Docker install smoke test, fast path; required before release) - If the immediate previous npm release is known broken, set `CLAWDBOT_INSTALL_SMOKE_PREVIOUS=` or `CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS=1` for the preinstall step. - [ ] (Optional) Full installer smoke (adds non-root + CLI coverage): `pnpm test:install:smoke` -- [ ] (Optional) Installer E2E (Docker, runs `curl -fsSL https://clawd.bot/install.sh | bash`, onboards, then runs real tool calls): +- [ ] (Optional) Installer E2E (Docker, runs `curl -fsSL https://molt.bot/install.sh | bash`, onboards, then runs real tool calls): - `pnpm test:install:e2e:openai` (requires `OPENAI_API_KEY`) - `pnpm test:install:e2e:anthropic` (requires `ANTHROPIC_API_KEY`) - `pnpm test:install:e2e` (requires both keys; runs both providers) diff --git a/docs/start/getting-started.md b/docs/start/getting-started.md index 790a79dfc..92a772c4d 100644 --- a/docs/start/getting-started.md +++ b/docs/start/getting-started.md @@ -54,7 +54,7 @@ Windows: use **WSL2** (Ubuntu recommended). WSL2 is strongly recommended; native ## 1) Install the CLI (recommended) ```bash -curl -fsSL https://clawd.bot/install.sh | bash +curl -fsSL https://molt.bot/install.sh | bash ``` Installer options (install method, non-interactive, from GitHub): [Install](/install). @@ -62,7 +62,7 @@ Installer options (install method, non-interactive, from GitHub): [Install](/ins Windows (PowerShell): ```powershell -iwr -useb https://clawd.bot/install.ps1 | iex +iwr -useb https://molt.bot/install.ps1 | iex ``` Alternative (global install): diff --git a/extensions/lobster/src/lobster-tool.test.ts b/extensions/lobster/src/lobster-tool.test.ts index 5c887cc76..7314c9d5e 100644 --- a/extensions/lobster/src/lobster-tool.test.ts +++ b/extensions/lobster/src/lobster-tool.test.ts @@ -83,6 +83,26 @@ describe("lobster plugin tool", () => { expect(res.details).toMatchObject({ ok: true, status: "ok" }); }); + it("tolerates noisy stdout before the JSON envelope", async () => { + const payload = { ok: true, status: "ok", output: [], requiresApproval: null }; + const { binPath } = await writeFakeLobsterScript( + `const payload = ${JSON.stringify(payload)};\n` + + `console.log("noise before json");\n` + + `process.stdout.write(JSON.stringify(payload));\n`, + "clawdbot-lobster-plugin-noisy-", + ); + + const tool = createLobsterTool(fakeApi()); + const res = await tool.execute("call-noisy", { + action: "run", + pipeline: "noop", + lobsterPath: binPath, + timeoutMs: 1000, + }); + + expect(res.details).toMatchObject({ ok: true, status: "ok" }); + }); + it("requires absolute lobsterPath when provided", async () => { const tool = createLobsterTool(fakeApi()); await expect( diff --git a/extensions/lobster/src/lobster-tool.ts b/extensions/lobster/src/lobster-tool.ts index 368389ab8..e6e8441ff 100644 --- a/extensions/lobster/src/lobster-tool.ts +++ b/extensions/lobster/src/lobster-tool.ts @@ -131,10 +131,28 @@ async function runLobsterSubprocess(params: { } function parseEnvelope(stdout: string): LobsterEnvelope { - let parsed: unknown; - try { - parsed = JSON.parse(stdout); - } catch { + const trimmed = stdout.trim(); + + const tryParse = (input: string) => { + try { + return JSON.parse(input) as unknown; + } catch { + return undefined; + } + }; + + let parsed: unknown = tryParse(trimmed); + + // Some environments can leak extra stdout (e.g. warnings/logs) before the + // final JSON envelope. Be tolerant and parse the last JSON-looking suffix. + if (parsed === undefined) { + const suffixMatch = trimmed.match(/({[\s\S]*}|\[[\s\S]*])\s*$/); + if (suffixMatch?.[1]) { + parsed = tryParse(suffixMatch[1]); + } + } + + if (parsed === undefined) { throw new Error("lobster returned invalid JSON"); } diff --git a/extensions/mattermost/src/onboarding.ts b/extensions/mattermost/src/onboarding.ts index 431c648ae..31578b3e7 100644 --- a/extensions/mattermost/src/onboarding.ts +++ b/extensions/mattermost/src/onboarding.ts @@ -17,7 +17,7 @@ async function noteMattermostSetup(prompter: WizardPrompter): Promise { "2) Create a bot + copy its token", "3) Use your server base URL (e.g., https://chat.example.com)", "Tip: the bot must be a member of any channel you want it to monitor.", - "Docs: https://docs.clawd.bot/channels/mattermost", + "Docs: https://docs.molt.bot/channels/mattermost", ].join("\n"), "Mattermost bot token", ); diff --git a/extensions/tlon/README.md b/extensions/tlon/README.md index aa02cab93..19c8fbb27 100644 --- a/extensions/tlon/README.md +++ b/extensions/tlon/README.md @@ -2,4 +2,4 @@ Tlon/Urbit channel plugin for Clawdbot. Supports DMs, group mentions, and thread replies. -Docs: https://docs.clawd.bot/channels/tlon +Docs: https://docs.molt.bot/channels/tlon diff --git a/extensions/twitch/README.md b/extensions/twitch/README.md index 2d3e4ceea..377b49baa 100644 --- a/extensions/twitch/README.md +++ b/extensions/twitch/README.md @@ -80,7 +80,7 @@ Multi-account config (advanced): ## Full documentation -See https://docs.clawd.bot/channels/twitch for: +See https://docs.molt.bot/channels/twitch for: - Token refresh setup - Access control patterns diff --git a/extensions/voice-call/README.md b/extensions/voice-call/README.md index 5f009aa28..269a67d11 100644 --- a/extensions/voice-call/README.md +++ b/extensions/voice-call/README.md @@ -8,8 +8,8 @@ Providers: - **Plivo** (Voice API + XML transfer + GetInput speech) - **Mock** (dev/no network) -Docs: `https://docs.clawd.bot/plugins/voice-call` -Plugin system: `https://docs.clawd.bot/plugin` +Docs: `https://docs.molt.bot/plugins/voice-call` +Plugin system: `https://docs.molt.bot/plugin` ## Install (local dev) diff --git a/extensions/voice-call/src/cli.ts b/extensions/voice-call/src/cli.ts index 6291ef137..7769ae09f 100644 --- a/extensions/voice-call/src/cli.ts +++ b/extensions/voice-call/src/cli.ts @@ -45,7 +45,7 @@ export function registerVoiceCallCli(params: { const root = program .command("voicecall") .description("Voice call utilities") - .addHelpText("after", () => `\nDocs: https://docs.clawd.bot/cli/voicecall\n`); + .addHelpText("after", () => `\nDocs: https://docs.molt.bot/cli/voicecall\n`); root .command("call") diff --git a/extensions/voice-call/src/core-bridge.ts b/extensions/voice-call/src/core-bridge.ts index a1d01e10f..bc008a515 100644 --- a/extensions/voice-call/src/core-bridge.ts +++ b/extensions/voice-call/src/core-bridge.ts @@ -88,7 +88,7 @@ function findPackageRoot(startDir: string, name: string): string | null { function resolveClawdbotRoot(): string { if (coreRootCache) return coreRootCache; - const override = process.env.CLAWDBOT_ROOT?.trim(); + const override = process.env.MOLTBOT_ROOT?.trim() || process.env.CLAWDBOT_ROOT?.trim(); if (override) { coreRootCache = override; return override; @@ -107,15 +107,17 @@ function resolveClawdbotRoot(): string { } for (const start of candidates) { - const found = findPackageRoot(start, "clawdbot"); - if (found) { - coreRootCache = found; - return found; + for (const name of ["moltbot", "clawdbot"]) { + const found = findPackageRoot(start, name); + if (found) { + coreRootCache = found; + return found; + } } } throw new Error( - "Unable to resolve Clawdbot root. Set CLAWDBOT_ROOT to the package root.", + "Unable to resolve core root. Set MOLTBOT_ROOT (or legacy CLAWDBOT_ROOT) to the package root.", ); } diff --git a/extensions/zalo/src/onboarding.ts b/extensions/zalo/src/onboarding.ts index 4cb681dbe..3c4e5c197 100644 --- a/extensions/zalo/src/onboarding.ts +++ b/extensions/zalo/src/onboarding.ts @@ -132,7 +132,7 @@ async function noteZaloTokenHelp(prompter: WizardPrompter): Promise { "2) Create a bot and get the token", "3) Token looks like 12345689:abc-xyz", "Tip: you can also set ZALO_BOT_TOKEN in your env.", - "Docs: https://docs.clawd.bot/channels/zalo", + "Docs: https://docs.molt.bot/channels/zalo", ].join("\n"), "Zalo bot token", ); diff --git a/extensions/zalouser/src/channel.ts b/extensions/zalouser/src/channel.ts index 6554d7874..712143f65 100644 --- a/extensions/zalouser/src/channel.ts +++ b/extensions/zalouser/src/channel.ts @@ -471,7 +471,7 @@ export const zalouserPlugin: ChannelPlugin = { const ok = await checkZcaInstalled(); if (!ok) { throw new Error( - "Missing dependency: `zca` not found in PATH. See docs.clawd.bot/channels/zalouser", + "Missing dependency: `zca` not found in PATH. See docs.molt.bot/channels/zalouser", ); } runtime.log( diff --git a/extensions/zalouser/src/onboarding.ts b/extensions/zalouser/src/onboarding.ts index a5c205015..2a318d281 100644 --- a/extensions/zalouser/src/onboarding.ts +++ b/extensions/zalouser/src/onboarding.ts @@ -53,7 +53,7 @@ async function noteZalouserHelp(prompter: WizardPrompter): Promise { "1) Install zca-cli", "2) You'll scan a QR code with your Zalo app", "", - "Docs: https://docs.clawd.bot/channels/zalouser", + "Docs: https://docs.molt.bot/channels/zalouser", ].join("\n"), "Zalo Personal Setup", ); @@ -316,7 +316,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = { "The `zca` binary was not found in PATH.", "", "Install zca-cli, then re-run onboarding:", - "Docs: https://docs.clawd.bot/channels/zalouser", + "Docs: https://docs.molt.bot/channels/zalouser", ].join("\n"), "Missing Dependency", ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1c55dd8d..82f0a96e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -316,7 +316,7 @@ importers: devDependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot extensions/imessage: {} @@ -324,7 +324,7 @@ importers: devDependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot extensions/llm-task: {} @@ -350,7 +350,7 @@ importers: devDependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot extensions/mattermost: {} @@ -385,7 +385,7 @@ importers: version: 1.2.2 clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot express: specifier: ^5.2.1 version: 5.2.1 @@ -399,7 +399,7 @@ importers: dependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot nostr-tools: specifier: ^2.20.0 version: 2.20.0(typescript@5.9.3) @@ -441,7 +441,7 @@ importers: devDependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot extensions/voice-call: dependencies: @@ -461,7 +461,7 @@ importers: dependencies: clawdbot: specifier: workspace:* - version: link:../.. + version: link:../../packages/clawdbot undici: specifier: 7.19.0 version: 7.19.0 @@ -472,6 +472,12 @@ importers: specifier: 0.34.47 version: 0.34.47 clawdbot: + specifier: workspace:* + version: link:../../packages/clawdbot + + packages/clawdbot: + dependencies: + moltbot: specifier: workspace:* version: link:../.. diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 115f9a8c0..acf898add 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - . - ui + - packages/* - extensions/* onlyBuiltDependencies: diff --git a/scripts/docker/install-sh-e2e/run.sh b/scripts/docker/install-sh-e2e/run.sh index 474fc329e..0cc4a27e4 100755 --- a/scripts/docker/install-sh-e2e/run.sh +++ b/scripts/docker/install-sh-e2e/run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}" +INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" MODELS_MODE="${CLAWDBOT_E2E_MODELS:-both}" # both|openai|anthropic INSTALL_TAG="${CLAWDBOT_INSTALL_TAG:-latest}" E2E_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_E2E_PREVIOUS:-}" diff --git a/scripts/docker/install-sh-nonroot/run.sh b/scripts/docker/install-sh-nonroot/run.sh index 82e2275ae..bdb91bb13 100644 --- a/scripts/docker/install-sh-nonroot/run.sh +++ b/scripts/docker/install-sh-nonroot/run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}" +INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" echo "==> Pre-flight: ensure git absent" if command -v git >/dev/null; then diff --git a/scripts/docker/install-sh-smoke/run.sh b/scripts/docker/install-sh-smoke/run.sh index b73864ee1..de0e2536f 100755 --- a/scripts/docker/install-sh-smoke/run.sh +++ b/scripts/docker/install-sh-smoke/run.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}" +INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" SMOKE_PREVIOUS_VERSION="${CLAWDBOT_INSTALL_SMOKE_PREVIOUS:-}" SKIP_PREVIOUS="${CLAWDBOT_INSTALL_SMOKE_SKIP_PREVIOUS:-0}" diff --git a/scripts/test-install-sh-docker.sh b/scripts/test-install-sh-docker.sh index ba87eeb65..03cde30be 100755 --- a/scripts/test-install-sh-docker.sh +++ b/scripts/test-install-sh-docker.sh @@ -4,8 +4,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SMOKE_IMAGE="${CLAWDBOT_INSTALL_SMOKE_IMAGE:-clawdbot-install-smoke:local}" NONROOT_IMAGE="${CLAWDBOT_INSTALL_NONROOT_IMAGE:-clawdbot-install-nonroot:local}" -INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}" -CLI_INSTALL_URL="${CLAWDBOT_INSTALL_CLI_URL:-https://clawd.bot/install-cli.sh}" +INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" +CLI_INSTALL_URL="${CLAWDBOT_INSTALL_CLI_URL:-https://molt.bot/install-cli.sh}" SKIP_NONROOT="${CLAWDBOT_INSTALL_SMOKE_SKIP_NONROOT:-0}" LATEST_DIR="$(mktemp -d)" LATEST_FILE="${LATEST_DIR}/latest" diff --git a/scripts/test-install-sh-e2e-docker.sh b/scripts/test-install-sh-e2e-docker.sh index 0ad79aaf8..600be4e56 100755 --- a/scripts/test-install-sh-e2e-docker.sh +++ b/scripts/test-install-sh-e2e-docker.sh @@ -3,7 +3,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" IMAGE_NAME="${CLAWDBOT_INSTALL_E2E_IMAGE:-clawdbot-install-e2e:local}" -INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://clawd.bot/install.sh}" +INSTALL_URL="${CLAWDBOT_INSTALL_URL:-https://molt.bot/install.sh}" OPENAI_API_KEY="${OPENAI_API_KEY:-}" ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}" diff --git a/src/agents/clawdbot-gateway-tool.test.ts b/src/agents/clawdbot-gateway-tool.test.ts index 76d8ff8e4..8def33988 100644 --- a/src/agents/clawdbot-gateway-tool.test.ts +++ b/src/agents/clawdbot-gateway-tool.test.ts @@ -50,7 +50,7 @@ describe("gateway tool", () => { }; expect(parsed.payload?.kind).toBe("restart"); expect(parsed.payload?.doctorHint).toBe( - "Run: clawdbot --profile isolated doctor --non-interactive", + "Run: moltbot --profile isolated doctor --non-interactive", ); expect(kill).not.toHaveBeenCalled(); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index 1d1a6a5eb..4d4512298 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -116,7 +116,7 @@ function buildDocsSection(params: { docsPath?: string; isMinimal: boolean; readT return [ "## Documentation", `Clawdbot docs: ${docsPath}`, - "Mirror: https://docs.clawd.bot", + "Mirror: https://docs.molt.bot", "Source: https://github.com/clawdbot/clawdbot", "Community: https://discord.com/invite/clawd", "Find new skills: https://clawdhub.com", diff --git a/src/agents/tools/web-search.ts b/src/agents/tools/web-search.ts index 50d3d19a1..7cec8f21c 100644 --- a/src/agents/tools/web-search.ts +++ b/src/agents/tools/web-search.ts @@ -128,13 +128,13 @@ function missingSearchKeyPayload(provider: (typeof SEARCH_PROVIDERS)[number]) { error: "missing_perplexity_api_key", message: "web_search (perplexity) needs an API key. Set PERPLEXITY_API_KEY or OPENROUTER_API_KEY in the Gateway environment, or configure tools.web.search.perplexity.apiKey.", - docs: "https://docs.clawd.bot/tools/web", + docs: "https://docs.molt.bot/tools/web", }; } return { error: "missing_brave_api_key", message: `web_search needs a Brave Search API key. Run \`${formatCliCommand("clawdbot configure --section web")}\` to store it, or set BRAVE_API_KEY in the Gateway environment.`, - docs: "https://docs.clawd.bot/tools/web", + docs: "https://docs.molt.bot/tools/web", }; } @@ -279,7 +279,7 @@ async function runPerplexitySearch(params: { headers: { "Content-Type": "application/json", Authorization: `Bearer ${params.apiKey}`, - "HTTP-Referer": "https://clawdbot.com", + "HTTP-Referer": "https://molt.bot", "X-Title": "Clawdbot Web Search", }, body: JSON.stringify({ @@ -447,7 +447,7 @@ export function createWebSearchTool(options?: { return jsonResult({ error: "unsupported_freshness", message: "freshness is only supported by the Brave web_search provider.", - docs: "https://docs.clawd.bot/tools/web", + docs: "https://docs.molt.bot/tools/web", }); } const freshness = rawFreshness ? normalizeFreshness(rawFreshness) : undefined; @@ -456,7 +456,7 @@ export function createWebSearchTool(options?: { error: "invalid_freshness", message: "freshness must be one of pd, pw, pm, py, or a range like YYYY-MM-DDtoYYYY-MM-DD.", - docs: "https://docs.clawd.bot/tools/web", + docs: "https://docs.molt.bot/tools/web", }); } const result = await runWebSearch({ diff --git a/src/auto-reply/reply/bash-command.ts b/src/auto-reply/reply/bash-command.ts index a83bf3952..47ea45504 100644 --- a/src/auto-reply/reply/bash-command.ts +++ b/src/auto-reply/reply/bash-command.ts @@ -189,7 +189,7 @@ export async function handleBashChatCommand(params: { }): Promise { if (params.cfg.commands?.bash !== true) { return { - text: "⚠️ bash is disabled. Set commands.bash=true to enable. Docs: https://docs.clawd.bot/tools/slash-commands#config", + text: "⚠️ bash is disabled. Set commands.bash=true to enable. Docs: https://docs.molt.bot/tools/slash-commands#config", }; } diff --git a/src/canvas-host/a2ui/.bundle.hash b/src/canvas-host/a2ui/.bundle.hash index 19a232f5c..01a6e013f 100644 --- a/src/canvas-host/a2ui/.bundle.hash +++ b/src/canvas-host/a2ui/.bundle.hash @@ -1 +1 @@ -2567ca5bbc065b922d96717a488d5db3120b5b033c5d0508682d1aa8fbba470a +b3c955e808e8d11cdbb6f716a038f26ccdd4b69228ad0c4ce76fd81e98496d56 diff --git a/src/channels/plugins/catalog.test.ts b/src/channels/plugins/catalog.test.ts index 2470dbd33..a50aa2b36 100644 --- a/src/channels/plugins/catalog.test.ts +++ b/src/channels/plugins/catalog.test.ts @@ -8,7 +8,7 @@ import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries } from ". describe("channel plugin catalog", () => { it("includes Microsoft Teams", () => { const entry = getChannelPluginCatalogEntry("msteams"); - expect(entry?.install.npmSpec).toBe("@clawdbot/msteams"); + expect(entry?.install.npmSpec).toBe("@moltbot/msteams"); expect(entry?.meta.aliases).toContain("teams"); }); diff --git a/src/channels/plugins/onboarding/telegram.ts b/src/channels/plugins/onboarding/telegram.ts index fdbc044c5..90920ab40 100644 --- a/src/channels/plugins/onboarding/telegram.ts +++ b/src/channels/plugins/onboarding/telegram.ts @@ -38,7 +38,7 @@ async function noteTelegramTokenHelp(prompter: WizardPrompter): Promise { "3) Copy the token (looks like 123456:ABC...)", "Tip: you can also set TELEGRAM_BOT_TOKEN in your env.", `Docs: ${formatDocsLink("/telegram")}`, - "Website: https://clawd.bot", + "Website: https://molt.bot", ].join("\n"), "Telegram bot token", ); @@ -51,7 +51,7 @@ async function noteTelegramUserIdHelp(prompter: WizardPrompter): Promise { "2) Or call https://api.telegram.org/bot/getUpdates and read message.from.id", "3) Third-party: DM @userinfobot or @getidsbot", `Docs: ${formatDocsLink("/telegram")}`, - "Website: https://clawd.bot", + "Website: https://molt.bot", ].join("\n"), "Telegram user id", ); diff --git a/src/channels/registry.test.ts b/src/channels/registry.test.ts index 84940aa42..44b53ef6d 100644 --- a/src/channels/registry.test.ts +++ b/src/channels/registry.test.ts @@ -33,6 +33,6 @@ describe("channel registry", () => { ); expect(line).not.toContain("Docs:"); expect(line).toContain("/channels/telegram"); - expect(line).toContain("https://clawd.bot"); + expect(line).toContain("https://molt.bot"); }); }); diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 10ddeed4a..6afe1996c 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -22,7 +22,7 @@ export const DEFAULT_CHAT_CHANNEL: ChatChannelId = "whatsapp"; export type ChatChannelMeta = ChannelMeta; -const WEBSITE_URL = "https://clawd.bot"; +const WEBSITE_URL = "https://molt.bot"; const CHAT_CHANNEL_META: Record = { telegram: { diff --git a/src/cli/acp-cli.ts b/src/cli/acp-cli.ts index f2283d23d..8669d77fa 100644 --- a/src/cli/acp-cli.ts +++ b/src/cli/acp-cli.ts @@ -21,7 +21,7 @@ export function registerAcpCli(program: Command) { .option("--verbose, -v", "Verbose logging to stderr", false) .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/acp", "docs.clawd.bot/cli/acp")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/acp", "docs.molt.bot/cli/acp")}\n`, ) .action((opts) => { try { @@ -46,7 +46,7 @@ export function registerAcpCli(program: Command) { .command("client") .description("Run an interactive ACP client against the local ACP bridge") .option("--cwd ", "Working directory for the ACP session") - .option("--server ", "ACP server command (default: clawdbot)") + .option("--server ", "ACP server command (default: moltbot)") .option("--server-args ", "Extra arguments for the ACP server") .option("--server-verbose", "Enable verbose logging on the ACP server", false) .option("--verbose, -v", "Verbose client logging", false) diff --git a/src/cli/banner.ts b/src/cli/banner.ts index 7a811f56e..87803c9c7 100644 --- a/src/cli/banner.ts +++ b/src/cli/banner.ts @@ -2,6 +2,7 @@ import { resolveCommitHash } from "../infra/git-commit.js"; import { visibleWidth } from "../terminal/ansi.js"; import { isRich, theme } from "../terminal/theme.js"; import { pickTagline, type TaglineOptions } from "./tagline.js"; +import { resolveCliName } from "./cli-name.js"; type BannerOptions = TaglineOptions & { argv?: string[]; @@ -37,7 +38,8 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {} const commitLabel = commit ?? "unknown"; const tagline = pickTagline(options); const rich = options.richTty ?? isRich(); - const title = "🦞 Clawdbot"; + const cliName = resolveCliName(options.argv ?? process.argv, options.env); + const title = cliName === "clawdbot" ? "🦞 Clawdbot" : "🦞 Moltbot"; const prefix = "🦞 "; const columns = options.columns ?? process.stdout.columns ?? 120; const plainFullLine = `${title} ${version} (${commitLabel}) — ${tagline}`; diff --git a/src/cli/browser-cli-extension.ts b/src/cli/browser-cli-extension.ts index 3bd7c5b3a..6775216e6 100644 --- a/src/cli/browser-cli-extension.ts +++ b/src/cli/browser-cli-extension.ts @@ -90,7 +90,7 @@ export function registerBrowserExtensionCommands( `- “Load unpacked” → select: ${displayPath}`, `- Pin “Clawdbot Browser Relay”, then click it on the tab (badge shows ON)`, "", - `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, + `${theme.muted("Docs:")} ${formatDocsLink("/tools/chrome-extension", "docs.molt.bot/tools/chrome-extension")}`, ].join("\n"), ), ); @@ -107,7 +107,7 @@ export function registerBrowserExtensionCommands( danger( [ `Chrome extension is not installed. Run: "${formatCliCommand("clawdbot browser extension install")}"`, - `Docs: ${formatDocsLink("/tools/chrome-extension", "docs.clawd.bot/tools/chrome-extension")}`, + `Docs: ${formatDocsLink("/tools/chrome-extension", "docs.molt.bot/tools/chrome-extension")}`, ].join("\n"), ), ); diff --git a/src/cli/browser-cli.ts b/src/cli/browser-cli.ts index 50c71ef01..cda4d4275 100644 --- a/src/cli/browser-cli.ts +++ b/src/cli/browser-cli.ts @@ -31,7 +31,7 @@ export function registerBrowserCli(program: Command) { true, )}\n\n${theme.muted("Docs:")} ${formatDocsLink( "/cli/browser", - "docs.clawd.bot/cli/browser", + "docs.molt.bot/cli/browser", )}\n`, ) .action(() => { diff --git a/src/cli/channels-cli.ts b/src/cli/channels-cli.ts index 97fbee520..6d90ae935 100644 --- a/src/cli/channels-cli.ts +++ b/src/cli/channels-cli.ts @@ -76,7 +76,7 @@ export function registerChannelsCli(program: Command) { () => `\n${theme.muted("Docs:")} ${formatDocsLink( "/cli/channels", - "docs.clawd.bot/cli/channels", + "docs.molt.bot/cli/channels", )}\n`, ); diff --git a/src/cli/command-format.ts b/src/cli/command-format.ts index 9e5eef160..e62806cd4 100644 --- a/src/cli/command-format.ts +++ b/src/cli/command-format.ts @@ -1,6 +1,7 @@ import { normalizeProfileName } from "./profile-utils.js"; +import { replaceCliName, resolveCliName } from "./cli-name.js"; -const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+clawdbot\b|^clawdbot\b/; +const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+(?:clawdbot|moltbot)\b|^(?:clawdbot|moltbot)\b/; const PROFILE_FLAG_RE = /(?:^|\s)--profile(?:\s|=|$)/; const DEV_FLAG_RE = /(?:^|\s)--dev(?:\s|$)/; @@ -8,9 +9,13 @@ export function formatCliCommand( command: string, env: Record = process.env as Record, ): string { + const cliName = resolveCliName(undefined, env); + const normalizedCommand = replaceCliName(command, cliName); const profile = normalizeProfileName(env.CLAWDBOT_PROFILE); - if (!profile) return command; - if (!CLI_PREFIX_RE.test(command)) return command; - if (PROFILE_FLAG_RE.test(command) || DEV_FLAG_RE.test(command)) return command; - return command.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`); + if (!profile) return normalizedCommand; + if (!CLI_PREFIX_RE.test(normalizedCommand)) return normalizedCommand; + if (PROFILE_FLAG_RE.test(normalizedCommand) || DEV_FLAG_RE.test(normalizedCommand)) { + return normalizedCommand; + } + return normalizedCommand.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`); } diff --git a/src/cli/config-cli.ts b/src/cli/config-cli.ts index 605482251..80550599d 100644 --- a/src/cli/config-cli.ts +++ b/src/cli/config-cli.ts @@ -185,7 +185,7 @@ export function registerConfigCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/config", "docs.clawd.bot/cli/config")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/config", "docs.molt.bot/cli/config")}\n`, ) .option( "--section
    ", diff --git a/src/cli/cron-cli/register.ts b/src/cli/cron-cli/register.ts index 7989e08fb..aa115f1fa 100644 --- a/src/cli/cron-cli/register.ts +++ b/src/cli/cron-cli/register.ts @@ -15,7 +15,7 @@ export function registerCronCli(program: Command) { .description("Manage cron jobs (via Gateway)") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/cron", "docs.clawd.bot/cli/cron")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/cron", "docs.molt.bot/cli/cron")}\n`, ); registerCronStatusCommand(cron); diff --git a/src/cli/daemon-cli/register.ts b/src/cli/daemon-cli/register.ts index 92b47690d..8c180c732 100644 --- a/src/cli/daemon-cli/register.ts +++ b/src/cli/daemon-cli/register.ts @@ -18,7 +18,7 @@ export function registerDaemonCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.clawd.bot/cli/gateway")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.molt.bot/cli/gateway")}\n`, ); daemon diff --git a/src/cli/daemon-cli/status.print.ts b/src/cli/daemon-cli/status.print.ts index 801980d24..dc34e00e6 100644 --- a/src/cli/daemon-cli/status.print.ts +++ b/src/cli/daemon-cli/status.print.ts @@ -322,5 +322,5 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean }) } defaultRuntime.log(`${label("Troubles:")} run ${formatCliCommand("clawdbot status")}`); - defaultRuntime.log(`${label("Troubleshooting:")} https://docs.clawd.bot/troubleshooting`); + defaultRuntime.log(`${label("Troubleshooting:")} https://docs.molt.bot/troubleshooting`); } diff --git a/src/cli/directory-cli.ts b/src/cli/directory-cli.ts index d58663d8a..51fdd6049 100644 --- a/src/cli/directory-cli.ts +++ b/src/cli/directory-cli.ts @@ -39,7 +39,7 @@ export function registerDirectoryCli(program: Command) { () => `\n${theme.muted("Docs:")} ${formatDocsLink( "/cli/directory", - "docs.clawd.bot/cli/directory", + "docs.molt.bot/cli/directory", )}\n`, ) .action(() => { diff --git a/src/cli/dns-cli.ts b/src/cli/dns-cli.ts index 7798415e8..c063163a5 100644 --- a/src/cli/dns-cli.ts +++ b/src/cli/dns-cli.ts @@ -97,7 +97,7 @@ export function registerDnsCli(program: Command) { .description("DNS helpers for wide-area discovery (Tailscale + CoreDNS)") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/dns", "docs.clawd.bot/cli/dns")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/dns", "docs.molt.bot/cli/dns")}\n`, ); dns diff --git a/src/cli/docs-cli.ts b/src/cli/docs-cli.ts index 8078b8afe..0bcbe3bac 100644 --- a/src/cli/docs-cli.ts +++ b/src/cli/docs-cli.ts @@ -13,7 +13,7 @@ export function registerDocsCli(program: Command) { .argument("[query...]", "Search query") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/docs", "docs.clawd.bot/cli/docs")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/docs", "docs.molt.bot/cli/docs")}\n`, ) .action(async (queryParts: string[]) => { await runCommandWithRuntime(defaultRuntime, async () => { diff --git a/src/cli/exec-approvals-cli.ts b/src/cli/exec-approvals-cli.ts index f59b5475b..8b420db50 100644 --- a/src/cli/exec-approvals-cli.ts +++ b/src/cli/exec-approvals-cli.ts @@ -233,7 +233,7 @@ export function registerExecApprovalsCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.clawd.bot/cli/approvals")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.molt.bot/cli/approvals")}\n`, ); const getCmd = approvals @@ -337,7 +337,7 @@ export function registerExecApprovalsCli(program: Command) { )}\n${formatExample( 'clawdbot approvals allowlist remove "~/Projects/**/bin/rg"', "Remove an allowlist pattern.", - )}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.clawd.bot/cli/approvals")}\n`, + )}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/approvals", "docs.molt.bot/cli/approvals")}\n`, ); const allowlistAdd = allowlist diff --git a/src/cli/gateway-cli/register.ts b/src/cli/gateway-cli/register.ts index 8334cc6f7..eec58dd58 100644 --- a/src/cli/gateway-cli/register.ts +++ b/src/cli/gateway-cli/register.ts @@ -103,7 +103,7 @@ export function registerGatewayCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.clawd.bot/cli/gateway")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/gateway", "docs.molt.bot/cli/gateway")}\n`, ), ); diff --git a/src/cli/hooks-cli.test.ts b/src/cli/hooks-cli.test.ts index edbaf797d..6a4ee37fc 100644 --- a/src/cli/hooks-cli.test.ts +++ b/src/cli/hooks-cli.test.ts @@ -16,7 +16,7 @@ const report: HookStatusReport = { handlerPath: "/tmp/hooks/session-memory/handler.js", hookKey: "session-memory", emoji: "💾", - homepage: "https://docs.clawd.bot/hooks#session-memory", + homepage: "https://docs.molt.bot/hooks#session-memory", events: ["command:new"], always: false, disabled: false, diff --git a/src/cli/hooks-cli.ts b/src/cli/hooks-cli.ts index 022c7c295..a0eab75f5 100644 --- a/src/cli/hooks-cli.ts +++ b/src/cli/hooks-cli.ts @@ -424,7 +424,7 @@ export function registerHooksCli(program: Command): void { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/hooks", "docs.clawd.bot/cli/hooks")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/hooks", "docs.molt.bot/cli/hooks")}\n`, ); hooks diff --git a/src/cli/logs-cli.ts b/src/cli/logs-cli.ts index e5af23af8..8fcd8fffa 100644 --- a/src/cli/logs-cli.ts +++ b/src/cli/logs-cli.ts @@ -180,7 +180,7 @@ export function registerLogsCli(program: Command) { .option("--no-color", "Disable ANSI colors") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/logs", "docs.clawd.bot/cli/logs")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/logs", "docs.molt.bot/cli/logs")}\n`, ); addGatewayClientOptions(logs); diff --git a/src/cli/memory-cli.ts b/src/cli/memory-cli.ts index a184fa1b4..68894adf5 100644 --- a/src/cli/memory-cli.ts +++ b/src/cli/memory-cli.ts @@ -424,7 +424,7 @@ export function registerMemoryCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.clawd.bot/cli/memory")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.molt.bot/cli/memory")}\n`, ); memory diff --git a/src/cli/models-cli.ts b/src/cli/models-cli.ts index d914629e7..f68631d18 100644 --- a/src/cli/models-cli.ts +++ b/src/cli/models-cli.ts @@ -44,7 +44,7 @@ export function registerModelsCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/models", "docs.clawd.bot/cli/models")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/models", "docs.molt.bot/cli/models")}\n`, ); models diff --git a/src/cli/node-cli/register.ts b/src/cli/node-cli/register.ts index 8712c6a44..8f5c5b5ad 100644 --- a/src/cli/node-cli/register.ts +++ b/src/cli/node-cli/register.ts @@ -23,7 +23,7 @@ export function registerNodeCli(program: Command) { .description("Run a headless node host (system.run/system.which)") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.clawd.bot/cli/node")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.molt.bot/cli/node")}\n`, ); node diff --git a/src/cli/nodes-camera.test.ts b/src/cli/nodes-camera.test.ts index a024b535a..55d62cf06 100644 --- a/src/cli/nodes-camera.test.ts +++ b/src/cli/nodes-camera.test.ts @@ -51,11 +51,11 @@ describe("nodes camera helpers", () => { tmpDir: "/tmp", id: "id1", }); - expect(p).toBe(path.join("/tmp", "clawdbot-camera-snap-front-id1.jpg")); + expect(p).toBe(path.join("/tmp", "moltbot-camera-snap-front-id1.jpg")); }); it("writes base64 to file", async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdbot-test-")); + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "moltbot-test-")); const out = path.join(dir, "x.bin"); await writeBase64ToFile(out, "aGk="); await expect(fs.readFile(out, "utf8")).resolves.toBe("hi"); diff --git a/src/cli/nodes-camera.ts b/src/cli/nodes-camera.ts index d91053e76..f5bf2f2ea 100644 --- a/src/cli/nodes-camera.ts +++ b/src/cli/nodes-camera.ts @@ -3,6 +3,8 @@ import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; +import { resolveCliName } from "./cli-name.js"; + export type CameraFacing = "front" | "back"; export type CameraSnapPayload = { @@ -70,7 +72,8 @@ export function cameraTempPath(opts: { const id = opts.id ?? randomUUID(); const facingPart = opts.facing ? `-${opts.facing}` : ""; const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`; - return path.join(tmpDir, `clawdbot-camera-${opts.kind}${facingPart}-${id}${ext}`); + const cliName = resolveCliName(); + return path.join(tmpDir, `${cliName}-camera-${opts.kind}${facingPart}-${id}${ext}`); } export async function writeBase64ToFile(filePath: string, base64: string) { diff --git a/src/cli/nodes-canvas.ts b/src/cli/nodes-canvas.ts index b021ef22f..577c20560 100644 --- a/src/cli/nodes-canvas.ts +++ b/src/cli/nodes-canvas.ts @@ -2,6 +2,8 @@ import { randomUUID } from "node:crypto"; import * as os from "node:os"; import * as path from "node:path"; +import { resolveCliName } from "./cli-name.js"; + export type CanvasSnapshotPayload = { format: string; base64: string; @@ -29,5 +31,6 @@ export function canvasSnapshotTempPath(opts: { ext: string; tmpDir?: string; id? const tmpDir = opts.tmpDir ?? os.tmpdir(); const id = opts.id ?? randomUUID(); const ext = opts.ext.startsWith(".") ? opts.ext : `.${opts.ext}`; - return path.join(tmpDir, `clawdbot-canvas-snapshot-${id}${ext}`); + const cliName = resolveCliName(); + return path.join(tmpDir, `${cliName}-canvas-snapshot-${id}${ext}`); } diff --git a/src/cli/nodes-cli/register.ts b/src/cli/nodes-cli/register.ts index 20dd61902..501b28103 100644 --- a/src/cli/nodes-cli/register.ts +++ b/src/cli/nodes-cli/register.ts @@ -17,7 +17,7 @@ export function registerNodesCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/nodes", "docs.clawd.bot/cli/nodes")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/nodes", "docs.molt.bot/cli/nodes")}\n`, ); registerNodesStatusCommands(nodes); diff --git a/src/cli/pairing-cli.ts b/src/cli/pairing-cli.ts index 4d405bff5..a5d0ae137 100644 --- a/src/cli/pairing-cli.ts +++ b/src/cli/pairing-cli.ts @@ -53,7 +53,7 @@ export function registerPairingCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/pairing", "docs.clawd.bot/cli/pairing")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/pairing", "docs.molt.bot/cli/pairing")}\n`, ); pairing diff --git a/src/cli/plugins-cli.ts b/src/cli/plugins-cli.ts index 303231bdb..57709e04a 100644 --- a/src/cli/plugins-cli.ts +++ b/src/cli/plugins-cli.ts @@ -98,7 +98,7 @@ export function registerPluginsCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/plugins", "docs.clawd.bot/cli/plugins")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/plugins", "docs.molt.bot/cli/plugins")}\n`, ); plugins @@ -521,7 +521,7 @@ export function registerPluginsCli(program: Command) { lines.push(`- ${target}${diag.message}`); } } - const docs = formatDocsLink("/plugin", "docs.clawd.bot/plugin"); + const docs = formatDocsLink("/plugin", "docs.molt.bot/plugin"); lines.push(""); lines.push(`${theme.muted("Docs:")} ${docs}`); defaultRuntime.log(lines.join("\n")); diff --git a/src/cli/profile.test.ts b/src/cli/profile.test.ts index 0470bcf2c..f17e56214 100644 --- a/src/cli/profile.test.ts +++ b/src/cli/profile.test.ts @@ -80,60 +80,60 @@ describe("applyCliProfileEnv", () => { describe("formatCliCommand", () => { it("returns command unchanged when no profile is set", () => { - expect(formatCliCommand("clawdbot doctor --fix", {})).toBe("clawdbot doctor --fix"); + expect(formatCliCommand("clawdbot doctor --fix", {})).toBe("moltbot doctor --fix"); }); it("returns command unchanged when profile is default", () => { expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "default" })).toBe( - "clawdbot doctor --fix", + "moltbot doctor --fix", ); }); it("returns command unchanged when profile is Default (case-insensitive)", () => { expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "Default" })).toBe( - "clawdbot doctor --fix", + "moltbot doctor --fix", ); }); it("returns command unchanged when profile is invalid", () => { expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "bad profile" })).toBe( - "clawdbot doctor --fix", + "moltbot doctor --fix", ); }); it("returns command unchanged when --profile is already present", () => { expect( formatCliCommand("clawdbot --profile work doctor --fix", { CLAWDBOT_PROFILE: "work" }), - ).toBe("clawdbot --profile work doctor --fix"); + ).toBe("moltbot --profile work doctor --fix"); }); it("returns command unchanged when --dev is already present", () => { expect(formatCliCommand("clawdbot --dev doctor", { CLAWDBOT_PROFILE: "dev" })).toBe( - "clawdbot --dev doctor", + "moltbot --dev doctor", ); }); it("inserts --profile flag when profile is set", () => { expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: "work" })).toBe( - "clawdbot --profile work doctor --fix", + "moltbot --profile work doctor --fix", ); }); it("trims whitespace from profile", () => { expect(formatCliCommand("clawdbot doctor --fix", { CLAWDBOT_PROFILE: " jbclawd " })).toBe( - "clawdbot --profile jbclawd doctor --fix", + "moltbot --profile jbclawd doctor --fix", ); }); it("handles command with no args after clawdbot", () => { expect(formatCliCommand("clawdbot", { CLAWDBOT_PROFILE: "test" })).toBe( - "clawdbot --profile test", + "moltbot --profile test", ); }); it("handles pnpm wrapper", () => { expect(formatCliCommand("pnpm clawdbot doctor", { CLAWDBOT_PROFILE: "work" })).toBe( - "pnpm clawdbot --profile work doctor", + "pnpm moltbot --profile work doctor", ); }); }); diff --git a/src/cli/program.nodes-media.test.ts b/src/cli/program.nodes-media.test.ts index 60513b8e1..3132a98b9 100644 --- a/src/cli/program.nodes-media.test.ts +++ b/src/cli/program.nodes-media.test.ts @@ -174,7 +174,7 @@ describe("cli program (nodes media)", () => { const out = String(runtime.log.mock.calls[0]?.[0] ?? ""); const mediaPath = out.replace(/^MEDIA:/, "").trim(); - expect(mediaPath).toMatch(/clawdbot-camera-clip-front-.*\.mp4$/); + expect(mediaPath).toMatch(/moltbot-camera-clip-front-.*\.mp4$/); try { await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("hi"); @@ -421,7 +421,7 @@ describe("cli program (nodes media)", () => { const out = String(runtime.log.mock.calls[0]?.[0] ?? ""); const mediaPath = out.replace(/^MEDIA:/, "").trim(); - expect(mediaPath).toMatch(/clawdbot-canvas-snapshot-.*\.png$/); + expect(mediaPath).toMatch(/moltbot-canvas-snapshot-.*\.png$/); try { await expect(fs.readFile(mediaPath, "utf8")).resolves.toBe("hi"); diff --git a/src/cli/program/help.ts b/src/cli/program/help.ts index c90f9eb96..9ad704c16 100644 --- a/src/cli/program/help.ts +++ b/src/cli/program/help.ts @@ -2,8 +2,11 @@ import type { Command } from "commander"; import { formatDocsLink } from "../../terminal/links.js"; import { isRich, theme } from "../../terminal/theme.js"; import { formatCliBannerLine, hasEmittedCliBanner } from "../banner.js"; +import { replaceCliName, resolveCliName } from "../cli-name.js"; import type { ProgramContext } from "./context.js"; +const CLI_NAME = resolveCliName(); + const EXAMPLES = [ [ "clawdbot channels login --verbose", @@ -29,7 +32,7 @@ const EXAMPLES = [ export function configureProgramHelp(program: Command, ctx: ProgramContext) { program - .name("clawdbot") + .name(CLI_NAME) .description("") .version(ctx.programVersion) .option( @@ -77,12 +80,12 @@ export function configureProgramHelp(program: Command, ctx: ProgramContext) { }); const fmtExamples = EXAMPLES.map( - ([cmd, desc]) => ` ${theme.command(cmd)}\n ${theme.muted(desc)}`, + ([cmd, desc]) => ` ${theme.command(replaceCliName(cmd, CLI_NAME))}\n ${theme.muted(desc)}`, ).join("\n"); program.addHelpText("afterAll", ({ command }) => { if (command !== program) return ""; - const docs = formatDocsLink("/cli", "docs.clawd.bot/cli"); + const docs = formatDocsLink("/cli", "docs.molt.bot/cli"); return `\n${theme.heading("Examples:")}\n${fmtExamples}\n\n${theme.muted("Docs:")} ${docs}\n`; }); } diff --git a/src/cli/program/preaction.ts b/src/cli/program/preaction.ts index 1607e0634..acdcde387 100644 --- a/src/cli/program/preaction.ts +++ b/src/cli/program/preaction.ts @@ -6,6 +6,7 @@ import { ensureConfigReady } from "./config-guard.js"; import { ensurePluginRegistryLoaded } from "../plugin-registry.js"; import { isTruthyEnvValue } from "../../infra/env.js"; import { setVerbose } from "../../globals.js"; +import { resolveCliName } from "../cli-name.js"; function setProcessTitleForCommand(actionCommand: Command) { let current: Command = actionCommand; @@ -13,8 +14,9 @@ function setProcessTitleForCommand(actionCommand: Command) { current = current.parent; } const name = current.name(); - if (!name || name === "clawdbot") return; - process.title = `clawdbot-${name}`; + const cliName = resolveCliName(); + if (!name || name === cliName) return; + process.title = `${cliName}-${name}`; } // Commands that need channel plugins loaded diff --git a/src/cli/program/register.agent.ts b/src/cli/program/register.agent.ts index d597efde9..80eb9a35f 100644 --- a/src/cli/program/register.agent.ts +++ b/src/cli/program/register.agent.ts @@ -68,7 +68,7 @@ ${formatHelpExamples([ ], ])} -${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.clawd.bot/cli/agent")}`, +${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.molt.bot/cli/agent")}`, ) .action(async (opts) => { const verboseLevel = typeof opts.verbose === "string" ? opts.verbose.toLowerCase() : ""; @@ -86,7 +86,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/agent", "docs.clawd.bot/cli/agent .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/agents", "docs.clawd.bot/cli/agents")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/agents", "docs.molt.bot/cli/agents")}\n`, ); agents diff --git a/src/cli/program/register.configure.ts b/src/cli/program/register.configure.ts index a10805e88..55bec5209 100644 --- a/src/cli/program/register.configure.ts +++ b/src/cli/program/register.configure.ts @@ -16,7 +16,7 @@ export function registerConfigureCommand(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/configure", "docs.clawd.bot/cli/configure")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/configure", "docs.molt.bot/cli/configure")}\n`, ) .option( "--section
    ", diff --git a/src/cli/program/register.maintenance.ts b/src/cli/program/register.maintenance.ts index 023965c5c..efb8b7851 100644 --- a/src/cli/program/register.maintenance.ts +++ b/src/cli/program/register.maintenance.ts @@ -15,7 +15,7 @@ export function registerMaintenanceCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/doctor", "docs.clawd.bot/cli/doctor")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/doctor", "docs.molt.bot/cli/doctor")}\n`, ) .option("--no-workspace-suggestions", "Disable workspace memory system suggestions", false) .option("--yes", "Accept defaults without prompting", false) @@ -45,7 +45,7 @@ export function registerMaintenanceCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/dashboard", "docs.clawd.bot/cli/dashboard")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/dashboard", "docs.molt.bot/cli/dashboard")}\n`, ) .option("--no-open", "Print URL but do not launch a browser", false) .action(async (opts) => { @@ -62,7 +62,7 @@ export function registerMaintenanceCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/reset", "docs.clawd.bot/cli/reset")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/reset", "docs.molt.bot/cli/reset")}\n`, ) .option("--scope ", "config|config+creds+sessions|full (default: interactive prompt)") .option("--yes", "Skip confirmation prompts", false) @@ -85,7 +85,7 @@ export function registerMaintenanceCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/uninstall", "docs.clawd.bot/cli/uninstall")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/uninstall", "docs.molt.bot/cli/uninstall")}\n`, ) .option("--service", "Remove the gateway service", false) .option("--state", "Remove state + config", false) diff --git a/src/cli/program/register.message.ts b/src/cli/program/register.message.ts index 24af5f37f..b9fa22a47 100644 --- a/src/cli/program/register.message.ts +++ b/src/cli/program/register.message.ts @@ -46,7 +46,7 @@ ${formatHelpExamples([ ], ])} -${theme.muted("Docs:")} ${formatDocsLink("/cli/message", "docs.clawd.bot/cli/message")}`, +${theme.muted("Docs:")} ${formatDocsLink("/cli/message", "docs.molt.bot/cli/message")}`, ) .action(() => { message.help({ error: true }); diff --git a/src/cli/program/register.onboard.ts b/src/cli/program/register.onboard.ts index eac6a60df..8f31635f0 100644 --- a/src/cli/program/register.onboard.ts +++ b/src/cli/program/register.onboard.ts @@ -38,7 +38,7 @@ export function registerOnboardCommand(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/onboard", "docs.clawd.bot/cli/onboard")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/onboard", "docs.molt.bot/cli/onboard")}\n`, ) .option("--workspace ", "Agent workspace directory (default: ~/clawd)") .option("--reset", "Reset config + credentials + sessions + workspace before running wizard") diff --git a/src/cli/program/register.setup.ts b/src/cli/program/register.setup.ts index aa79a5dda..ad36a70e5 100644 --- a/src/cli/program/register.setup.ts +++ b/src/cli/program/register.setup.ts @@ -14,7 +14,7 @@ export function registerSetupCommand(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/setup", "docs.clawd.bot/cli/setup")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/setup", "docs.molt.bot/cli/setup")}\n`, ) .option( "--workspace ", diff --git a/src/cli/program/register.status-health-sessions.ts b/src/cli/program/register.status-health-sessions.ts index 66d0c4abb..42913588f 100644 --- a/src/cli/program/register.status-health-sessions.ts +++ b/src/cli/program/register.status-health-sessions.ts @@ -53,7 +53,7 @@ export function registerStatusHealthSessionsCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/status", "docs.clawd.bot/cli/status")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/status", "docs.molt.bot/cli/status")}\n`, ) .action(async (opts) => { const verbose = resolveVerbose(opts); @@ -87,7 +87,7 @@ export function registerStatusHealthSessionsCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/health", "docs.clawd.bot/cli/health")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/health", "docs.molt.bot/cli/health")}\n`, ) .action(async (opts) => { const verbose = resolveVerbose(opts); @@ -130,7 +130,7 @@ export function registerStatusHealthSessionsCommands(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/sessions", "docs.clawd.bot/cli/sessions")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/sessions", "docs.molt.bot/cli/sessions")}\n`, ) .action(async (opts) => { setVerbose(Boolean(opts.verbose)); diff --git a/src/cli/sandbox-cli.ts b/src/cli/sandbox-cli.ts index 746566f82..bcf9df54a 100644 --- a/src/cli/sandbox-cli.ts +++ b/src/cli/sandbox-cli.ts @@ -68,7 +68,7 @@ export function registerSandboxCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/sandbox", "docs.clawd.bot/cli/sandbox")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/sandbox", "docs.molt.bot/cli/sandbox")}\n`, ) .action(() => { sandbox.help({ error: true }); diff --git a/src/cli/security-cli.ts b/src/cli/security-cli.ts index 2bd5a36b7..7a72ad6da 100644 --- a/src/cli/security-cli.ts +++ b/src/cli/security-cli.ts @@ -34,7 +34,7 @@ export function registerSecurityCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.clawd.bot/cli/security")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/security", "docs.molt.bot/cli/security")}\n`, ); security diff --git a/src/cli/skills-cli.ts b/src/cli/skills-cli.ts index 7488aee83..520b1a5c0 100644 --- a/src/cli/skills-cli.ts +++ b/src/cli/skills-cli.ts @@ -337,7 +337,7 @@ export function registerSkillsCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/skills", "docs.clawd.bot/cli/skills")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/skills", "docs.molt.bot/cli/skills")}\n`, ); skills diff --git a/src/cli/system-cli.ts b/src/cli/system-cli.ts index 4162c16de..9ade7aa15 100644 --- a/src/cli/system-cli.ts +++ b/src/cli/system-cli.ts @@ -23,7 +23,7 @@ export function registerSystemCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/system", "docs.clawd.bot/cli/system")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/system", "docs.molt.bot/cli/system")}\n`, ); addGatewayClientOptions( diff --git a/src/cli/tui-cli.ts b/src/cli/tui-cli.ts index 3fbf6bc86..795642410 100644 --- a/src/cli/tui-cli.ts +++ b/src/cli/tui-cli.ts @@ -20,7 +20,7 @@ export function registerTuiCli(program: Command) { .option("--history-limit ", "History entries to load", "200") .addHelpText( "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/tui", "docs.clawd.bot/cli/tui")}\n`, + () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/tui", "docs.molt.bot/cli/tui")}\n`, ) .action(async (opts) => { try { diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index 9e597e485..c80878f90 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -39,6 +39,7 @@ import { trimLogTail } from "../infra/restart-sentinel.js"; import { defaultRuntime } from "../runtime.js"; import { formatDocsLink } from "../terminal/links.js"; import { formatCliCommand } from "./command-format.js"; +import { replaceCliName, resolveCliName } from "./cli-name.js"; import { stylePromptHint, stylePromptMessage } from "../terminal/prompt-style.js"; import { theme } from "../terminal/theme.js"; import { renderTable } from "../terminal/table.js"; @@ -81,6 +82,7 @@ const STEP_LABELS: Record = { build: "Building", "ui:build": "Building UI", "clawdbot doctor": "Running doctor checks", + "moltbot doctor": "Running doctor checks", "git rev-parse HEAD (after)": "Verifying update", "global update": "Updating via package manager", "global install": "Installing global package", @@ -110,6 +112,9 @@ const UPDATE_QUIPS = [ ]; const MAX_LOG_CHARS = 8000; +const DEFAULT_PACKAGE_NAME = "moltbot"; +const CORE_PACKAGE_NAMES = new Set([DEFAULT_PACKAGE_NAME, "clawdbot"]); +const CLI_NAME = resolveCliName(); const CLAWDBOT_REPO_URL = "https://github.com/clawdbot/clawdbot.git"; const DEFAULT_GIT_DIR = path.join(os.homedir(), "clawdbot"); @@ -117,7 +122,11 @@ function normalizeTag(value?: string | null): string | null { if (!value) return null; const trimmed = value.trim(); if (!trimmed) return null; - return trimmed.startsWith("clawdbot@") ? trimmed.slice("clawdbot@".length) : trimmed; + if (trimmed.startsWith("clawdbot@")) return trimmed.slice("clawdbot@".length); + if (trimmed.startsWith(`${DEFAULT_PACKAGE_NAME}@`)) { + return trimmed.slice(`${DEFAULT_PACKAGE_NAME}@`.length); + } + return trimmed; } function pickUpdateQuip(): string { @@ -157,16 +166,22 @@ async function isGitCheckout(root: string): Promise { } } -async function isClawdbotPackage(root: string): Promise { +async function readPackageName(root: string): Promise { try { const raw = await fs.readFile(path.join(root, "package.json"), "utf-8"); const parsed = JSON.parse(raw) as { name?: string }; - return parsed?.name === "clawdbot"; + const name = parsed?.name?.trim(); + return name ? name : null; } catch { - return false; + return null; } } +async function isCorePackage(root: string): Promise { + const name = await readPackageName(root); + return Boolean(name && CORE_PACKAGE_NAMES.has(name)); +} + async function pathExists(targetPath: string): Promise { try { await fs.stat(targetPath); @@ -269,8 +284,8 @@ async function ensureGitCheckout(params: { }); } - if (!(await isClawdbotPackage(params.dir))) { - throw new Error(`CLAWDBOT_GIT_DIR does not look like a clawdbot checkout: ${params.dir}.`); + if (!(await isCorePackage(params.dir))) { + throw new Error(`CLAWDBOT_GIT_DIR does not look like a core checkout: ${params.dir}.`); } return null; @@ -691,10 +706,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { return { stdout: res.stdout, stderr: res.stderr, code: res.code }; }; const pkgRoot = await resolveGlobalPackageRoot(manager, runCommand, timeoutMs ?? 20 * 60_000); + const packageName = + (pkgRoot ? await readPackageName(pkgRoot) : await readPackageName(root)) ?? + DEFAULT_PACKAGE_NAME; const beforeVersion = pkgRoot ? await readPackageVersion(pkgRoot) : null; const updateStep = await runUpdateStep({ name: "global update", - argv: globalInstallArgs(manager, `clawdbot@${tag}`), + argv: globalInstallArgs(manager, `${packageName}@${tag}`), timeoutMs: timeoutMs ?? 20 * 60_000, progress, }); @@ -705,7 +723,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { const entryPath = path.join(pkgRoot, "dist", "entry.js"); if (await pathExists(entryPath)) { const doctorStep = await runUpdateStep({ - name: "clawdbot doctor", + name: `${CLI_NAME} doctor`, argv: [resolveNodeRunner(), entryPath, "doctor", "--non-interactive"], timeoutMs: timeoutMs ?? 20 * 60_000, progress, @@ -806,11 +824,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { if (result.reason === "not-git-install") { defaultRuntime.log( theme.warn( - `Skipped: this Clawdbot install isn't a git checkout, and the package manager couldn't be detected. Update via your package manager, then run \`${formatCliCommand("clawdbot doctor")}\` and \`${formatCliCommand("clawdbot gateway restart")}\`.`, + `Skipped: this Clawdbot install isn't a git checkout, and the package manager couldn't be detected. Update via your package manager, then run \`${replaceCliName(formatCliCommand("clawdbot doctor"), CLI_NAME)}\` and \`${replaceCliName(formatCliCommand("clawdbot gateway restart"), CLI_NAME)}\`.`, ), ); defaultRuntime.log( - theme.muted("Examples: `npm i -g clawdbot@latest` or `pnpm add -g clawdbot@latest`"), + theme.muted( + `Examples: \`${replaceCliName("npm i -g clawdbot@latest", CLI_NAME)}\` or \`${replaceCliName("pnpm add -g clawdbot@latest", CLI_NAME)}\``, + ), ); } defaultRuntime.exit(0); @@ -926,7 +946,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`)); defaultRuntime.log( theme.muted( - `You may need to restart the service manually: ${formatCliCommand("clawdbot gateway restart")}`, + `You may need to restart the service manually: ${replaceCliName(formatCliCommand("clawdbot gateway restart"), CLI_NAME)}`, ), ); } @@ -936,13 +956,13 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { if (result.mode === "npm" || result.mode === "pnpm") { defaultRuntime.log( theme.muted( - `Tip: Run \`${formatCliCommand("clawdbot doctor")}\`, then \`${formatCliCommand("clawdbot gateway restart")}\` to apply updates to a running gateway.`, + `Tip: Run \`${replaceCliName(formatCliCommand("clawdbot doctor"), CLI_NAME)}\`, then \`${replaceCliName(formatCliCommand("clawdbot gateway restart"), CLI_NAME)}\` to apply updates to a running gateway.`, ), ); } else { defaultRuntime.log( theme.muted( - `Tip: Run \`${formatCliCommand("clawdbot gateway restart")}\` to apply updates to a running gateway.`, + `Tip: Run \`${replaceCliName(formatCliCommand("clawdbot gateway restart"), CLI_NAME)}\` to apply updates to a running gateway.`, ), ); } @@ -1137,7 +1157,7 @@ ${theme.heading("Notes:")} - Downgrades require confirmation (can break configuration) - Skips update if the working directory has uncommitted changes -${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.clawd.bot/cli/update")}`; +${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}`; }) .action(async (opts) => { try { @@ -1161,7 +1181,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.clawd.bot/cli/upda .option("--timeout ", "Timeout for each update step in seconds (default: 1200)") .addHelpText( "after", - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.clawd.bot/cli/update")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}\n`, ) .action(async (opts) => { try { @@ -1188,7 +1208,7 @@ ${theme.muted("Docs:")} ${formatDocsLink("/cli/update", "docs.clawd.bot/cli/upda "- Shows current update channel (stable/beta/dev) and source", )}\n${theme.muted("- Includes git tag/branch/SHA for source checkouts")}\n\n${theme.muted( "Docs:", - )} ${formatDocsLink("/cli/update", "docs.clawd.bot/cli/update")}`, + )} ${formatDocsLink("/cli/update", "docs.molt.bot/cli/update")}`, ) .action(async (opts) => { try { diff --git a/src/cli/webhooks-cli.ts b/src/cli/webhooks-cli.ts index 08c77af4d..1792533c2 100644 --- a/src/cli/webhooks-cli.ts +++ b/src/cli/webhooks-cli.ts @@ -28,7 +28,7 @@ export function registerWebhooksCli(program: Command) { .addHelpText( "after", () => - `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/webhooks", "docs.clawd.bot/cli/webhooks")}\n`, + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/webhooks", "docs.molt.bot/cli/webhooks")}\n`, ); const gmail = webhooks.command("gmail").description("Gmail Pub/Sub hooks (via gogcli)"); diff --git a/src/commands/agents.commands.add.ts b/src/commands/agents.commands.add.ts index 53b8ba049..5423ac79e 100644 --- a/src/commands/agents.commands.add.ts +++ b/src/commands/agents.commands.add.ts @@ -326,7 +326,7 @@ export async function agentsAddCommand( await prompter.note( [ "Routing unchanged. Add bindings when you're ready.", - "Docs: https://docs.clawd.bot/concepts/multi-agent", + "Docs: https://docs.molt.bot/concepts/multi-agent", ].join("\n"), "Routing", ); diff --git a/src/commands/auth-choice.apply.openai.ts b/src/commands/auth-choice.apply.openai.ts index 947b81181..d00208fab 100644 --- a/src/commands/auth-choice.apply.openai.ts +++ b/src/commands/auth-choice.apply.openai.ts @@ -138,7 +138,7 @@ export async function applyAuthChoiceOpenAI( spin.stop("OpenAI OAuth failed"); params.runtime.error(String(err)); await params.prompter.note( - "Trouble with OAuth? See https://docs.clawd.bot/start/faq", + "Trouble with OAuth? See https://docs.molt.bot/start/faq", "OAuth help", ); } diff --git a/src/commands/channels.adds-non-default-telegram-account.test.ts b/src/commands/channels.adds-non-default-telegram-account.test.ts index d5241a2fd..ff04b5c24 100644 --- a/src/commands/channels.adds-non-default-telegram-account.test.ts +++ b/src/commands/channels.adds-non-default-telegram-account.test.ts @@ -370,7 +370,7 @@ describe("channels command", () => { }); expect(lines.join("\n")).toMatch(/Warnings:/); expect(lines.join("\n")).toMatch(/Message Content Intent is disabled/i); - expect(lines.join("\n")).toMatch(/Run: clawdbot( --profile isolated)? doctor/); + expect(lines.join("\n")).toMatch(/Run: (?:clawdbot|moltbot)( --profile isolated)? doctor/); }); it("surfaces Discord permission audit issues in channels status output", () => { diff --git a/src/commands/configure.gateway.ts b/src/commands/configure.gateway.ts index d572e54a9..273231f31 100644 --- a/src/commands/configure.gateway.ts +++ b/src/commands/configure.gateway.ts @@ -139,9 +139,7 @@ export async function promptGatewayConfig( let tailscaleResetOnExit = false; if (tailscaleMode !== "off") { note( - ["Docs:", "https://docs.clawd.bot/gateway/tailscale", "https://docs.clawd.bot/web"].join( - "\n", - ), + ["Docs:", "https://docs.molt.bot/gateway/tailscale", "https://docs.molt.bot/web"].join("\n"), "Tailscale", ); tailscaleResetOnExit = Boolean( diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 7563643e1..535efef31 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -100,7 +100,7 @@ async function promptWebToolsConfig( [ "Web search lets your agent look things up online using the `web_search` tool.", "It requires a Brave Search API key (you can store it in the config or set BRAVE_API_KEY in the Gateway environment).", - "Docs: https://docs.clawd.bot/tools/web", + "Docs: https://docs.molt.bot/tools/web", ].join("\n"), "Web search", ); @@ -136,7 +136,7 @@ async function promptWebToolsConfig( [ "No key stored yet, so web_search will stay unavailable.", "Store a key here or set BRAVE_API_KEY in the Gateway environment.", - "Docs: https://docs.clawd.bot/tools/web", + "Docs: https://docs.molt.bot/tools/web", ].join("\n"), "Web search", ); @@ -189,7 +189,7 @@ export async function runConfigureWizard( [ ...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`), "", - "Docs: https://docs.clawd.bot/gateway/configuration", + "Docs: https://docs.molt.bot/gateway/configuration", ].join("\n"), "Config issues", ); @@ -393,8 +393,8 @@ export async function runConfigureWizard( note( [ "Docs:", - "https://docs.clawd.bot/gateway/health", - "https://docs.clawd.bot/gateway/troubleshooting", + "https://docs.molt.bot/gateway/health", + "https://docs.molt.bot/gateway/troubleshooting", ].join("\n"), "Health check help", ); @@ -518,8 +518,8 @@ export async function runConfigureWizard( note( [ "Docs:", - "https://docs.clawd.bot/gateway/health", - "https://docs.clawd.bot/gateway/troubleshooting", + "https://docs.molt.bot/gateway/health", + "https://docs.molt.bot/gateway/troubleshooting", ].join("\n"), "Health check help", ); @@ -577,7 +577,7 @@ export async function runConfigureWizard( `Web UI: ${links.httpUrl}`, `Gateway WS: ${links.wsUrl}`, gatewayStatusLine, - "Docs: https://docs.clawd.bot/web/control-ui", + "Docs: https://docs.molt.bot/web/control-ui", ].join("\n"), "Control UI", ); diff --git a/src/commands/daemon-install-helpers.test.ts b/src/commands/daemon-install-helpers.test.ts index e3b873737..179da04a2 100644 --- a/src/commands/daemon-install-helpers.test.ts +++ b/src/commands/daemon-install-helpers.test.ts @@ -235,7 +235,7 @@ describe("gatewayInstallErrorHint", () => { it("returns platform-specific hints", () => { expect(gatewayInstallErrorHint("win32")).toContain("Run as administrator"); expect(gatewayInstallErrorHint("linux")).toMatch( - /clawdbot( --profile isolated)? gateway install/, + /(?:clawdbot|moltbot)( --profile isolated)? gateway install/, ); }); }); diff --git a/src/commands/docs.ts b/src/commands/docs.ts index 4ca479f65..d0f01ce6e 100644 --- a/src/commands/docs.ts +++ b/src/commands/docs.ts @@ -5,7 +5,7 @@ import { formatDocsLink } from "../terminal/links.js"; import { isRich, theme } from "../terminal/theme.js"; import { formatCliCommand } from "../cli/command-format.js"; -const SEARCH_TOOL = "https://docs.clawd.bot/mcp.SearchClawdbot"; +const SEARCH_TOOL = "https://docs.molt.bot/mcp.SearchClawdbot"; const SEARCH_TIMEOUT_MS = 30_000; const DEFAULT_SNIPPET_MAX = 220; @@ -148,12 +148,12 @@ async function renderMarkdown(markdown: string, runtime: RuntimeEnv) { export async function docsSearchCommand(queryParts: string[], runtime: RuntimeEnv) { const query = queryParts.join(" ").trim(); if (!query) { - const docs = formatDocsLink("/", "docs.clawd.bot"); + const docs = formatDocsLink("/", "docs.molt.bot"); if (isRich()) { runtime.log(`${theme.muted("Docs:")} ${docs}`); runtime.log(`${theme.muted("Search:")} ${formatCliCommand('clawdbot docs "your query"')}`); } else { - runtime.log("Docs: https://docs.clawd.bot/"); + runtime.log("Docs: https://docs.molt.bot/"); runtime.log(`Search: ${formatCliCommand('clawdbot docs "your query"')}`); } return; diff --git a/src/commands/onboard-helpers.ts b/src/commands/onboard-helpers.ts index dd8288a60..27c74ae00 100644 --- a/src/commands/onboard-helpers.ts +++ b/src/commands/onboard-helpers.ts @@ -177,8 +177,8 @@ export function formatControlUiSshHint(params: { localUrl, authedUrl, "Docs:", - "https://docs.clawd.bot/gateway/remote", - "https://docs.clawd.bot/web/control-ui", + "https://docs.molt.bot/gateway/remote", + "https://docs.molt.bot/web/control-ui", ] .filter(Boolean) .join("\n"); diff --git a/src/commands/onboard-hooks.test.ts b/src/commands/onboard-hooks.test.ts index 10c99140a..e83f61399 100644 --- a/src/commands/onboard-hooks.test.ts +++ b/src/commands/onboard-hooks.test.ts @@ -239,7 +239,7 @@ describe("onboard-hooks", () => { // Second note should confirm configuration expect(noteCalls[1][0]).toContain("Enabled 1 hook: session-memory"); - expect(noteCalls[1][0]).toMatch(/clawdbot( --profile isolated)? hooks list/); + expect(noteCalls[1][0]).toMatch(/(?:clawdbot|moltbot)( --profile isolated)? hooks list/); }); }); }); diff --git a/src/commands/onboard-hooks.ts b/src/commands/onboard-hooks.ts index 10cdf1293..9fcc322a7 100644 --- a/src/commands/onboard-hooks.ts +++ b/src/commands/onboard-hooks.ts @@ -15,7 +15,7 @@ export async function setupInternalHooks( "Hooks let you automate actions when agent commands are issued.", "Example: Save session context to memory when you issue /new.", "", - "Learn more: https://docs.clawd.bot/hooks", + "Learn more: https://docs.molt.bot/hooks", ].join("\n"), "Hooks", ); diff --git a/src/commands/onboard-non-interactive/local.ts b/src/commands/onboard-non-interactive/local.ts index 8701bac7a..ec78dfba9 100644 --- a/src/commands/onboard-non-interactive/local.ts +++ b/src/commands/onboard-non-interactive/local.ts @@ -125,7 +125,7 @@ export async function runNonInteractiveOnboardingLocal(params: { if (!opts.json) { runtime.log( - `Tip: run \`${formatCliCommand("clawdbot configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web`, + `Tip: run \`${formatCliCommand("clawdbot configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.molt.bot/tools/web`, ); } } diff --git a/src/commands/onboard-non-interactive/remote.ts b/src/commands/onboard-non-interactive/remote.ts index 1426448fc..6807434e0 100644 --- a/src/commands/onboard-non-interactive/remote.ts +++ b/src/commands/onboard-non-interactive/remote.ts @@ -47,7 +47,7 @@ export async function runNonInteractiveOnboardingRemote(params: { runtime.log(`Remote gateway: ${remoteUrl}`); runtime.log(`Auth: ${payload.auth}`); runtime.log( - `Tip: run \`${formatCliCommand("clawdbot configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.clawd.bot/tools/web`, + `Tip: run \`${formatCliCommand("clawdbot configure --section web")}\` to store your Brave API key for web_search. Docs: https://docs.molt.bot/tools/web`, ); } } diff --git a/src/commands/onboard-remote.ts b/src/commands/onboard-remote.ts index 24c8773c8..cb71cabdf 100644 --- a/src/commands/onboard-remote.ts +++ b/src/commands/onboard-remote.ts @@ -43,7 +43,7 @@ export async function promptRemoteGatewayConfig( await prompter.note( [ "Bonjour discovery requires dns-sd (macOS) or avahi-browse (Linux).", - "Docs: https://docs.clawd.bot/gateway/discovery", + "Docs: https://docs.molt.bot/gateway/discovery", ].join("\n"), "Discovery", ); @@ -96,7 +96,7 @@ export async function promptRemoteGatewayConfig( `ssh -N -L 18789:127.0.0.1:18789 @${host}${ selectedBeacon.sshPort ? ` -p ${selectedBeacon.sshPort}` : "" }`, - "Docs: https://docs.clawd.bot/gateway/remote", + "Docs: https://docs.molt.bot/gateway/remote", ].join("\n"), "SSH tunnel", ); diff --git a/src/commands/onboard-skills.ts b/src/commands/onboard-skills.ts index ce3b06123..8d90dccee 100644 --- a/src/commands/onboard-skills.ts +++ b/src/commands/onboard-skills.ts @@ -156,7 +156,7 @@ export async function setupSkills( runtime.log( `Tip: run \`${formatCliCommand("clawdbot doctor")}\` to review skills + requirements.`, ); - runtime.log("Docs: https://docs.clawd.bot/skills"); + runtime.log("Docs: https://docs.molt.bot/skills"); } } } diff --git a/src/commands/onboard.ts b/src/commands/onboard.ts index 348aca613..6ede2b768 100644 --- a/src/commands/onboard.ts +++ b/src/commands/onboard.ts @@ -44,7 +44,7 @@ export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv = runtime.error( [ "Non-interactive onboarding requires explicit risk acknowledgement.", - "Read: https://docs.clawd.bot/security", + "Read: https://docs.molt.bot/security", `Re-run with: ${formatCliCommand("clawdbot onboard --non-interactive --accept-risk ...")}`, ].join("\n"), ); @@ -65,7 +65,7 @@ export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv = [ "Windows detected.", "WSL2 is strongly recommended; native Windows is untested and more problematic.", - "Guide: https://docs.clawd.bot/windows", + "Guide: https://docs.molt.bot/windows", ].join("\n"), ); } diff --git a/src/commands/sandbox-explain.test.ts b/src/commands/sandbox-explain.test.ts index d3e5ea2ee..a2fa611d8 100644 --- a/src/commands/sandbox-explain.test.ts +++ b/src/commands/sandbox-explain.test.ts @@ -36,7 +36,7 @@ describe("sandbox explain command", () => { const out = logs.join(""); const parsed = JSON.parse(out); - expect(parsed).toHaveProperty("docsUrl", "https://docs.clawd.bot/sandbox"); + expect(parsed).toHaveProperty("docsUrl", "https://docs.molt.bot/sandbox"); expect(parsed).toHaveProperty("sandbox.mode", "all"); expect(parsed).toHaveProperty("sandbox.tools.sources.allow.source"); expect(Array.isArray(parsed.fixIt)).toBe(true); diff --git a/src/commands/sandbox-explain.ts b/src/commands/sandbox-explain.ts index daaa9e483..f7c4c1cdb 100644 --- a/src/commands/sandbox-explain.ts +++ b/src/commands/sandbox-explain.ts @@ -30,7 +30,7 @@ type SandboxExplainOptions = { json: boolean; }; -const SANDBOX_DOCS_URL = "https://docs.clawd.bot/sandbox"; +const SANDBOX_DOCS_URL = "https://docs.molt.bot/sandbox"; function normalizeExplainSessionKey(params: { cfg: ClawdbotConfig; @@ -307,7 +307,7 @@ export async function sandboxExplainCommand( lines.push(heading("Fix-it:")); for (const key of payload.fixIt) lines.push(` - ${key}`); lines.push(""); - lines.push(`${key("Docs:")} ${formatDocsLink("/sandbox", "docs.clawd.bot/sandbox")}`); + lines.push(`${key("Docs:")} ${formatDocsLink("/sandbox", "docs.molt.bot/sandbox")}`); runtime.log(`${lines.join("\n")}\n`); } diff --git a/src/commands/status-all/diagnosis.ts b/src/commands/status-all/diagnosis.ts index 90912da1b..5e73e3743 100644 --- a/src/commands/status-all/diagnosis.ts +++ b/src/commands/status-all/diagnosis.ts @@ -234,6 +234,6 @@ export async function appendStatusAllDiagnosis(params: { lines.push(""); lines.push(muted("Pasteable debug report. Auth tokens redacted.")); - lines.push("Troubleshooting: https://docs.clawd.bot/troubleshooting"); + lines.push("Troubleshooting: https://docs.molt.bot/troubleshooting"); lines.push(""); } diff --git a/src/commands/status.command.ts b/src/commands/status.command.ts index 1ce771ac8..c36b91c99 100644 --- a/src/commands/status.command.ts +++ b/src/commands/status.command.ts @@ -560,8 +560,8 @@ export async function statusCommand( } runtime.log(""); - runtime.log("FAQ: https://docs.clawd.bot/faq"); - runtime.log("Troubleshooting: https://docs.clawd.bot/troubleshooting"); + runtime.log("FAQ: https://docs.molt.bot/faq"); + runtime.log("Troubleshooting: https://docs.molt.bot/troubleshooting"); runtime.log(""); const updateHint = formatUpdateAvailableHint(update); if (updateHint) { diff --git a/src/commands/status.test.ts b/src/commands/status.test.ts index ca9d8ae96..047f43a07 100644 --- a/src/commands/status.test.ts +++ b/src/commands/status.test.ts @@ -330,6 +330,8 @@ describe("statusCommand", () => { expect( logs.some( (l) => + l.includes("moltbot status --all") || + l.includes("moltbot --profile isolated status --all") || l.includes("clawdbot status --all") || l.includes("clawdbot --profile isolated status --all"), ), diff --git a/src/hooks/bundled/README.md b/src/hooks/bundled/README.md index 48ad5ea95..77df25ee4 100644 --- a/src/hooks/bundled/README.md +++ b/src/hooks/bundled/README.md @@ -39,7 +39,7 @@ Swaps injected `SOUL.md` content with `SOUL_EVIL.md` during a purge window or by **Events**: `agent:bootstrap` **What it does**: Overrides the injected SOUL content before the system prompt is built. **Output**: No files written; swaps happen in-memory only. -**Docs**: https://docs.clawd.bot/hooks/soul-evil +**Docs**: https://docs.molt.bot/hooks/soul-evil **Enable**: @@ -82,7 +82,7 @@ session-memory/ --- name: my-hook description: "Short description" -homepage: https://docs.clawd.bot/hooks#my-hook +homepage: https://docs.molt.bot/hooks#my-hook metadata: { "clawdbot": { "emoji": "🔗", "events": ["command:new"], "requires": { "bins": ["node"] } } } --- @@ -221,4 +221,4 @@ Test your hooks by: ## Documentation -Full documentation: https://docs.clawd.bot/hooks +Full documentation: https://docs.molt.bot/hooks diff --git a/src/hooks/bundled/boot-md/HOOK.md b/src/hooks/bundled/boot-md/HOOK.md index dac210b62..2572c50cc 100644 --- a/src/hooks/bundled/boot-md/HOOK.md +++ b/src/hooks/bundled/boot-md/HOOK.md @@ -1,7 +1,7 @@ --- name: boot-md description: "Run BOOT.md on gateway startup" -homepage: https://docs.clawd.bot/hooks#boot-md +homepage: https://docs.molt.bot/hooks#boot-md metadata: { "clawdbot": diff --git a/src/hooks/bundled/command-logger/HOOK.md b/src/hooks/bundled/command-logger/HOOK.md index 10034fab8..bf33cdeff 100644 --- a/src/hooks/bundled/command-logger/HOOK.md +++ b/src/hooks/bundled/command-logger/HOOK.md @@ -1,7 +1,7 @@ --- name: command-logger description: "Log all command events to a centralized audit file" -homepage: https://docs.clawd.bot/hooks#command-logger +homepage: https://docs.molt.bot/hooks#command-logger metadata: { "clawdbot": diff --git a/src/hooks/bundled/session-memory/HOOK.md b/src/hooks/bundled/session-memory/HOOK.md index cc3eab0a2..bb3ac1468 100644 --- a/src/hooks/bundled/session-memory/HOOK.md +++ b/src/hooks/bundled/session-memory/HOOK.md @@ -1,7 +1,7 @@ --- name: session-memory description: "Save session context to memory when /new command is issued" -homepage: https://docs.clawd.bot/hooks#session-memory +homepage: https://docs.molt.bot/hooks#session-memory metadata: { "clawdbot": diff --git a/src/hooks/bundled/soul-evil/HOOK.md b/src/hooks/bundled/soul-evil/HOOK.md index 776163bd0..7bb50620b 100644 --- a/src/hooks/bundled/soul-evil/HOOK.md +++ b/src/hooks/bundled/soul-evil/HOOK.md @@ -1,7 +1,7 @@ --- name: soul-evil description: "Swap SOUL.md with SOUL_EVIL.md during a purge window or by random chance" -homepage: https://docs.clawd.bot/hooks/soul-evil +homepage: https://docs.molt.bot/hooks/soul-evil metadata: { "clawdbot": diff --git a/src/hooks/bundled/soul-evil/README.md b/src/hooks/bundled/soul-evil/README.md index 3f0e09a85..a49bfe33d 100644 --- a/src/hooks/bundled/soul-evil/README.md +++ b/src/hooks/bundled/soul-evil/README.md @@ -2,7 +2,7 @@ Small persona swap hook for Clawdbot. -Docs: https://docs.clawd.bot/hooks/soul-evil +Docs: https://docs.molt.bot/hooks/soul-evil ## Setup diff --git a/src/hooks/frontmatter.test.ts b/src/hooks/frontmatter.test.ts index 761eaa75f..91545796d 100644 --- a/src/hooks/frontmatter.test.ts +++ b/src/hooks/frontmatter.test.ts @@ -233,7 +233,7 @@ describe("resolveClawdbotMetadata", () => { const content = `--- name: session-memory description: "Save session context to memory when /new command is issued" -homepage: https://docs.clawd.bot/hooks#session-memory +homepage: https://docs.molt.bot/hooks#session-memory metadata: { "clawdbot": diff --git a/src/infra/clawdbot-root.ts b/src/infra/clawdbot-root.ts index 9a3443ffb..d7d20467f 100644 --- a/src/infra/clawdbot-root.ts +++ b/src/infra/clawdbot-root.ts @@ -2,6 +2,8 @@ import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +const CORE_PACKAGE_NAMES = new Set(["moltbot", "clawdbot"]); + async function readPackageName(dir: string): Promise { try { const raw = await fs.readFile(path.join(dir, "package.json"), "utf-8"); @@ -16,7 +18,7 @@ async function findPackageRoot(startDir: string, maxDepth = 12): Promise { @@ -714,7 +732,8 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise< const beforeVersion = await readPackageVersion(pkgRoot); const globalManager = await detectGlobalInstallManagerForRoot(runCommand, pkgRoot, timeoutMs); if (globalManager) { - const spec = `clawdbot@${normalizeTag(opts.tag)}`; + const packageName = (await readPackageName(pkgRoot)) ?? DEFAULT_PACKAGE_NAME; + const spec = `${packageName}@${normalizeTag(opts.tag)}`; const updateStep = await runStep({ runCommand, name: "global update", diff --git a/src/pairing/pairing-messages.test.ts b/src/pairing/pairing-messages.test.ts index 416a6ab19..1997a0e30 100644 --- a/src/pairing/pairing-messages.test.ts +++ b/src/pairing/pairing-messages.test.ts @@ -52,9 +52,10 @@ describe("buildPairingReply", () => { expect(text).toContain(testCase.idLine); expect(text).toContain(`Pairing code: ${testCase.code}`); // CLI commands should respect CLAWDBOT_PROFILE when set (most tests run with isolated profile) - expect(text).toContain( - `clawdbot --profile isolated pairing approve ${testCase.channel} `, + const commandRe = new RegExp( + `(?:clawdbot|moltbot) --profile isolated pairing approve ${testCase.channel} `, ); + expect(text).toMatch(commandRe); }); } }); diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 931c15d59..178e2d3c2 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -206,7 +206,14 @@ export function loadClawdbotPlugins(options: PluginLoadOptions = {}): PluginRegi const jiti = createJiti(import.meta.url, { interopDefault: true, extensions: [".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".js", ".mjs", ".cjs", ".json"], - ...(pluginSdkAlias ? { alias: { "clawdbot/plugin-sdk": pluginSdkAlias } } : {}), + ...(pluginSdkAlias + ? { + alias: { + "clawdbot/plugin-sdk": pluginSdkAlias, + "moltbot/plugin-sdk": pluginSdkAlias, + }, + } + : {}), }); const manifestByRoot = new Map( diff --git a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts index 165488426..58ecc8d7a 100644 --- a/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts +++ b/src/telegram/bot.media.downloads-media-file-path-no-file-download.test.ts @@ -105,7 +105,8 @@ vi.mock("../auto-reply/reply.js", () => { }); describe("telegram inbound media", () => { - const INBOUND_MEDIA_TEST_TIMEOUT_MS = process.platform === "win32" ? 30_000 : 20_000; + // Parallel vitest shards can make this suite slower than the standalone run. + const INBOUND_MEDIA_TEST_TIMEOUT_MS = process.platform === "win32" ? 60_000 : 45_000; it( "downloads media via file_path (no file.download)", diff --git a/src/terminal/links.ts b/src/terminal/links.ts index b26ff37bb..0d965633a 100644 --- a/src/terminal/links.ts +++ b/src/terminal/links.ts @@ -1,6 +1,6 @@ import { formatTerminalLink } from "../utils.js"; -export const DOCS_ROOT = "https://docs.clawd.bot"; +export const DOCS_ROOT = "https://docs.molt.bot"; export function formatDocsLink( path: string, diff --git a/src/wizard/onboarding.finalize.ts b/src/wizard/onboarding.finalize.ts index 32ab53dcf..670b4eaf6 100644 --- a/src/wizard/onboarding.finalize.ts +++ b/src/wizard/onboarding.finalize.ts @@ -214,8 +214,8 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption await prompter.note( [ "Docs:", - "https://docs.clawd.bot/gateway/health", - "https://docs.clawd.bot/gateway/troubleshooting", + "https://docs.molt.bot/gateway/health", + "https://docs.molt.bot/gateway/troubleshooting", ].join("\n"), "Health check help", ); @@ -277,7 +277,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption tokenParam ? `Web UI (with token): ${authedUrl}` : undefined, `Gateway WS: ${links.wsUrl}`, gatewayStatusLine, - "Docs: https://docs.clawd.bot/web/control-ui", + "Docs: https://docs.molt.bot/web/control-ui", ] .filter(Boolean) .join("\n"), @@ -383,14 +383,14 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption } await prompter.note( - ["Back up your agent workspace.", "Docs: https://docs.clawd.bot/concepts/agent-workspace"].join( + ["Back up your agent workspace.", "Docs: https://docs.molt.bot/concepts/agent-workspace"].join( "\n", ), "Workspace backup", ); await prompter.note( - "Running agents on your computer is risky — harden your setup: https://docs.clawd.bot/security", + "Running agents on your computer is risky — harden your setup: https://docs.molt.bot/security", "Security", ); @@ -443,7 +443,7 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption webSearchKey ? "API key: stored in config (tools.web.search.apiKey)." : "API key: provided via BRAVE_API_KEY env var (Gateway environment).", - "Docs: https://docs.clawd.bot/tools/web", + "Docs: https://docs.molt.bot/tools/web", ].join("\n") : [ "If you want your agent to be able to search the web, you’ll need an API key.", @@ -455,13 +455,13 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption "- Enable web_search and paste your Brave Search API key", "", "Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).", - "Docs: https://docs.clawd.bot/tools/web", + "Docs: https://docs.molt.bot/tools/web", ].join("\n"), "Web search (optional)", ); await prompter.note( - 'What now: https://clawd.bot/showcase ("What People Are Building").', + 'What now: https://molt.bot/showcase ("What People Are Building").', "What now", ); diff --git a/src/wizard/onboarding.gateway-config.ts b/src/wizard/onboarding.gateway-config.ts index c68836b32..f8d56af62 100644 --- a/src/wizard/onboarding.gateway-config.ts +++ b/src/wizard/onboarding.gateway-config.ts @@ -145,9 +145,7 @@ export async function configureGatewayForOnboarding( let tailscaleResetOnExit = flow === "quickstart" ? quickstartGateway.tailscaleResetOnExit : false; if (tailscaleMode !== "off" && flow !== "quickstart") { await prompter.note( - ["Docs:", "https://docs.clawd.bot/gateway/tailscale", "https://docs.clawd.bot/web"].join( - "\n", - ), + ["Docs:", "https://docs.molt.bot/gateway/tailscale", "https://docs.molt.bot/web"].join("\n"), "Tailscale", ); tailscaleResetOnExit = Boolean( diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index 39d17befa..52f9285aa 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -70,7 +70,7 @@ async function requireRiskAcknowledgement(params: { "clawdbot security audit --deep", "clawdbot security audit --fix", "", - "Must read: https://docs.clawd.bot/gateway/security", + "Must read: https://docs.molt.bot/gateway/security", ].join("\n"), "Security", ); @@ -103,7 +103,7 @@ export async function runOnboardingWizard( [ ...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`), "", - "Docs: https://docs.clawd.bot/gateway/configuration", + "Docs: https://docs.molt.bot/gateway/configuration", ].join("\n"), "Config issues", ); diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index fe67c86f1..49f72de8c 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -180,7 +180,7 @@ export function renderApp(state: AppViewState) {