// @vitest-environment nodeimport { readFileSync } from "node:fs";import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const ensureBirdclawDirsMock = vi.fn();const getBirdclawPathsMock = vi.fn();const getDefaultAccountSelectorMock = vi.fn();const resolveOperationAccountMock = vi.fn();const resolveMentionsDataSourceMock = vi.fn();const setActionsTransportMock = vi.fn();const getQueryEnvelopeMock = vi.fn();const getNativeDbMock = vi.fn();const seedDemoDataMock = vi.fn();const findArchivesMock = vi.fn();const importArchiveMock = vi.fn();const importTweetsViaFxTwitterMock = vi.fn();const importThreadViaFxTwitterMock = vi.fn();const importConversationViaFxTwitterMock = vi.fn();const importProfileViaFxTwitterMock = vi.fn();const importSearchViaFxTwitterMock = vi.fn();const importBlocklistMock = vi.fn();const addBlockMock = vi.fn();const recordBlockMock = vi.fn();const syncBlocksMock = vi.fn();const exportMentionItemsMock = vi.fn();const exportMentionsViaCachedAutoMock = vi.fn();const exportMentionsViaCachedBirdMock = vi.fn();const exportMentionsViaCachedXurlMock = vi.fn();const syncMentionsMock = vi.fn();const syncMentionThreadsMock = vi.fn();const syncDirectMessagesViaCachedBirdMock = vi.fn();const resolveProfilesForIdsMock = vi.fn();const expandUrlsFromTextsMock = vi.fn();const searchLinksMock = vi.fn();const backfillLinkIndexMock = vi.fn();const fetchTweetMediaMock = vi.fn();const formatMediaFetchResultMock = vi.fn();const runWhoisMock = vi.fn();const formatWhoisMock = vi.fn();const listBlocksMock = vi.fn();const addMuteMock = vi.fn();const listMutesMock = vi.fn();const recordMuteMock = vi.fn();const listInboxItemsMock = vi.fn();const scoreInboxMock = vi.fn();const syncFollowGraphMock = vi.fn();const getFollowGraphSummaryMock = vi.fn();const listFollowEventsMock = vi.fn();const listMutualsMock = vi.fn();const listNonMutualFollowingMock = vi.fn();const listTopFollowersMock = vi.fn();const listUnfollowedSinceMock = vi.fn();const listTimelineItemsMock = vi.fn();const listDmConversationsMock = vi.fn();const applyDmRequestMutationToLocalStoreMock = vi.fn();const runDirectMessageRequestMutationViaBirdMock = vi.fn();const getAuthenticatedBirdAccountMock = vi.fn();const getConversationThreadMock = vi.fn();const hydrateProfilesFromXMock = vi.fn();const inspectProfileRepliesMock = vi.fn();const runResearchModeMock = vi.fn();const streamPeriodDigestMock = vi.fn();const streamSearchDiscussionMock = vi.fn();const syncAuthoredTweetsMock = vi.fn();const syncTimelineCollectionMock = vi.fn();const createPostMock = vi.fn();const createTweetReplyMock = vi.fn();const createDmReplyMock = vi.fn();const removeBlockMock = vi.fn();const removeMuteMock = vi.fn();const maybeAutoUpdateBackupMock = vi.fn();const maybeAutoSyncBackupMock = vi.fn();const exportBackupMock = vi.fn();const importBackupMock = vi.fn();const syncBackupMock = vi.fn();const validateBackupMock = vi.fn();const runProductionServerMock = vi.fn();const packageVersion = ( JSON.parse( readFileSync(new URL("../package.json", import.meta.url), "utf8"), ) as { version: string }).version;const execFileAsyncMock = vi.fn();const execFileMock = vi.fn();const consoleLogMock = vi.spyOn(console, "log").mockImplementation(() => {}); class FxTwitterErrorMock extends Error { readonly kind: string; readonly status?: number; readonly retryAfterMs?: number; constructor(options: { kind: string; message: string; status?: number; retryAfterMs?: number; }) { super(options.message); this.kind = options.kind; this.status = options.status; this.retryAfterMs = options.retryAfterMs; }} Object.defineProperty( execFileMock, Symbol.for("nodejs.util.promisify.custom"), { value: execFileAsyncMock, },); vi.mock("#/lib/config", () => ({ ensureBirdclawDirs: () => ensureBirdclawDirsMock(), getDefaultAccountSelector: () => getDefaultAccountSelectorMock(), getBirdclawConfig: () => ({}), getBirdclawPaths: () => getBirdclawPathsMock(), resolveMentionsDataSource: (...args: unknown[]) => resolveMentionsDataSourceMock(...args), setActionsTransport: (...args: unknown[]) => setActionsTransportMock(...args),})); vi.mock("#/lib/db", () => ({ closeDatabase: vi.fn(), getNativeDb: (...args: unknown[]) => getNativeDbMock(...args),})); vi.mock("#/lib/seed", () => ({ seedDemoData: (...args: unknown[]) => seedDemoDataMock(...args),})); vi.mock("#/lib/account-selection", () => ({ resolveOperationAccount: (...args: unknown[]) => resolveOperationAccountMock(...args),})); vi.mock("#/lib/dm-read-model", () => ({ getConversationThread: (...args: unknown[]) => getConversationThreadMock(...args),})); vi.mock("#/lib/archive-finder", () => ({ findArchives: () => findArchivesMock(),})); vi.mock("#/lib/archive-import", () => ({ ARCHIVE_IMPORT_SLICES: [ "tweets", "likes", "bookmarks", "directMessages", "profiles", "followers", "following", ], importArchive: (...args: unknown[]) => importArchiveMock(...args),})); vi.mock("#/lib/fxtwitter", () => ({ FxTwitterError: FxTwitterErrorMock, importConversationViaFxTwitter: (...args: unknown[]) => importConversationViaFxTwitterMock(...args), importProfileViaFxTwitter: (...args: unknown[]) => importProfileViaFxTwitterMock(...args), importSearchViaFxTwitter: (...args: unknown[]) => importSearchViaFxTwitterMock(...args), importThreadViaFxTwitter: (...args: unknown[]) => importThreadViaFxTwitterMock(...args), importTweetsViaFxTwitter: (...args: unknown[]) => importTweetsViaFxTwitterMock(...args),})); vi.mock("#/lib/backup", async (importOriginal) => { const actual = await importOriginal<typeof import("#/lib/backup")>(); return { ...actual, exportBackup: (...args: unknown[]) => exportBackupMock(...args), importBackup: (...args: unknown[]) => importBackupMock(...args), maybeAutoUpdateBackup: () => maybeAutoUpdateBackupMock(), maybeAutoSyncBackup: () => maybeAutoSyncBackupMock(), syncBackup: (...args: unknown[]) => syncBackupMock(...args), validateBackup: (...args: unknown[]) => validateBackupMock(...args), };}); vi.mock("#/lib/blocklist", () => ({ importBlocklist: (...args: unknown[]) => importBlocklistMock(...args),})); vi.mock("#/lib/blocks", () => ({ addBlock: (...args: unknown[]) => addBlockMock(...args), listBlocks: (...args: unknown[]) => listBlocksMock(...args), recordBlock: (...args: unknown[]) => recordBlockMock(...args), removeBlock: (...args: unknown[]) => removeBlockMock(...args), syncBlocks: (...args: unknown[]) => syncBlocksMock(...args),})); vi.mock("#/lib/inbox", () => ({ listInboxItems: (...args: unknown[]) => listInboxItemsMock(...args), scoreInbox: (...args: unknown[]) => scoreInboxMock(...args),})); vi.mock("#/lib/link-index", () => ({ backfillLinkIndex: (...args: unknown[]) => backfillLinkIndexMock(...args), searchLinks: (...args: unknown[]) => searchLinksMock(...args),})); vi.mock("#/lib/media-fetch", () => ({ fetchTweetMedia: (...args: unknown[]) => fetchTweetMediaMock(...args), formatMediaFetchResult: (...args: unknown[]) => formatMediaFetchResultMock(...args),})); vi.mock("#/lib/follow-graph", () => ({ getFollowGraphSummary: (...args: unknown[]) => getFollowGraphSummaryMock(...args), listFollowEvents: (...args: unknown[]) => listFollowEventsMock(...args), listMutuals: (...args: unknown[]) => listMutualsMock(...args), listNonMutualFollowing: (...args: unknown[]) => listNonMutualFollowingMock(...args), listTopFollowers: (...args: unknown[]) => listTopFollowersMock(...args), listUnfollowedSince: (...args: unknown[]) => listUnfollowedSinceMock(...args), syncFollowGraph: (...args: unknown[]) => syncFollowGraphMock(...args),})); vi.mock("#/lib/mutes", () => ({ addMute: (...args: unknown[]) => addMuteMock(...args), listMutes: (...args: unknown[]) => listMutesMock(...args), recordMute: (...args: unknown[]) => recordMuteMock(...args), removeMute: (...args: unknown[]) => removeMuteMock(...args),})); vi.mock("#/lib/mentions-export", () => ({ exportMentionItems: (...args: unknown[]) => exportMentionItemsMock(...args),})); vi.mock("#/lib/mentions-live", () => ({ exportMentionsViaCachedAuto: (...args: unknown[]) => exportMentionsViaCachedAutoMock(...args), exportMentionsViaCachedBird: (...args: unknown[]) => exportMentionsViaCachedBirdMock(...args), exportMentionsViaCachedXurl: (...args: unknown[]) => exportMentionsViaCachedXurlMock(...args), syncMentions: (...args: unknown[]) => syncMentionsMock(...args),})); vi.mock("#/lib/mention-threads-live", () => ({ syncMentionThreads: (...args: unknown[]) => syncMentionThreadsMock(...args),})); vi.mock("#/lib/dms-live", () => ({ syncDirectMessagesViaCachedBird: (...args: unknown[]) => syncDirectMessagesViaCachedBirdMock(...args),})); vi.mock("#/lib/profile-hydration", () => ({ hydrateProfilesFromX: (...args: unknown[]) => hydrateProfilesFromXMock(...args),})); vi.mock("#/lib/profile-resolver", () => ({ resolveProfilesForIds: (...args: unknown[]) => resolveProfilesForIdsMock(...args),})); vi.mock("#/lib/profile-replies", () => ({ inspectProfileReplies: (...args: unknown[]) => inspectProfileRepliesMock(...args),})); vi.mock("#/lib/research", () => ({ runResearchMode: (...args: unknown[]) => runResearchModeMock(...args),})); vi.mock("#/lib/period-digest", () => ({ normalizeDigestLanguage: (value: string | undefined) => { const trimmed = value?.trim(); if (!trimmed) return undefined; if (trimmed === "ZH-cn") return "zh-CN"; throw new Error( "Digest language must be a valid Unicode locale identifier", ); }, streamPeriodDigest: (...args: unknown[]) => streamPeriodDigestMock(...args),})); vi.mock("#/lib/search-discussion", () => ({ streamSearchDiscussion: (...args: unknown[]) => streamSearchDiscussionMock(...args),})); vi.mock("#/lib/authored-live", () => ({ AuthoredSyncError: class AuthoredSyncError extends Error { constructor( message: string, public readonly exitCode: number, ) { super(message); } }, syncAuthoredTweets: (...args: unknown[]) => syncAuthoredTweetsMock(...args),})); vi.mock("#/lib/queries", () => ({ applyDmRequestMutationToLocalStore: (...args: unknown[]) => applyDmRequestMutationToLocalStoreMock(...args), getQueryEnvelope: (...args: unknown[]) => getQueryEnvelopeMock(...args), listTimelineItems: (...args: unknown[]) => listTimelineItemsMock(...args), listDmConversations: (...args: unknown[]) => listDmConversationsMock(...args), createPost: (...args: unknown[]) => createPostMock(...args), createTweetReply: (...args: unknown[]) => createTweetReplyMock(...args), createDmReply: (...args: unknown[]) => createDmReplyMock(...args),})); vi.mock("#/lib/production-server", () => ({ runProductionServer: (...args: unknown[]) => runProductionServerMock(...args),})); vi.mock("#/lib/bird", () => ({ getAuthenticatedBirdAccount: (...args: unknown[]) => getAuthenticatedBirdAccountMock(...args), runDirectMessageRequestMutationViaBird: (...args: unknown[]) => runDirectMessageRequestMutationViaBirdMock(...args),})); vi.mock("#/lib/timeline-collections-live", () => ({ syncTimelineCollection: (...args: unknown[]) => syncTimelineCollectionMock(...args),})); vi.mock("#/lib/url-expansion", () => ({ expandUrlsFromTexts: (...args: unknown[]) => expandUrlsFromTextsMock(...args),})); vi.mock("#/lib/whois", () => ({ formatWhois: (...args: unknown[]) => formatWhoisMock(...args), runWhois: (...args: unknown[]) => runWhoisMock(...args),})); vi.mock("node:child_process", () => ({ execFile: execFileMock,})); async function loadCli() { vi.resetModules(); return import("./cli");} describe("cli", () => { beforeEach(() => { process.exitCode = undefined; consoleLogMock.mockClear(); ensureBirdclawDirsMock.mockReset(); getBirdclawPathsMock.mockReset(); getDefaultAccountSelectorMock.mockReset(); resolveOperationAccountMock.mockReset(); resolveMentionsDataSourceMock.mockReset(); setActionsTransportMock.mockReset(); getQueryEnvelopeMock.mockReset(); getNativeDbMock.mockReset(); seedDemoDataMock.mockReset(); findArchivesMock.mockReset(); importArchiveMock.mockReset(); importTweetsViaFxTwitterMock.mockReset(); importThreadViaFxTwitterMock.mockReset(); importConversationViaFxTwitterMock.mockReset(); importProfileViaFxTwitterMock.mockReset(); importSearchViaFxTwitterMock.mockReset(); importBlocklistMock.mockReset(); addBlockMock.mockReset(); recordBlockMock.mockReset(); syncBlocksMock.mockReset(); exportMentionItemsMock.mockReset(); exportMentionsViaCachedAutoMock.mockReset(); exportMentionsViaCachedBirdMock.mockReset(); exportMentionsViaCachedXurlMock.mockReset(); syncMentionsMock.mockReset(); syncMentionThreadsMock.mockReset(); syncDirectMessagesViaCachedBirdMock.mockReset(); resolveProfilesForIdsMock.mockReset(); expandUrlsFromTextsMock.mockReset(); searchLinksMock.mockReset(); backfillLinkIndexMock.mockReset(); fetchTweetMediaMock.mockReset(); formatMediaFetchResultMock.mockReset(); runWhoisMock.mockReset(); formatWhoisMock.mockReset(); listBlocksMock.mockReset(); addMuteMock.mockReset(); listMutesMock.mockReset(); recordMuteMock.mockReset(); listInboxItemsMock.mockReset(); scoreInboxMock.mockReset(); syncFollowGraphMock.mockReset(); getFollowGraphSummaryMock.mockReset(); listFollowEventsMock.mockReset(); listMutualsMock.mockReset(); listNonMutualFollowingMock.mockReset(); listTopFollowersMock.mockReset(); listUnfollowedSinceMock.mockReset(); listTimelineItemsMock.mockReset(); listDmConversationsMock.mockReset(); applyDmRequestMutationToLocalStoreMock.mockReset(); runDirectMessageRequestMutationViaBirdMock.mockReset(); getAuthenticatedBirdAccountMock.mockReset(); getConversationThreadMock.mockReset(); hydrateProfilesFromXMock.mockReset(); inspectProfileRepliesMock.mockReset(); runResearchModeMock.mockReset(); streamPeriodDigestMock.mockReset(); streamSearchDiscussionMock.mockReset(); syncAuthoredTweetsMock.mockReset(); syncTimelineCollectionMock.mockReset(); createPostMock.mockReset(); createTweetReplyMock.mockReset(); createDmReplyMock.mockReset(); removeBlockMock.mockReset(); removeMuteMock.mockReset(); maybeAutoUpdateBackupMock.mockReset(); maybeAutoSyncBackupMock.mockReset(); exportBackupMock.mockReset(); importBackupMock.mockReset(); syncBackupMock.mockReset(); validateBackupMock.mockReset(); runProductionServerMock.mockReset(); execFileAsyncMock.mockReset(); ensureBirdclawDirsMock.mockReturnValue({ rootDir: "/tmp/.birdclaw", configPath: "/tmp/.birdclaw/config.json", dbPath: "/tmp/.birdclaw/birdclaw.sqlite", mediaOriginalsDir: "/tmp/.birdclaw/media/originals", mediaThumbsDir: "/tmp/.birdclaw/media/thumbs", }); getBirdclawPathsMock.mockReturnValue({ rootDir: "/tmp/.birdclaw", dbPath: "/tmp/.birdclaw/birdclaw.sqlite", }); getDefaultAccountSelectorMock.mockReturnValue(undefined); resolveOperationAccountMock.mockImplementation((selector?: string) => ({ id: selector === "steipete" || selector === "@steipete" ? "acct_primary" : (selector ?? "acct_primary"), username: selector?.replace(/^@/, "") ?? "steipete", externalUserId: "25401953", })); getConversationThreadMock.mockReturnValue({ conversation: { accountId: "acct_primary" }, }); getAuthenticatedBirdAccountMock.mockResolvedValue({ id: "25401953", username: "steipete", }); resolveMentionsDataSourceMock.mockImplementation( (mode?: string) => mode ?? "birdclaw", ); setActionsTransportMock.mockImplementation((transport: string) => ({ configPath: "/tmp/.birdclaw/config.json", transport, })); getQueryEnvelopeMock.mockResolvedValue({ stats: { home: 4, mentions: 2, dms: 4, needsReply: 2, inbox: 4 }, transport: { statusText: "local", installed: false }, accounts: [], archives: [], }); getNativeDbMock.mockReturnValue({}); seedDemoDataMock.mockReturnValue({ seeded: true, counts: { accounts: 2, profiles: 6, tweets: 6, conversations: 4, messages: 8, }, }); findArchivesMock.mockResolvedValue([{ name: "twitter.zip" }]); importArchiveMock.mockResolvedValue({ ok: true, archivePath: "/tmp/twitter.zip", }); importTweetsViaFxTwitterMock.mockResolvedValue({ ok: true, readOnlyTransport: true, source: "fxtwitter", endpoint: "https://api.fxtwitter.com", requestedCount: 1, importedCount: 1, items: [ { tweetId: "20", source: "fxtwitter", sourceUrl: "https://api.fxtwitter.com/2/status/20", }, ], }); for (const mock of [ importThreadViaFxTwitterMock,Lines 1–500 of 3301. Load more
// @vitest-environment node import { readFileSync } from "node:fs"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const ensureBirdclawDirsMock = vi.fn(); const getBirdclawPathsMock = vi.fn(); const getDefaultAccountSelectorMock = vi.fn(); const resolveOperationAccountMock = vi.fn(); const resolveMentionsDataSourceMock = vi.fn(); const setActionsTransportMock = vi.fn(); const getQueryEnvelopeMock = vi.fn(); const getNativeDbMock = vi.fn(); const seedDemoDataMock = vi.fn(); const findArchivesMock = vi.fn(); const importArchiveMock = vi.fn(); const importTweetsViaFxTwitterMock = vi.fn(); const importThreadViaFxTwitterMock = vi.fn(); const importConversationViaFxTwitterMock = vi.fn(); const importProfileViaFxTwitterMock = vi.fn(); const importSearchViaFxTwitterMock = vi.fn(); const importBlocklistMock = vi.fn(); const addBlockMock = vi.fn(); const recordBlockMock = vi.fn(); const syncBlocksMock = vi.fn(); const exportMentionItemsMock = vi.fn(); const exportMentionsViaCachedAutoMock = vi.fn(); const exportMentionsViaCachedBirdMock = vi.fn(); const exportMentionsViaCachedXurlMock = vi.fn(); const syncMentionsMock = vi.fn(); const syncMentionThreadsMock = vi.fn(); const syncDirectMessagesViaCachedBirdMock = vi.fn(); const resolveProfilesForIdsMock = vi.fn(); const expandUrlsFromTextsMock = vi.fn(); const searchLinksMock = vi.fn(); const backfillLinkIndexMock = vi.fn(); const fetchTweetMediaMock = vi.fn(); const formatMediaFetchResultMock = vi.fn(); const runWhoisMock = vi.fn(); const formatWhoisMock = vi.fn(); const listBlocksMock = vi.fn(); const addMuteMock = vi.fn(); const listMutesMock = vi.fn(); const recordMuteMock = vi.fn(); const listInboxItemsMock = vi.fn(); const scoreInboxMock = vi.fn(); const syncFollowGraphMock = vi.fn(); const getFollowGraphSummaryMock = vi.fn(); const listFollowEventsMock = vi.fn(); const listMutualsMock = vi.fn(); const listNonMutualFollowingMock = vi.fn(); const listTopFollowersMock = vi.fn(); const listUnfollowedSinceMock = vi.fn(); const listTimelineItemsMock = vi.fn(); const listDmConversationsMock = vi.fn(); const applyDmRequestMutationToLocalStoreMock = vi.fn(); const runDirectMessageRequestMutationViaBirdMock = vi.fn(); const getAuthenticatedBirdAccountMock = vi.fn(); const getConversationThreadMock = vi.fn(); const hydrateProfilesFromXMock = vi.fn(); const inspectProfileRepliesMock = vi.fn(); const runResearchModeMock = vi.fn(); const streamPeriodDigestMock = vi.fn(); const streamSearchDiscussionMock = vi.fn(); const syncAuthoredTweetsMock = vi.fn(); const syncTimelineCollectionMock = vi.fn(); const createPostMock = vi.fn(); const createTweetReplyMock = vi.fn(); const createDmReplyMock = vi.fn(); const removeBlockMock = vi.fn(); const removeMuteMock = vi.fn(); const maybeAutoUpdateBackupMock = vi.fn(); const maybeAutoSyncBackupMock = vi.fn(); const exportBackupMock = vi.fn(); const importBackupMock = vi.fn(); const syncBackupMock = vi.fn(); const validateBackupMock = vi.fn(); const runProductionServerMock = vi.fn(); const packageVersion = ( JSON.parse( readFileSync(new URL("../package.json", import.meta.url), "utf8"), ) as { version: string } ).version; const execFileAsyncMock = vi.fn(); const execFileMock = vi.fn(); const consoleLogMock = vi.spyOn(console, "log").mockImplementation(() => {}); class FxTwitterErrorMock extends Error { readonly kind: string; readonly status?: number; readonly retryAfterMs?: number; constructor(options: { kind: string; message: string; status?: number; retryAfterMs?: number; }) { super(options.message); this.kind = options.kind; this.status = options.status; this.retryAfterMs = options.retryAfterMs; } } Object.defineProperty( execFileMock, Symbol.for("nodejs.util.promisify.custom"), { value: execFileAsyncMock, }, ); vi.mock("#/lib/config", () => ({ ensureBirdclawDirs: () => ensureBirdclawDirsMock(), getDefaultAccountSelector: () => getDefaultAccountSelectorMock(), getBirdclawConfig: () => ({}), getBirdclawPaths: () => getBirdclawPathsMock(), resolveMentionsDataSource: (...args: unknown[]) => resolveMentionsDataSourceMock(...args), setActionsTransport: (...args: unknown[]) => setActionsTransportMock(...args), })); vi.mock("#/lib/db", () => ({ closeDatabase: vi.fn(), getNativeDb: (...args: unknown[]) => getNativeDbMock(...args), })); vi.mock("#/lib/seed", () => ({ seedDemoData: (...args: unknown[]) => seedDemoDataMock(...args), })); vi.mock("#/lib/account-selection", () => ({ resolveOperationAccount: (...args: unknown[]) => resolveOperationAccountMock(...args), })); vi.mock("#/lib/dm-read-model", () => ({ getConversationThread: (...args: unknown[]) => getConversationThreadMock(...args), })); vi.mock("#/lib/archive-finder", () => ({ findArchives: () => findArchivesMock(), })); vi.mock("#/lib/archive-import", () => ({ ARCHIVE_IMPORT_SLICES: [ "tweets", "likes", "bookmarks", "directMessages", "profiles", "followers", "following", ], importArchive: (...args: unknown[]) => importArchiveMock(...args), })); vi.mock("#/lib/fxtwitter", () => ({ FxTwitterError: FxTwitterErrorMock, importConversationViaFxTwitter: (...args: unknown[]) => importConversationViaFxTwitterMock(...args), importProfileViaFxTwitter: (...args: unknown[]) => importProfileViaFxTwitterMock(...args), importSearchViaFxTwitter: (...args: unknown[]) => importSearchViaFxTwitterMock(...args), importThreadViaFxTwitter: (...args: unknown[]) => importThreadViaFxTwitterMock(...args), importTweetsViaFxTwitter: (...args: unknown[]) => importTweetsViaFxTwitterMock(...args), })); vi.mock("#/lib/backup", async (importOriginal) => { const actual = await importOriginal<typeof import("#/lib/backup")>(); return { ...actual, exportBackup: (...args: unknown[]) => exportBackupMock(...args), importBackup: (...args: unknown[]) => importBackupMock(...args), maybeAutoUpdateBackup: () => maybeAutoUpdateBackupMock(), maybeAutoSyncBackup: () => maybeAutoSyncBackupMock(), syncBackup: (...args: unknown[]) => syncBackupMock(...args), validateBackup: (...args: unknown[]) => validateBackupMock(...args), }; }); vi.mock("#/lib/blocklist", () => ({ importBlocklist: (...args: unknown[]) => importBlocklistMock(...args), })); vi.mock("#/lib/blocks", () => ({ addBlock: (...args: unknown[]) => addBlockMock(...args), listBlocks: (...args: unknown[]) => listBlocksMock(...args), recordBlock: (...args: unknown[]) => recordBlockMock(...args), removeBlock: (...args: unknown[]) => removeBlockMock(...args), syncBlocks: (...args: unknown[]) => syncBlocksMock(...args), })); vi.mock("#/lib/inbox", () => ({ listInboxItems: (...args: unknown[]) => listInboxItemsMock(...args), scoreInbox: (...args: unknown[]) => scoreInboxMock(...args), })); vi.mock("#/lib/link-index", () => ({ backfillLinkIndex: (...args: unknown[]) => backfillLinkIndexMock(...args), searchLinks: (...args: unknown[]) => searchLinksMock(...args), })); vi.mock("#/lib/media-fetch", () => ({ fetchTweetMedia: (...args: unknown[]) => fetchTweetMediaMock(...args), formatMediaFetchResult: (...args: unknown[]) => formatMediaFetchResultMock(...args), })); vi.mock("#/lib/follow-graph", () => ({ getFollowGraphSummary: (...args: unknown[]) => getFollowGraphSummaryMock(...args), listFollowEvents: (...args: unknown[]) => listFollowEventsMock(...args), listMutuals: (...args: unknown[]) => listMutualsMock(...args), listNonMutualFollowing: (...args: unknown[]) => listNonMutualFollowingMock(...args), listTopFollowers: (...args: unknown[]) => listTopFollowersMock(...args), listUnfollowedSince: (...args: unknown[]) => listUnfollowedSinceMock(...args), syncFollowGraph: (...args: unknown[]) => syncFollowGraphMock(...args), })); vi.mock("#/lib/mutes", () => ({ addMute: (...args: unknown[]) => addMuteMock(...args), listMutes: (...args: unknown[]) => listMutesMock(...args), recordMute: (...args: unknown[]) => recordMuteMock(...args), removeMute: (...args: unknown[]) => removeMuteMock(...args), })); vi.mock("#/lib/mentions-export", () => ({ exportMentionItems: (...args: unknown[]) => exportMentionItemsMock(...args), })); vi.mock("#/lib/mentions-live", () => ({ exportMentionsViaCachedAuto: (...args: unknown[]) => exportMentionsViaCachedAutoMock(...args), exportMentionsViaCachedBird: (...args: unknown[]) => exportMentionsViaCachedBirdMock(...args), exportMentionsViaCachedXurl: (...args: unknown[]) => exportMentionsViaCachedXurlMock(...args), syncMentions: (...args: unknown[]) => syncMentionsMock(...args), })); vi.mock("#/lib/mention-threads-live", () => ({ syncMentionThreads: (...args: unknown[]) => syncMentionThreadsMock(...args), })); vi.mock("#/lib/dms-live", () => ({ syncDirectMessagesViaCachedBird: (...args: unknown[]) => syncDirectMessagesViaCachedBirdMock(...args), })); vi.mock("#/lib/profile-hydration", () => ({ hydrateProfilesFromX: (...args: unknown[]) => hydrateProfilesFromXMock(...args), })); vi.mock("#/lib/profile-resolver", () => ({ resolveProfilesForIds: (...args: unknown[]) => resolveProfilesForIdsMock(...args), })); vi.mock("#/lib/profile-replies", () => ({ inspectProfileReplies: (...args: unknown[]) => inspectProfileRepliesMock(...args), })); vi.mock("#/lib/research", () => ({ runResearchMode: (...args: unknown[]) => runResearchModeMock(...args), })); vi.mock("#/lib/period-digest", () => ({ normalizeDigestLanguage: (value: string | undefined) => { const trimmed = value?.trim(); if (!trimmed) return undefined; if (trimmed === "ZH-cn") return "zh-CN"; throw new Error( "Digest language must be a valid Unicode locale identifier", ); }, streamPeriodDigest: (...args: unknown[]) => streamPeriodDigestMock(...args), })); vi.mock("#/lib/search-discussion", () => ({ streamSearchDiscussion: (...args: unknown[]) => streamSearchDiscussionMock(...args), })); vi.mock("#/lib/authored-live", () => ({ AuthoredSyncError: class AuthoredSyncError extends Error { constructor( message: string, public readonly exitCode: number, ) { super(message); } }, syncAuthoredTweets: (...args: unknown[]) => syncAuthoredTweetsMock(...args), })); vi.mock("#/lib/queries", () => ({ applyDmRequestMutationToLocalStore: (...args: unknown[]) => applyDmRequestMutationToLocalStoreMock(...args), getQueryEnvelope: (...args: unknown[]) => getQueryEnvelopeMock(...args), listTimelineItems: (...args: unknown[]) => listTimelineItemsMock(...args), listDmConversations: (...args: unknown[]) => listDmConversationsMock(...args), createPost: (...args: unknown[]) => createPostMock(...args), createTweetReply: (...args: unknown[]) => createTweetReplyMock(...args), createDmReply: (...args: unknown[]) => createDmReplyMock(...args), })); vi.mock("#/lib/production-server", () => ({ runProductionServer: (...args: unknown[]) => runProductionServerMock(...args), })); vi.mock("#/lib/bird", () => ({ getAuthenticatedBirdAccount: (...args: unknown[]) => getAuthenticatedBirdAccountMock(...args), runDirectMessageRequestMutationViaBird: (...args: unknown[]) => runDirectMessageRequestMutationViaBirdMock(...args), })); vi.mock("#/lib/timeline-collections-live", () => ({ syncTimelineCollection: (...args: unknown[]) => syncTimelineCollectionMock(...args), })); vi.mock("#/lib/url-expansion", () => ({ expandUrlsFromTexts: (...args: unknown[]) => expandUrlsFromTextsMock(...args), })); vi.mock("#/lib/whois", () => ({ formatWhois: (...args: unknown[]) => formatWhoisMock(...args), runWhois: (...args: unknown[]) => runWhoisMock(...args), })); vi.mock("node:child_process", () => ({ execFile: execFileMock, })); async function loadCli() { vi.resetModules(); return import("./cli"); } describe("cli", () => { beforeEach(() => { process.exitCode = undefined; consoleLogMock.mockClear(); ensureBirdclawDirsMock.mockReset(); getBirdclawPathsMock.mockReset(); getDefaultAccountSelectorMock.mockReset(); resolveOperationAccountMock.mockReset(); resolveMentionsDataSourceMock.mockReset(); setActionsTransportMock.mockReset(); getQueryEnvelopeMock.mockReset(); getNativeDbMock.mockReset(); seedDemoDataMock.mockReset(); findArchivesMock.mockReset(); importArchiveMock.mockReset(); importTweetsViaFxTwitterMock.mockReset(); importThreadViaFxTwitterMock.mockReset(); importConversationViaFxTwitterMock.mockReset(); importProfileViaFxTwitterMock.mockReset(); importSearchViaFxTwitterMock.mockReset(); importBlocklistMock.mockReset(); addBlockMock.mockReset(); recordBlockMock.mockReset(); syncBlocksMock.mockReset(); exportMentionItemsMock.mockReset(); exportMentionsViaCachedAutoMock.mockReset(); exportMentionsViaCachedBirdMock.mockReset(); exportMentionsViaCachedXurlMock.mockReset(); syncMentionsMock.mockReset(); syncMentionThreadsMock.mockReset(); syncDirectMessagesViaCachedBirdMock.mockReset(); resolveProfilesForIdsMock.mockReset(); expandUrlsFromTextsMock.mockReset(); searchLinksMock.mockReset(); backfillLinkIndexMock.mockReset(); fetchTweetMediaMock.mockReset(); formatMediaFetchResultMock.mockReset(); runWhoisMock.mockReset(); formatWhoisMock.mockReset(); listBlocksMock.mockReset(); addMuteMock.mockReset(); listMutesMock.mockReset(); recordMuteMock.mockReset(); listInboxItemsMock.mockReset(); scoreInboxMock.mockReset(); syncFollowGraphMock.mockReset(); getFollowGraphSummaryMock.mockReset(); listFollowEventsMock.mockReset(); listMutualsMock.mockReset(); listNonMutualFollowingMock.mockReset(); listTopFollowersMock.mockReset(); listUnfollowedSinceMock.mockReset(); listTimelineItemsMock.mockReset(); listDmConversationsMock.mockReset(); applyDmRequestMutationToLocalStoreMock.mockReset(); runDirectMessageRequestMutationViaBirdMock.mockReset(); getAuthenticatedBirdAccountMock.mockReset(); getConversationThreadMock.mockReset(); hydrateProfilesFromXMock.mockReset(); inspectProfileRepliesMock.mockReset(); runResearchModeMock.mockReset(); streamPeriodDigestMock.mockReset(); streamSearchDiscussionMock.mockReset(); syncAuthoredTweetsMock.mockReset(); syncTimelineCollectionMock.mockReset(); createPostMock.mockReset(); createTweetReplyMock.mockReset(); createDmReplyMock.mockReset(); removeBlockMock.mockReset(); removeMuteMock.mockReset(); maybeAutoUpdateBackupMock.mockReset(); maybeAutoSyncBackupMock.mockReset(); exportBackupMock.mockReset(); importBackupMock.mockReset(); syncBackupMock.mockReset(); validateBackupMock.mockReset(); runProductionServerMock.mockReset(); execFileAsyncMock.mockReset(); ensureBirdclawDirsMock.mockReturnValue({ rootDir: "/tmp/.birdclaw", configPath: "/tmp/.birdclaw/config.json", dbPath: "/tmp/.birdclaw/birdclaw.sqlite", mediaOriginalsDir: "/tmp/.birdclaw/media/originals", mediaThumbsDir: "/tmp/.birdclaw/media/thumbs", }); getBirdclawPathsMock.mockReturnValue({ rootDir: "/tmp/.birdclaw", dbPath: "/tmp/.birdclaw/birdclaw.sqlite", }); getDefaultAccountSelectorMock.mockReturnValue(undefined); resolveOperationAccountMock.mockImplementation((selector?: string) => ({ id: selector === "steipete" || selector === "@steipete" ? "acct_primary" : (selector ?? "acct_primary"), username: selector?.replace(/^@/, "") ?? "steipete", externalUserId: "25401953", })); getConversationThreadMock.mockReturnValue({ conversation: { accountId: "acct_primary" }, }); getAuthenticatedBirdAccountMock.mockResolvedValue({ id: "25401953", username: "steipete", }); resolveMentionsDataSourceMock.mockImplementation( (mode?: string) => mode ?? "birdclaw", ); setActionsTransportMock.mockImplementation((transport: string) => ({ configPath: "/tmp/.birdclaw/config.json", transport, })); getQueryEnvelopeMock.mockResolvedValue({ stats: { home: 4, mentions: 2, dms: 4, needsReply: 2, inbox: 4 }, transport: { statusText: "local", installed: false }, accounts: [], archives: [], }); getNativeDbMock.mockReturnValue({}); seedDemoDataMock.mockReturnValue({ seeded: true, counts: { accounts: 2, profiles: 6, tweets: 6, conversations: 4, messages: 8, }, }); findArchivesMock.mockResolvedValue([{ name: "twitter.zip" }]); importArchiveMock.mockResolvedValue({ ok: true, archivePath: "/tmp/twitter.zip", }); importTweetsViaFxTwitterMock.mockResolvedValue({ ok: true, readOnlyTransport: true, source: "fxtwitter", endpoint: "https://api.fxtwitter.com", requestedCount: 1, importedCount: 1, items: [ { tweetId: "20", source: "fxtwitter", sourceUrl: "https://api.fxtwitter.com/2/status/20", }, ], }); for (const mock of [ importThreadViaFxTwitterMock,