@threadbase-sh/scanner 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/dist/cli.js +1447 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1340 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +1279 -0
- package/dist/index.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/filters.ts","../src/git.ts","../src/logger.ts","../src/indexer.ts","../src/profiles.ts","../src/scanner.ts","../src/cache.ts","../src/discovery.ts","../src/parser.ts","../src/tags.ts","../src/tiers.ts","../src/types.ts"],"sourcesContent":["export {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n} from \"./filters\";\nexport { readGitBranch } from \"./git\";\nexport { SearchIndexer } from \"./indexer\";\nexport type { Logger, LoggerOptions } from \"./logger\";\nexport { createLogger, getLogger, setLogger } from \"./logger\";\nexport {\n detectDefaultProfile,\n getProjectsDir,\n loadProfiles,\n resolveConfigDir,\n saveProfiles,\n} from \"./profiles\";\nexport { ConversationScanner } from \"./scanner\";\nexport { cleanSystemTags } from \"./tags\";\nexport { DEFAULT_TIERS, resolveTier } from \"./tiers\";\nexport * from \"./types\";\n\n// ─── Standalone Convenience Functions ───────────────────────────────\n\nimport { ConversationScanner } from \"./scanner\";\nimport type {\n Conversation,\n GetConversationOptions,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n} from \"./types\";\n\nlet defaultScanner: ConversationScanner | undefined;\n\nfunction getDefaultScanner(): ConversationScanner {\n if (!defaultScanner) {\n defaultScanner = new ConversationScanner();\n }\n return defaultScanner;\n}\n\nexport function resetDefaultScanner(): void {\n defaultScanner = undefined;\n}\n\nexport async function scan(\n options?: ScanOptions,\n scanner?: ConversationScanner,\n): Promise<ScanResult> {\n return (scanner ?? getDefaultScanner()).scan(options);\n}\n\nexport async function search(\n query: string,\n options?: SearchOptions,\n scanner?: ConversationScanner,\n): Promise<SearchResult[]> {\n return (scanner ?? getDefaultScanner()).search(query, options);\n}\n\nexport async function getConversation(\n id: string,\n options?: GetConversationOptions,\n scanner?: ConversationScanner,\n): Promise<Conversation | null> {\n return (scanner ?? getDefaultScanner()).getConversation(id, options);\n}\n","import type { ConversationMeta, Include, SortOrder } from \"./types\";\n\nexport function applySort(metas: ConversationMeta[], order: SortOrder): ConversationMeta[] {\n const out = [...metas];\n switch (order) {\n case \"recent\":\n out.sort((a, b) => b.timestamp.localeCompare(a.timestamp));\n break;\n case \"oldest\":\n out.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n break;\n case \"messages-desc\":\n out.sort((a, b) => b.messageCount - a.messageCount);\n break;\n case \"messages-asc\":\n out.sort((a, b) => a.messageCount - b.messageCount);\n break;\n case \"alpha\":\n out.sort((a, b) => {\n const cmp = a.projectName.localeCompare(b.projectName);\n return cmp !== 0 ? cmp : a.preview.localeCompare(b.preview);\n });\n break;\n }\n return out;\n}\n\nexport function applySinceFilter(metas: ConversationMeta[], since: string): ConversationMeta[] {\n const cutoff = parseSinceCutoff(since);\n return metas.filter((m) => new Date(m.timestamp).getTime() >= cutoff.getTime());\n}\n\nexport function applyIncludeFilter(\n metas: ConversationMeta[],\n include: Include,\n): ConversationMeta[] {\n switch (include) {\n case \"all\":\n return metas;\n case \"conversations\":\n return metas.filter((m) => !m.isSubagent && !m.isTeammate);\n case \"subagents\":\n return metas.filter((m) => m.isSubagent);\n case \"teammates\":\n return metas.filter((m) => m.isTeammate);\n }\n}\n\nexport function applyProjectFilter(metas: ConversationMeta[], project: string): ConversationMeta[] {\n const lower = project.toLowerCase();\n return metas.filter(\n (m) =>\n m.projectPath.toLowerCase().includes(lower) || m.projectName.toLowerCase().includes(lower),\n );\n}\n\nexport function applyAccountFilter(metas: ConversationMeta[], account: string): ConversationMeta[] {\n return metas.filter((m) => m.account === account);\n}\n\nexport function applyPagination<T>(\n items: T[],\n limit: number,\n offset: number,\n): { items: T[]; total: number } {\n return {\n items: items.slice(offset, offset + limit),\n total: items.length,\n };\n}\n\nexport function parseSinceCutoff(value: string): Date {\n const s = value.trim();\n if (!s) throw new Error(\"Empty --since value\");\n\n // Try ISO date\n const isoMatch = s.match(/^\\d{4}-\\d{2}-\\d{2}$/);\n if (isoMatch) {\n const d = new Date(`${s}T00:00:00Z`);\n if (!Number.isNaN(d.getTime())) return d;\n }\n\n // Try duration: digits + unit\n const durationMatch = s.match(/^(\\d+)([hdw])$/);\n if (!durationMatch) {\n throw new Error(\n `Invalid --since value \"${s}\": expected duration like \"7d\", \"24h\", \"2w\" or ISO date \"2006-01-02\"`,\n );\n }\n\n const n = parseInt(durationMatch[1], 10);\n const unit = durationMatch[2];\n let ms: number;\n switch (unit) {\n case \"h\":\n ms = n * 60 * 60 * 1000;\n break;\n case \"d\":\n ms = n * 24 * 60 * 60 * 1000;\n break;\n case \"w\":\n ms = n * 7 * 24 * 60 * 60 * 1000;\n break;\n default:\n throw new Error(`Invalid unit \"${unit}\"`);\n }\n\n return new Date(Date.now() - ms);\n}\n","import { readFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport { getLogger } from \"./logger\";\n\nconst REF_PREFIX = \"ref: refs/heads/\";\nconst MAX_DEPTH = 6;\n\nexport function readGitBranch(projectPath: string): string | null {\n if (!projectPath) return null;\n const log = getLogger();\n\n let dir = projectPath;\n let depth = 0;\n\n while (depth < MAX_DEPTH) {\n const headPath = join(dir, \".git\", \"HEAD\");\n try {\n const content = readFileSync(headPath, \"utf-8\").trim();\n if (content.startsWith(REF_PREFIX)) {\n const branch = content.slice(REF_PREFIX.length);\n log.trace({ projectPath, dir, branch }, \"git: branch resolved\");\n return branch;\n }\n // Detached HEAD: raw commit SHA\n if (content.length >= 7) {\n log.trace({ projectPath, dir }, \"git: detached HEAD\");\n return \"(detached)\";\n }\n return null;\n } catch {\n // .git/HEAD not found at this level\n }\n\n const parent = dirname(dir);\n if (parent === dir) return null; // filesystem root\n dir = parent;\n depth++;\n }\n\n log.trace({ projectPath }, \"git: no .git found within depth\");\n return null;\n}\n","import pino, { type Logger, type LoggerOptions } from \"pino\";\n\nlet currentLogger: Logger = pino({ level: \"silent\" });\n\nexport function createLogger(options?: LoggerOptions | Logger): Logger {\n if (options && typeof (options as Logger).child === \"function\") {\n return options as Logger;\n }\n return pino((options as LoggerOptions) ?? { level: \"silent\" });\n}\n\nexport function setLogger(logger: Logger): void {\n currentLogger = logger;\n}\n\nexport function getLogger(): Logger {\n return currentLogger;\n}\n\nexport type { Logger, LoggerOptions };\n","import FlexSearchModule from \"flexsearch\";\n\n// FlexSearch has inconsistent default export across ESM/CJS\nconst FlexSearch = (FlexSearchModule as any).default ?? FlexSearchModule;\n\nimport { getLogger } from \"./logger\";\nimport type { ConversationMeta, SearchMatch, SearchResult } from \"./types\";\n\nexport class SearchIndexer {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private index: any;\n private documents = new Map<string, ConversationMeta>();\n\n constructor() {\n this.index = this.createIndex();\n }\n\n private createIndex() {\n return new (FlexSearch as any).Document({\n document: {\n id: \"id\",\n index: [\n \"content\",\n \"projectName\",\n \"projectPath\",\n \"sessionId\",\n \"sessionName\",\n \"account\",\n \"model\",\n \"gitBranch\",\n \"toolNames\",\n ],\n store: [\"id\"],\n },\n tokenize: \"forward\",\n resolution: 9,\n cache: 100,\n });\n }\n\n addDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.add({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n buildIndex(metas: ConversationMeta[]): void {\n this.clear();\n for (const meta of metas) {\n this.addDocument(meta);\n }\n getLogger().debug({ docCount: metas.length }, \"indexer: built\");\n }\n\n search(query: string, options?: { fields?: string[]; limit?: number }): SearchResult[] {\n const limit = options?.limit ?? 50;\n\n if (!query.trim()) {\n return this.getRecent(limit);\n }\n\n const results = this.index.search(query, { limit: limit * 2, enrich: true });\n\n const seen = new Set<string>();\n const searchResults: SearchResult[] = [];\n\n for (const fieldResult of results) {\n if (!fieldResult.result) continue;\n for (const item of fieldResult.result) {\n const id = typeof item === \"object\" ? (item as { id: string }).id : String(item);\n if (seen.has(id)) continue;\n seen.add(id);\n\n const meta = this.documents.get(id);\n if (!meta) continue;\n\n const matches = this.generateMatches(meta, query);\n\n searchResults.push({ meta, score: 1, matches });\n if (searchResults.length >= limit) break;\n }\n if (searchResults.length >= limit) break;\n }\n\n return searchResults;\n }\n\n private getRecent(limit: number): SearchResult[] {\n return Array.from(this.documents.values())\n .sort((a, b) => b.timestamp.localeCompare(a.timestamp))\n .slice(0, limit)\n .map((meta) => ({\n meta,\n score: 1,\n matches: [{ field: \"timestamp\", snippet: meta.preview }],\n }));\n }\n\n private generateMatches(meta: ConversationMeta, query: string): SearchMatch[] {\n const matches: SearchMatch[] = [];\n const lowerQuery = query.toLowerCase();\n\n const fields: [string, string][] = [\n [\"contentSnippet\", meta.contentSnippet],\n [\"projectName\", meta.projectName],\n [\"sessionId\", meta.sessionId],\n [\"sessionName\", meta.sessionName],\n [\"account\", meta.account],\n [\"model\", meta.model || \"\"],\n [\"gitBranch\", meta.gitBranch || \"\"],\n [\"toolNames\", meta.toolNames.join(\" \")],\n ];\n\n for (const [field, value] of fields) {\n const idx = value.toLowerCase().indexOf(lowerQuery);\n if (idx !== -1) {\n const start = Math.max(0, idx - 80);\n const end = Math.min(value.length, idx + query.length + 120);\n let snippet = value.slice(start, end);\n if (start > 0) snippet = `...${snippet}`;\n if (end < value.length) snippet = `${snippet}...`;\n matches.push({ field, snippet });\n }\n }\n\n return matches.length > 0 ? matches : [{ field: \"preview\", snippet: meta.preview }];\n }\n\n getDocumentCount(): number {\n return this.documents.size;\n }\n\n // Replace an already-indexed document in place. FlexSearch's `add` does not\n // overwrite an existing id, so a single-file refresh must go through\n // `update` to avoid stale matches lingering in the index.\n updateDocument(meta: ConversationMeta): void {\n this.documents.set(meta.id, meta);\n this.index.update({\n id: meta.id,\n content: meta.contentSnippet,\n projectName: meta.projectName,\n projectPath: meta.projectPath,\n sessionId: meta.sessionId,\n sessionName: meta.sessionName,\n account: meta.account,\n model: meta.model || \"\",\n gitBranch: meta.gitBranch || \"\",\n toolNames: meta.toolNames.join(\" \"),\n });\n }\n\n removeDocument(id: string): void {\n this.documents.delete(id);\n this.index.remove(id);\n }\n\n clear(): void {\n this.documents.clear();\n this.index = this.createIndex();\n getLogger().trace(\"indexer: cleared\");\n }\n}\n","import { mkdir, readFile, writeFile } from \"fs/promises\";\nimport { homedir } from \"os\";\nimport { join } from \"path\";\nimport { getLogger } from \"./logger\";\nimport type { Profile } from \"./types\";\n\nconst PROFILES_FILE = \"profiles.json\";\n\nexport function resolveConfigDir(configDir: string): string {\n return configDir.replace(/^~/, homedir());\n}\n\nexport function getProjectsDir(profile: Profile): string {\n return join(resolveConfigDir(profile.configDir), \"projects\");\n}\n\nexport async function detectDefaultProfile(): Promise<Profile> {\n return {\n id: \"default\",\n label: \"Default\",\n configDir: join(homedir(), \".claude\"),\n enabled: true,\n emoji: \"🤖\",\n };\n}\n\nexport async function loadProfiles(configPath: string): Promise<Profile[]> {\n const log = getLogger();\n try {\n const resolved = resolveConfigDir(configPath);\n const data = await readFile(join(resolved, PROFILES_FILE), \"utf-8\");\n const profiles = JSON.parse(data) as Profile[];\n log.debug({ configPath, count: profiles.length }, \"profiles: loaded\");\n return profiles;\n } catch (err) {\n log.debug({ configPath, err }, \"profiles: load failed, using default\");\n const defaultProfile = await detectDefaultProfile();\n return [defaultProfile];\n }\n}\n\nexport async function saveProfiles(profiles: Profile[], configPath: string): Promise<void> {\n const resolved = resolveConfigDir(configPath);\n await mkdir(resolved, { recursive: true });\n await writeFile(join(resolved, PROFILES_FILE), JSON.stringify(profiles, null, 2));\n getLogger().debug({ configPath, count: profiles.length }, \"profiles: saved\");\n}\n","import { statSync } from \"fs\";\nimport { LRUCache } from \"./cache\";\nimport { discoverJsonlFiles } from \"./discovery\";\nimport {\n applyAccountFilter,\n applyIncludeFilter,\n applyPagination,\n applyProjectFilter,\n applySinceFilter,\n applySort,\n parseSinceCutoff,\n} from \"./filters\";\nimport { readGitBranch } from \"./git\";\nimport { SearchIndexer } from \"./indexer\";\nimport { getLogger } from \"./logger\";\nimport { parseConversation, parseMeta } from \"./parser\";\nimport { getProjectsDir, loadProfiles } from \"./profiles\";\nimport { resolveTier } from \"./tiers\";\nimport type {\n ContentTier,\n Conversation,\n ConversationMeta,\n ConversationPage,\n GetConversationOptions,\n GetConversationPageOptions,\n GroupedConversations,\n Profile,\n ScanOptions,\n ScanResult,\n SearchOptions,\n SearchResult,\n SingleFilePage,\n TreeConversation,\n} from \"./types\";\n\nconst BATCH_SIZE = 12;\nconst DEFAULT_CONFIG_PATH = \"~/.config/threadbase-scanner\";\n\nexport class ConversationScanner {\n private metadataCache: Map<string, ConversationMeta> = new Map();\n private conversationLRU: LRUCache<string, Conversation>;\n private sessionIdIndex: Map<string, ConversationMeta> = new Map();\n private projects: Set<string> = new Set();\n private indexer: SearchIndexer = new SearchIndexer();\n // Tier the most recent scan() ran with, so refreshFile() re-parses a single\n // file at the same content depth. Defaults to the standard tier.\n private lastTier: ContentTier = resolveTier(\"standard\");\n\n constructor(options?: { metadataCacheSize?: number; conversationCacheSize?: number }) {\n this.conversationLRU = new LRUCache<string, Conversation>(options?.conversationCacheSize ?? 5);\n }\n\n async scan(options: ScanOptions = {}): Promise<ScanResult> {\n const log = getLogger();\n const startedAt = Date.now();\n const profiles = await this.resolveProfiles(options.profiles);\n const activeProfiles = profiles.filter((p) => p.enabled && p.scanHistory !== false);\n\n const tier = resolveTier(options.tier ?? \"standard\", options.tiers);\n this.lastTier = tier;\n\n log.info(\n {\n activeProfiles: activeProfiles.length,\n tier: tier.name,\n sort: options.sort ?? \"recent\",\n include: options.include ?? \"all\",\n view: options.view ?? \"flat\",\n },\n \"scan: start\",\n );\n\n // Clear caches\n this.metadataCache.clear();\n this.conversationLRU.clear();\n this.sessionIdIndex.clear();\n this.projects.clear();\n this.indexer.clear();\n\n const configDirs = activeProfiles.map((p) => ({\n projectsDir: getProjectsDir(p),\n account: p.id,\n }));\n\n const files = await discoverJsonlFiles(configDirs);\n const totalFiles = files.length;\n let scanned = 0;\n let parseFailures = 0;\n\n const allMetas: ConversationMeta[] = [];\n // Memoize git-branch lookups per project path for the duration of this\n // scan. readGitBranch walks the filesystem for .git/HEAD; without this it\n // runs once per conversation (thousands of times) even though there are\n // only a handful of distinct project roots. Scoped to one scan() call —\n // branches can change between scans, so a longer-lived memo would go stale.\n const gitBranchMemo = new Map<string, string | null>();\n const resolveGitBranch = (projectPath: string): string | null => {\n let branch = gitBranchMemo.get(projectPath);\n if (branch === undefined) {\n branch = readGitBranch(projectPath);\n gitBranchMemo.set(projectPath, branch);\n }\n return branch;\n };\n const { statCache } = options;\n for (let i = 0; i < files.length; i += BATCH_SIZE) {\n const batch = files.slice(i, i + BATCH_SIZE);\n const results = await Promise.all(\n batch.map(async ({ filePath, account }) => {\n if (statCache) {\n const cached = statCache.get(filePath);\n if (cached) {\n try {\n const s = statSync(filePath);\n if (s.mtimeMs === cached.stat.mtimeMs && s.size === cached.stat.size) {\n return cached.meta;\n }\n } catch {\n // file disappeared — fall through to parseMeta which will return null\n }\n }\n }\n try {\n const meta = await parseMeta(filePath, account, tier);\n if (meta) {\n meta.gitBranch = resolveGitBranch(meta.projectPath);\n }\n return meta;\n } catch (err) {\n parseFailures++;\n log.warn({ filePath, account, err }, \"scan: parseMeta threw\");\n return null;\n }\n }),\n );\n\n const batchMetas: ConversationMeta[] = [];\n for (const meta of results) {\n if (meta && meta.messageCount > 0) {\n this.metadataCache.set(meta.id, meta);\n this.sessionIdIndex.set(meta.sessionId, meta);\n this.projects.add(meta.projectPath);\n allMetas.push(meta);\n batchMetas.push(meta);\n this.indexer.addDocument(meta);\n }\n }\n\n if (batchMetas.length > 0) {\n options.onBatch?.(batchMetas);\n }\n\n scanned += batch.length;\n log.debug({ scanned, totalFiles, batchKept: batchMetas.length }, \"scan: batch complete\");\n options.onProgress?.(scanned, totalFiles);\n }\n\n // Apply filters\n let filtered = allMetas;\n if (options.include && options.include !== \"all\") {\n filtered = applyIncludeFilter(filtered, options.include);\n }\n if (options.project) {\n filtered = applyProjectFilter(filtered, options.project);\n }\n if (options.account) {\n filtered = applyAccountFilter(filtered, options.account);\n }\n if (options.since) {\n filtered = applySinceFilter(filtered, options.since);\n }\n\n filtered = applySort(filtered, options.sort ?? \"recent\");\n\n const total = filtered.length;\n const conversations = this.transformView(filtered, options);\n\n const elapsedMs = Date.now() - startedAt;\n log.info(\n {\n totalFiles,\n scanned,\n kept: allMetas.length,\n filteredTotal: total,\n parseFailures,\n elapsedMs,\n },\n \"scan: complete\",\n );\n\n if (Array.isArray(conversations)) {\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const paginated = applyPagination(conversations, limit, offset);\n return { conversations: paginated.items, total, scanned };\n }\n\n return { conversations, total, scanned };\n }\n\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const log = getLogger();\n log.debug({ query, indexSize: this.indexer.getDocumentCount() }, \"search: start\");\n\n if (this.indexer.getDocumentCount() === 0) {\n log.debug(\"search: index empty, triggering scan\");\n await this.scan({ ...options, limit: undefined, offset: undefined });\n }\n\n let results = this.indexer.search(query, {\n fields: options.fields,\n limit: (options.limit ?? 50) * 2,\n });\n\n if (options.include && options.include !== \"all\") {\n results = results.filter((r) => {\n switch (options.include) {\n case \"conversations\":\n return !r.meta.isSubagent && !r.meta.isTeammate;\n case \"subagents\":\n return r.meta.isSubagent;\n case \"teammates\":\n return r.meta.isTeammate;\n default:\n return true;\n }\n });\n }\n if (options.project) {\n const lower = options.project.toLowerCase();\n results = results.filter(\n (r) =>\n r.meta.projectPath.toLowerCase().includes(lower) ||\n r.meta.projectName.toLowerCase().includes(lower),\n );\n }\n if (options.account) {\n results = results.filter((r) => r.meta.account === options.account);\n }\n if (options.since) {\n const cutoff = parseSinceCutoff(options.since);\n results = results.filter((r) => new Date(r.meta.timestamp).getTime() >= cutoff.getTime());\n }\n\n const limit = options.limit ?? 50;\n const offset = options.offset ?? 0;\n const sliced = results.slice(offset, offset + limit);\n log.debug({ query, matched: results.length, returned: sliced.length }, \"search: complete\");\n return sliced;\n }\n\n async getConversation(\n id: string,\n _options?: GetConversationOptions,\n ): Promise<Conversation | null> {\n const log = getLogger();\n const cached = this.conversationLRU.get(id);\n if (cached) {\n log.debug({ id }, \"getConversation: cache hit\");\n return cached;\n }\n\n const meta = this.metadataCache.get(id) ?? this.sessionIdIndex.get(id);\n if (!meta) {\n log.debug({ id }, \"getConversation: not found in metadata\");\n return null;\n }\n\n log.debug({ id, filePath: meta.filePath }, \"getConversation: cache miss, parsing\");\n try {\n const conversation = await parseConversation(meta.filePath, meta.account);\n if (conversation) {\n this.conversationLRU.set(id, conversation);\n }\n return conversation;\n } catch (err) {\n log.warn({ id, filePath: meta.filePath, err }, \"getConversation: parse failed\");\n return null;\n }\n }\n\n // Return one bounded window of a conversation's messages plus the total\n // message count, so a caller can serve the last page and scroll back without\n // holding the whole conversation itself.\n //\n // The window is `[max(0, beforeIndex - limit), beforeIndex)` in chronological\n // order; `beforeIndex` defaults to `total` (the newest page). `fromIndex` is\n // the window's start index, so the caller can derive `has_more_older`\n // (fromIndex > 0). Returns null when the id can't be resolved/parsed — the\n // same contract as getConversation.\n //\n // Strategy: parse-once-then-slice. This delegates to getConversation, which\n // parses the full conversation and caches it in conversationLRU, then slices\n // the window. Message indices are therefore identical to a full\n // parseConversation() by construction (same parse, same messages array).\n // Repeated page requests for the same id reuse the single cached parse. The\n // bounded-memory win (not holding all messages) is deferred — see\n // docs/plans/2026-06-10-paged-conversation-parse.md.\n async getConversationPage(\n id: string,\n options: GetConversationPageOptions,\n ): Promise<ConversationPage | null> {\n const conversation = await this.getConversation(id);\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex };\n }\n\n // Parse one JSONL file directly and slice a page window — without any prior\n // scan() or metadata index. This is the cold-start fast path: a single\n // conversation can be served from one file parse (~ms) instead of waiting\n // for a full filesystem scan. The window is the same\n // `[max(0, beforeIndex - limit), beforeIndex)` slice as getConversationPage,\n // and the parsed Conversation is returned alongside so the caller can build\n // the response meta (projectPath, timestamp, messageCount, …) without a\n // second parse. Returns null when the file can't be parsed. `account` only\n // feeds the conversation's account field; \"default\" is the single-profile\n // fallback, matching refreshFile.\n async parseSingleFilePage(\n filePath: string,\n account: string | undefined,\n options: GetConversationPageOptions,\n ): Promise<SingleFilePage | null> {\n const conversation = await parseConversation(filePath, account ?? \"default\");\n if (!conversation) return null;\n\n const { messages } = conversation;\n const total = messages.length;\n const { limit } = options;\n const beforeIndex = options.beforeIndex ?? total;\n\n const fromIndex = Math.max(0, beforeIndex - limit);\n const window = messages.slice(fromIndex, beforeIndex);\n\n return { messages: window, total, fromIndex, conversation };\n }\n\n // Re-parse a single JSONL file and update every in-memory index in place —\n // metadata cache, sessionId index, project set, search index — and evict the\n // file's parsed conversation from the LRU so the next getConversation()\n // re-reads it. This lets a long-lived scanner stay current with a file that\n // grew after the initial scan() without paying for a full rescan.\n //\n // `account` defaults to the account already recorded for this file (the id\n // is the file path), falling back to \"default\" for a file the scanner has\n // not seen before. Returns the fresh ConversationMeta, or null when the file\n // no longer parses (missing/empty) — in which case any prior entry for it is\n // dropped from all indexes.\n async refreshFile(filePath: string, account?: string): Promise<ConversationMeta | null> {\n const log = getLogger();\n const previous = this.metadataCache.get(filePath);\n const resolvedAccount = account ?? previous?.account ?? \"default\";\n\n let meta: ConversationMeta | null = null;\n try {\n meta = await parseMeta(filePath, resolvedAccount, this.lastTier);\n } catch (err) {\n log.warn({ filePath, err }, \"refreshFile: parseMeta threw\");\n meta = null;\n }\n\n // The LRU is keyed by the id used at getConversation() time, which can be\n // either the file-path id or the sessionId — evict both for the prior and\n // refreshed metas so no stale parse survives.\n const evict = (m: ConversationMeta | undefined | null) => {\n if (!m) return;\n this.conversationLRU.delete(m.id);\n this.conversationLRU.delete(m.sessionId);\n };\n evict(previous);\n evict(meta);\n\n if (!meta || meta.messageCount === 0) {\n if (previous) {\n this.metadataCache.delete(previous.id);\n this.sessionIdIndex.delete(previous.sessionId);\n this.indexer.removeDocument(previous.id);\n }\n log.debug({ filePath }, \"refreshFile: dropped (no parseable messages)\");\n return null;\n }\n\n meta.gitBranch = readGitBranch(meta.projectPath);\n\n // A re-parse can change the sessionId mapping; clear the old one first.\n if (previous && previous.sessionId !== meta.sessionId) {\n this.sessionIdIndex.delete(previous.sessionId);\n }\n this.metadataCache.set(meta.id, meta);\n this.sessionIdIndex.set(meta.sessionId, meta);\n this.projects.add(meta.projectPath);\n if (previous) {\n this.indexer.updateDocument(meta);\n } else {\n this.indexer.addDocument(meta);\n }\n\n log.debug(\n { filePath, messageCount: meta.messageCount },\n \"refreshFile: updated in-memory indexes\",\n );\n return meta;\n }\n\n getMetadataCache(): Map<string, ConversationMeta> {\n return this.metadataCache;\n }\n\n getProjects(): string[] {\n const normalized = new Set<string>();\n for (const p of this.projects) {\n normalized.add(p.replace(/\\/+$/, \"\"));\n }\n return Array.from(normalized).sort();\n }\n\n private async resolveProfiles(profiles?: Profile[]): Promise<Profile[]> {\n if (profiles && profiles.length > 0) return profiles;\n return loadProfiles(DEFAULT_CONFIG_PATH);\n }\n\n private transformView(\n metas: ConversationMeta[],\n options: ScanOptions,\n ): ConversationMeta[] | TreeConversation[] | GroupedConversations {\n switch (options.view) {\n case \"tree\":\n return this.toTree(metas);\n case \"grouped\":\n return this.toGrouped(metas);\n default:\n return metas;\n }\n }\n\n private toTree(metas: ConversationMeta[]): TreeConversation[] {\n const parents: TreeConversation[] = [];\n const subagents: ConversationMeta[] = [];\n\n for (const meta of metas) {\n if (meta.isSubagent) {\n subagents.push(meta);\n } else {\n parents.push({ ...meta, subagents: [] });\n }\n }\n\n const parentById = new Map(parents.map((p) => [p.id, p]));\n for (const sub of subagents) {\n const parent = sub.parentSessionId ? parentById.get(sub.parentSessionId) : undefined;\n if (parent) {\n parent.subagents.push(sub);\n } else {\n parents.push({ ...sub, subagents: [] });\n }\n }\n\n return parents;\n }\n\n private toGrouped(metas: ConversationMeta[]): GroupedConversations {\n const groups: GroupedConversations = {};\n for (const meta of metas) {\n const key = meta.teamName || \"_default\";\n if (!groups[key]) groups[key] = [];\n groups[key].push(meta);\n }\n return groups;\n }\n}\n","export class LRUCache<K, V> {\n private map = new Map<K, V>();\n private readonly capacity: number;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n }\n\n get(key: K): V | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Move to end (most recently used)\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: K, value: V): void {\n this.map.delete(key);\n this.map.set(key, value);\n if (this.map.size > this.capacity) {\n const oldest = this.map.keys().next();\n if (!oldest.done) this.map.delete(oldest.value);\n }\n }\n\n has(key: K): boolean {\n return this.map.has(key);\n }\n\n delete(key: K): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n\n get size(): number {\n return this.map.size;\n }\n}\n","import fg from \"fast-glob\";\nimport { stat } from \"fs/promises\";\nimport { getLogger } from \"./logger\";\n\nexport interface DiscoveredFile {\n filePath: string;\n account: string;\n}\n\nconst EXCLUDED_SEGMENTS = [\"/memory/\", \"/tool-results/\"];\n\n// stat() is cheap and IO-bound, so a large history scans much faster when the\n// calls run concurrently rather than one-at-a-time. Cap concurrency well under\n// typical fd limits.\nconst STAT_CONCURRENCY = 32;\n\nexport async function discoverJsonlFiles(\n dirs: { projectsDir: string; account: string }[],\n onProgress?: (found: number) => void,\n): Promise<DiscoveredFile[]> {\n const log = getLogger();\n const results: DiscoveredFile[] = [];\n\n for (const { projectsDir, account } of dirs) {\n let filePaths: string[];\n try {\n filePaths = await fg(\"**/*.jsonl\", {\n cwd: projectsDir,\n absolute: true,\n dot: false,\n });\n } catch (err) {\n log.warn({ projectsDir, account, err }, \"discovery: glob failed\");\n continue;\n }\n\n // Filter out excluded directory segments\n const filtered = filePaths.filter((fp) => !EXCLUDED_SEGMENTS.some((seg) => fp.includes(seg)));\n\n // Filter out empty files. Stat concurrently in bounded chunks; chunk order\n // is preserved and intra-chunk order follows the input, so discovery order\n // is stable (downstream sorts anyway).\n let kept = 0;\n let skippedEmpty = 0;\n let skippedInaccessible = 0;\n for (let i = 0; i < filtered.length; i += STAT_CONCURRENCY) {\n const chunk = filtered.slice(i, i + STAT_CONCURRENCY);\n const statted = await Promise.all(\n chunk.map(async (filePath) => {\n try {\n const s = await stat(filePath);\n return { filePath, size: s.size };\n } catch (err) {\n log.warn({ filePath, err }, \"discovery: stat failed\");\n return { filePath, size: -1 };\n }\n }),\n );\n for (const { filePath, size } of statted) {\n if (size < 0) {\n skippedInaccessible++;\n } else if (size > 0) {\n results.push({ filePath, account });\n kept++;\n } else {\n skippedEmpty++;\n }\n }\n }\n\n log.debug(\n {\n projectsDir,\n account,\n globMatches: filePaths.length,\n afterExclusions: filtered.length,\n kept,\n skippedEmpty,\n skippedInaccessible,\n },\n \"discovery: directory scanned\",\n );\n\n onProgress?.(results.length);\n }\n\n log.debug({ totalFiles: results.length, dirs: dirs.length }, \"discovery: complete\");\n return results;\n}\n","import { createReadStream } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { createInterface } from \"readline\";\nimport { getLogger } from \"./logger\";\nimport { cleanSystemTags } from \"./tags\";\nimport type {\n AttachmentSidecar,\n ContentTier,\n Conversation,\n ConversationMessage,\n ConversationMeta,\n MessageMetadata,\n MessageSender,\n MessageSnapshot,\n TeamInfo,\n ToolResultBlock,\n ToolUseBlock,\n TurnDuration,\n} from \"./types\";\n\nexport async function parseMeta(\n filePath: string,\n account: string,\n tier: ContentTier,\n): Promise<ConversationMeta | null> {\n const log = getLogger();\n log.trace({ filePath, account, tier: tier.name }, \"parseMeta: start\");\n let sessionId = \"\";\n let badJsonLines = 0;\n let sessionName = \"\";\n let latestTimestamp = \"\";\n let cwd = \"\";\n let teamName = \"\";\n let model: string | null = null;\n let messageCount = 0;\n let lastMessageSender: MessageSender = \"user\";\n let isTeammate = false;\n let firstUserSeen = false;\n let firstMessage: MessageSnapshot | null = null;\n let lastMessage: MessageSnapshot | null = null;\n let lastPrompt = \"\";\n const toolNameSet = new Set<string>();\n const previewParts: string[] = [];\n const snippetParts: string[] = [];\n let snippetLength = 0;\n let previewLength = 0;\n\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n badJsonLines++;\n continue;\n }\n\n if (entry.cwd && !cwd) cwd = entry.cwd as string;\n if (entry.sessionId && !sessionId) sessionId = entry.sessionId as string;\n if (entry.slug && !sessionName) sessionName = entry.slug as string;\n if (entry.teamName && !teamName) teamName = entry.teamName as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!latestTimestamp || ts > latestTimestamp) latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !lastPrompt) lastPrompt = entry.lastPrompt as string;\n continue;\n }\n\n // file-history-snapshot entries are intentionally excluded:\n // they contain file-edit undo state, not conversation content.\n if (type !== \"user\" && type !== \"assistant\") continue;\n if (entry.isMeta) continue;\n\n // Extract model from first assistant message\n if (model === null) {\n const msg = entry.message as Record<string, unknown> | undefined;\n if (msg?.model) model = msg.model as string;\n }\n\n // Check for teammate in first user message\n if (type === \"user\" && !firstUserSeen) {\n firstUserSeen = true;\n if (isTeammateContent((entry.message as Record<string, unknown>)?.content)) {\n isTeammate = true;\n }\n }\n\n // Extract content\n const msg = entry.message as Record<string, unknown> | undefined;\n const content = extractTextContent(msg?.content);\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isOnlyToolResult = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n // Collect tool names\n collectToolNames(msg?.content, toolNameSet);\n\n if (content || isOnlyToolResult) {\n messageCount++;\n lastMessageSender = type as MessageSender;\n\n if (content) {\n const ts = (entry.timestamp as string) || \"\";\n if (!firstMessage) {\n firstMessage = { text: content.slice(0, 200), timestamp: ts };\n }\n lastMessage = { text: content.slice(0, 200), timestamp: ts };\n\n if (previewLength < tier.previewMax) {\n previewParts.push(content);\n previewLength += content.length;\n }\n if (snippetLength < tier.snippetMax) {\n const remaining = tier.snippetMax - snippetLength;\n const chunk = content.length > remaining ? content.slice(0, remaining) : content;\n snippetParts.push(chunk);\n snippetLength += chunk.length;\n }\n }\n }\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseMeta: read failed\");\n return null;\n }\n\n if (badJsonLines > 0) {\n log.warn({ filePath, badJsonLines }, \"parseMeta: skipped malformed JSON lines\");\n }\n\n if (messageCount === 0) {\n log.trace({ filePath }, \"parseMeta: no messages\");\n return null;\n }\n\n const isSubagent = filePath.includes(\"/subagents/\");\n let parentSessionId: string | null = null;\n if (isSubagent) {\n const uuidDir = dirname(dirname(filePath));\n parentSessionId = join(dirname(uuidDir), `${basename(uuidDir)}.jsonl`);\n }\n\n const projectPath = cwd;\n const preview = previewParts.join(\" \").slice(0, tier.previewMax);\n\n return {\n id: filePath,\n filePath,\n sessionId: sessionId || basename(filePath, \".jsonl\"),\n sessionName,\n projectPath,\n projectName: getShortProjectName(projectPath),\n account,\n timestamp: latestTimestamp || new Date().toISOString(),\n messageCount,\n lastMessageSender,\n preview,\n contentSnippet: snippetParts.join(\" \"),\n gitBranch: null,\n model,\n isSubagent,\n parentSessionId,\n isTeammate,\n teamName: teamName || null,\n toolNames: Array.from(toolNameSet),\n firstMessage,\n lastMessage,\n lastPrompt: lastPrompt || undefined,\n };\n}\n\nexport async function parseConversation(\n filePath: string,\n account: string,\n): Promise<Conversation | null> {\n const log = getLogger();\n log.trace({ filePath, account }, \"parseConversation: start\");\n const messages: ConversationMessage[] = [];\n let badJsonLines = 0;\n let sessionId = \"\";\n let sessionName = \"\";\n let latestTimestamp = \"\";\n let cwd = \"\";\n const textParts: string[] = [];\n const pendingToolUses = new Map<string, ToolUseBlock>();\n const teamInfoMap = new Map<string, TeamInfo>();\n const turnDurations: TurnDuration[] = [];\n let lastPrompt = \"\";\n\n const fileStream = createReadStream(filePath);\n const rl = createInterface({ input: fileStream, crlfDelay: Infinity });\n\n try {\n for await (const line of rl) {\n if (!line.trim()) continue;\n\n let entry: Record<string, unknown>;\n try {\n entry = JSON.parse(line);\n } catch {\n badJsonLines++;\n continue;\n }\n\n if (entry.cwd && !cwd) cwd = entry.cwd as string;\n if (entry.sessionId && !sessionId) sessionId = entry.sessionId as string;\n if (entry.slug && !sessionName) sessionName = entry.slug as string;\n if (entry.timestamp) {\n const ts = entry.timestamp as string;\n if (!latestTimestamp || ts > latestTimestamp) latestTimestamp = ts;\n }\n\n const type = entry.type as string;\n\n if (type === \"last-prompt\") {\n if (entry.lastPrompt && !lastPrompt) lastPrompt = entry.lastPrompt as string;\n continue;\n }\n\n if (type === \"system\") {\n if (entry.subtype === \"turn_duration\" && typeof entry.durationMs === \"number\") {\n turnDurations.push({\n durationMs: entry.durationMs as number,\n messageCount: (entry.messageCount as number) || 0,\n uuid: entry.uuid as string | undefined,\n });\n }\n // file-history-snapshot entries are intentionally excluded:\n // they contain file-edit undo state, not conversation content.\n // stop_hook_summary and bridge_status are internal housekeeping only.\n continue;\n }\n\n if (type !== \"user\" && type !== \"assistant\") continue;\n if (entry.isMeta) continue;\n\n const msg = entry.message as Record<string, unknown> | undefined;\n\n // Track tool_use blocks from assistant messages\n const toolUseBlocks = extractToolUseBlocks(msg?.content);\n for (const block of toolUseBlocks) {\n pendingToolUses.set(block.id, block);\n }\n\n const hasToolUseResult = type === \"user\" && entry.toolUseResult != null;\n const isToolResultOnly = hasToolUseResult && isOnlyToolResultContent(msg?.content);\n\n const content = extractTextContent(msg?.content);\n\n const thinking = type === \"assistant\" ? extractThinking(msg?.content) : null;\n const hasThinking = !!(thinking?.content || thinking?.signature);\n\n if (content || isToolResultOnly || toolUseBlocks.length > 0 || hasThinking) {\n const metadata: MessageMetadata = {};\n\n if (msg?.model) metadata.model = msg.model as string;\n if (msg?.stop_reason !== undefined) metadata.stopReason = msg.stop_reason as string | null;\n if (entry.gitBranch) metadata.gitBranch = entry.gitBranch as string;\n if (entry.version) metadata.version = entry.version as string;\n\n const usage = msg?.usage as Record<string, number> | undefined;\n if (usage) {\n if (usage.input_tokens) metadata.inputTokens = usage.input_tokens;\n if (usage.output_tokens) metadata.outputTokens = usage.output_tokens;\n if (usage.cache_read_input_tokens)\n metadata.cacheReadTokens = usage.cache_read_input_tokens;\n if (usage.cache_creation_input_tokens)\n metadata.cacheCreationTokens = usage.cache_creation_input_tokens;\n }\n\n const toolUseNames = extractToolUseNames(msg?.content);\n if (toolUseNames.length > 0) metadata.toolUses = toolUseNames;\n if (toolUseBlocks.length > 0) metadata.toolUseBlocks = toolUseBlocks;\n\n if (isToolResultOnly) {\n const toolResultBlocks = extractToolResultBlocks(msg?.content, pendingToolUses);\n if (toolResultBlocks.length > 0) metadata.toolResults = toolResultBlocks;\n }\n\n if (entry.teamName) {\n metadata.teamName = entry.teamName as string;\n if (!teamInfoMap.has(metadata.teamName) && content) {\n const info = parseTeammateMessageTag(content);\n if (info) teamInfoMap.set(metadata.teamName, info);\n }\n }\n\n const thinkingContent = thinking?.content || undefined;\n const thinkingSignature = thinking?.signature || undefined;\n\n const hasMetadata = Object.keys(metadata).length > 0;\n\n messages.push({\n role: type as MessageSender,\n text: content || \"\",\n timestamp: (entry.timestamp as string) || \"\",\n uuid: (entry.uuid as string) || undefined,\n metadata: hasMetadata ? metadata : undefined,\n isToolResult: isToolResultOnly || undefined,\n isThinking: thinkingContent || thinkingSignature ? true : undefined,\n thinkingContent,\n thinkingSignature,\n parentUuid:\n entry.parentUuid !== undefined ? (entry.parentUuid as string | null) : undefined,\n requestId: type === \"assistant\" ? (entry.requestId as string | undefined) : undefined,\n promptId: type === \"user\" ? (entry.promptId as string | undefined) : undefined,\n isSidechain: typeof entry.isSidechain === \"boolean\" ? entry.isSidechain : undefined,\n permissionMode:\n type === \"user\" ? (entry.permissionMode as string | undefined) : undefined,\n hasImages: hasImageBlocks(msg?.content) || undefined,\n attachment:\n entry.attachment !== undefined ? (entry.attachment as AttachmentSidecar) : undefined,\n });\n if (content) textParts.push(content);\n }\n }\n } catch (err) {\n log.warn({ filePath, err }, \"parseConversation: read failed\");\n return null;\n }\n\n if (badJsonLines > 0) {\n log.warn({ filePath, badJsonLines }, \"parseConversation: skipped malformed JSON lines\");\n }\n\n if (messages.length === 0) {\n log.trace({ filePath }, \"parseConversation: no messages\");\n return null;\n }\n\n log.debug({ filePath, messageCount: messages.length }, \"parseConversation: complete\");\n\n // Apply collected team info to matching messages\n if (teamInfoMap.size > 0) {\n for (const msg of messages) {\n if (msg.metadata?.teamName) {\n const info = teamInfoMap.get(msg.metadata.teamName);\n if (info) msg.metadata.teamInfo = info;\n }\n }\n }\n\n return {\n id: filePath,\n filePath,\n projectPath: cwd,\n projectName: getShortProjectName(cwd),\n sessionId: sessionId || basename(filePath, \".jsonl\"),\n sessionName,\n messages,\n fullText: textParts.join(\" \"),\n timestamp: latestTimestamp || new Date().toISOString(),\n messageCount: messages.length,\n account,\n turnDurations: turnDurations.length > 0 ? turnDurations : undefined,\n lastPrompt: lastPrompt || undefined,\n };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction extractTextContent(content: unknown): string {\n if (!content) return \"\";\n if (typeof content === \"string\") return cleanSystemTags(content);\n if (Array.isArray(content)) {\n return content\n .map((item) => {\n if (typeof item === \"string\") return item;\n if (item?.type === \"text\" && item?.text) return item.text;\n if (item?.type === \"tool_result\" && typeof item?.content === \"string\") return item.content;\n return \"\";\n })\n .filter(Boolean)\n .map(cleanSystemTags)\n .join(\" \");\n }\n return \"\";\n}\n\nfunction extractToolUseNames(content: unknown): string[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name)\n .map((item) => item.name as string);\n}\n\nfunction extractToolUseBlocks(content: unknown): ToolUseBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_use\" && item?.name && item?.id)\n .map((item) => ({\n id: item.id as string,\n name: item.name as string,\n input: (item.input as Record<string, unknown>) || {},\n }));\n}\n\nconst TOOL_NAME_TO_TYPE: Record<string, ToolResultBlock[\"type\"]> = {\n Edit: \"edit\",\n Write: \"write\",\n Read: \"read\",\n Bash: \"bash\",\n Grep: \"grep\",\n Glob: \"glob\",\n Agent: \"taskAgent\",\n TaskCreate: \"taskCreate\",\n TaskUpdate: \"taskUpdate\",\n};\n\nfunction extractToolResultBlocks(\n content: unknown,\n pendingToolUses: Map<string, ToolUseBlock>,\n): ToolResultBlock[] {\n if (!Array.isArray(content)) return [];\n return content\n .filter((item) => item?.type === \"tool_result\" && item?.tool_use_id)\n .map((item) => {\n const toolName = pendingToolUses.get(item.tool_use_id as string)?.name ?? \"\";\n return {\n toolUseId: item.tool_use_id as string,\n type: TOOL_NAME_TO_TYPE[toolName] ?? \"generic\",\n content:\n typeof item.content === \"string\"\n ? { text: item.content as string }\n : ((item.content as Record<string, unknown>) ?? {}),\n isError: typeof item.is_error === \"boolean\" ? item.is_error : undefined,\n };\n });\n}\n\nfunction collectToolNames(content: unknown, toolSet: Set<string>): void {\n if (!Array.isArray(content)) return;\n for (const item of content) {\n if (item?.type === \"tool_use\" && item?.name) {\n toolSet.add(item.name as string);\n }\n }\n}\n\nfunction isOnlyToolResultContent(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.length > 0 && content.every((item) => item?.type === \"tool_result\");\n}\n\nfunction isTeammateContent(content: unknown): boolean {\n const raw =\n typeof content === \"string\"\n ? content\n : Array.isArray(content)\n ? content\n .map((item) =>\n typeof item === \"string\" ? item : item?.type === \"text\" ? (item.text ?? \"\") : \"\",\n )\n .join(\"\")\n : \"\";\n return raw.includes(\"<teammate-message\");\n}\n\nfunction extractThinking(content: unknown): { content: string; signature: string } {\n if (!Array.isArray(content)) return { content: \"\", signature: \"\" };\n const blocks = content.filter((item) => item?.type === \"thinking\");\n return {\n content: blocks\n .map((b) => b.thinking as string)\n .filter(Boolean)\n .join(\"\\n\\n\"),\n signature: blocks\n .map((b) => b.signature as string)\n .filter(Boolean)\n .join(\"\"),\n };\n}\n\nfunction hasImageBlocks(content: unknown): boolean {\n if (!Array.isArray(content)) return false;\n return content.some(\n (item) =>\n item?.type === \"image\" &&\n (item?.source?.type === \"base64\" || item?.file?.base64 !== undefined),\n );\n}\n\nfunction parseTeammateMessageTag(content: string): TeamInfo | null {\n const match = content.match(/<teammate-message\\s+([^>]*)>/);\n if (!match) return null;\n const attrs = match[1];\n const id = attrs.match(/teammate_id=\"([^\"]*)\"/)?.[1];\n if (!id) return null;\n const summary = attrs.match(/summary=\"([^\"]*)\"/)?.[1];\n const color = attrs.match(/color=\"([^\"]*)\"/)?.[1];\n return { teammateId: id, summary, color };\n}\n\nfunction getShortProjectName(fullPath: string): string {\n const parts = fullPath.split(\"/\").filter(Boolean);\n return parts.slice(-3).join(\"/\");\n}\n","const SYSTEM_TAGS = [\n \"system-reminder\",\n \"command-name\",\n \"command-message\",\n \"command-args\",\n \"ide_selection\",\n \"ide_opened_file\",\n \"local-command-stdout\",\n \"local-command-caveat\",\n \"retrieval_status\",\n \"task_id\",\n \"task_type\",\n \"task-id\",\n \"task-notification\",\n \"fast_mode_info\",\n \"persisted-output\",\n \"tool_use_error\",\n \"user-prompt-submit-hook\",\n \"thinking\",\n \"ask_user\",\n \"teammate-message\",\n];\n\nconst SYSTEM_TAG_RE = new RegExp(`<(${SYSTEM_TAGS.join(\"|\")})[^>]*>[\\\\s\\\\S]*?<\\\\/\\\\1>`, \"g\");\n\nexport function cleanSystemTags(text: string): string {\n return text\n .replace(SYSTEM_TAG_RE, \"\")\n .replace(/[^\\S\\n]+/g, \" \")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n","import type { ContentTier } from \"./types\";\n\nexport const DEFAULT_TIERS: Record<string, ContentTier> = {\n standard: { name: \"standard\", previewMax: 200, snippetMax: 5_000 },\n full: { name: \"full\", previewMax: 1_200, snippetMax: 50_000 },\n};\n\nexport function resolveTier(\n tierName: string,\n customTiers?: Record<string, ContentTier>,\n): ContentTier {\n const tier = customTiers?.[tierName] ?? DEFAULT_TIERS[tierName];\n if (!tier) {\n throw new Error(\n `Unknown tier \"${tierName}\". Available: ${Object.keys({ ...DEFAULT_TIERS, ...customTiers }).join(\", \")}`,\n );\n }\n return tier;\n}\n","// ─── Primitives ─────────────────────────────────────────────────────\n\nexport type MessageSender = \"user\" | \"assistant\";\nexport type Include = \"all\" | \"conversations\" | \"subagents\" | \"teammates\";\nexport type View = \"flat\" | \"tree\" | \"grouped\";\nexport type SortOrder = \"recent\" | \"oldest\" | \"messages-desc\" | \"messages-asc\" | \"alpha\";\n\nexport const VALID_SORT_ORDERS: SortOrder[] = [\n \"recent\",\n \"oldest\",\n \"messages-desc\",\n \"messages-asc\",\n \"alpha\",\n];\n\n// ─── Profile ────────────────────────────────────────────────────────\n\nexport interface Profile {\n id: string;\n label: string;\n configDir: string;\n enabled: boolean;\n emoji?: string;\n scanHistory?: boolean;\n}\n\n// ─── Content Tiers ──────────────────────────────────────────────────\n\nexport interface ContentTier {\n name: string;\n previewMax: number;\n snippetMax: number;\n}\n\n// ─── ConversationMeta (full superset) ───────────────────────────────\n\nexport interface MessageSnapshot {\n text: string;\n timestamp: string;\n}\n\nexport interface ConversationMeta {\n id: string;\n filePath: string;\n sessionId: string;\n sessionName: string;\n projectPath: string;\n projectName: string;\n account: string;\n timestamp: string;\n messageCount: number;\n lastMessageSender: MessageSender;\n preview: string;\n contentSnippet: string;\n gitBranch: string | null;\n model: string | null;\n isSubagent: boolean;\n parentSessionId: string | null;\n isTeammate: boolean;\n teamName: string | null;\n toolNames: string[];\n firstMessage: MessageSnapshot | null;\n lastMessage: MessageSnapshot | null;\n lastPrompt?: string;\n}\n\n// ─── View Variants ──────────────────────────────────────────────────\n\nexport interface TreeConversation extends ConversationMeta {\n subagents: ConversationMeta[];\n}\n\nexport interface GroupedConversations {\n [groupKey: string]: ConversationMeta[];\n}\n\n// ─── Options ────────────────────────────────────────────────────────\n\nexport interface FileStatEntry {\n mtimeMs: number;\n size: number;\n}\n\nexport interface ScanOptions {\n profiles?: Profile[];\n tier?: string;\n tiers?: Record<string, ContentTier>;\n include?: Include;\n view?: View;\n sort?: SortOrder;\n since?: string;\n project?: string;\n account?: string;\n limit?: number;\n offset?: number;\n onProgress?: (scanned: number, total: number) => void;\n onBatch?: (metas: ConversationMeta[]) => void;\n /** Known file stats from a previous scan. Files whose (mtimeMs, size) match\n * are skipped — the cached ConversationMeta is reused instead. */\n statCache?: Map<string, { stat: FileStatEntry; meta: ConversationMeta }>;\n}\n\nexport interface ScanResult {\n conversations: ConversationMeta[] | TreeConversation[] | GroupedConversations;\n total: number;\n scanned: number;\n}\n\nexport interface SearchOptions extends ScanOptions {\n fields?: string[];\n}\n\nexport interface SearchMatch {\n field: string;\n snippet: string;\n}\n\nexport interface SearchResult {\n meta: ConversationMeta;\n score: number;\n matches: SearchMatch[];\n}\n\nexport interface GetConversationOptions {\n profiles?: Profile[];\n}\n\nexport interface GetConversationPageOptions {\n beforeIndex?: number;\n limit: number;\n}\n\nexport interface ConversationPage {\n messages: ConversationMessage[];\n total: number;\n fromIndex: number;\n}\n\n// A page sliced directly from a single parsed JSONL file, carrying the parsed\n// Conversation alongside the window so a caller can build a full response\n// (meta + messages) from one parse, without a prior scan().\nexport interface SingleFilePage extends ConversationPage {\n conversation: Conversation;\n}\n\n// ─── Full Conversation ──────────────────────────────────────────────\n\nexport interface TurnDuration {\n durationMs: number;\n messageCount: number;\n uuid?: string;\n}\n\nexport interface DeferredToolsDeltaAttachment {\n type: \"deferred_tools_delta\";\n addedNames: string[];\n addedLines: unknown[];\n removedNames: string[];\n}\n\nexport type AttachmentSidecar =\n | DeferredToolsDeltaAttachment\n | { type: string; [key: string]: unknown };\n\nexport interface ToolUseBlock {\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n toolUseId: string;\n type:\n | \"edit\"\n | \"write\"\n | \"read\"\n | \"bash\"\n | \"grep\"\n | \"glob\"\n | \"taskAgent\"\n | \"taskCreate\"\n | \"taskUpdate\"\n | \"generic\";\n content: Record<string, unknown>;\n isError?: boolean;\n}\n\nexport interface TeamInfo {\n teammateId: string;\n summary?: string;\n color?: string;\n}\n\nexport interface MessageMetadata {\n model?: string;\n stopReason?: string | null;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n gitBranch?: string;\n version?: string;\n toolUses?: string[];\n toolUseBlocks?: ToolUseBlock[];\n toolResults?: ToolResultBlock[];\n teamName?: string;\n teamInfo?: TeamInfo;\n}\n\nexport interface ConversationMessage {\n role: MessageSender;\n text: string;\n timestamp: string;\n uuid?: string;\n metadata?: MessageMetadata;\n isToolResult?: boolean;\n isThinking?: boolean;\n thinkingContent?: string;\n thinkingSignature?: string;\n parentUuid?: string | null;\n requestId?: string;\n promptId?: string;\n isSidechain?: boolean;\n permissionMode?: string;\n hasImages?: boolean;\n attachment?: AttachmentSidecar;\n}\n\nexport interface Conversation {\n id: string;\n filePath: string;\n projectPath: string;\n projectName: string;\n sessionId: string;\n sessionName: string;\n messages: ConversationMessage[];\n fullText: string;\n timestamp: string;\n messageCount: number;\n account: string;\n turnDurations?: TurnDuration[];\n lastPrompt?: string;\n}\n"],"mappings":"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","names":["pino","FlexSearchModule","import_path","import_fs","import_promises","fg","import_fs","import_path","msg"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { LoggerOptions, Logger } from 'pino';
|
|
2
|
+
export { Logger, LoggerOptions } from 'pino';
|
|
3
|
+
|
|
4
|
+
type MessageSender = "user" | "assistant";
|
|
5
|
+
type Include = "all" | "conversations" | "subagents" | "teammates";
|
|
6
|
+
type View = "flat" | "tree" | "grouped";
|
|
7
|
+
type SortOrder = "recent" | "oldest" | "messages-desc" | "messages-asc" | "alpha";
|
|
8
|
+
declare const VALID_SORT_ORDERS: SortOrder[];
|
|
9
|
+
interface Profile {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
configDir: string;
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
emoji?: string;
|
|
15
|
+
scanHistory?: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface ContentTier {
|
|
18
|
+
name: string;
|
|
19
|
+
previewMax: number;
|
|
20
|
+
snippetMax: number;
|
|
21
|
+
}
|
|
22
|
+
interface MessageSnapshot {
|
|
23
|
+
text: string;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
}
|
|
26
|
+
interface ConversationMeta {
|
|
27
|
+
id: string;
|
|
28
|
+
filePath: string;
|
|
29
|
+
sessionId: string;
|
|
30
|
+
sessionName: string;
|
|
31
|
+
projectPath: string;
|
|
32
|
+
projectName: string;
|
|
33
|
+
account: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
messageCount: number;
|
|
36
|
+
lastMessageSender: MessageSender;
|
|
37
|
+
preview: string;
|
|
38
|
+
contentSnippet: string;
|
|
39
|
+
gitBranch: string | null;
|
|
40
|
+
model: string | null;
|
|
41
|
+
isSubagent: boolean;
|
|
42
|
+
parentSessionId: string | null;
|
|
43
|
+
isTeammate: boolean;
|
|
44
|
+
teamName: string | null;
|
|
45
|
+
toolNames: string[];
|
|
46
|
+
firstMessage: MessageSnapshot | null;
|
|
47
|
+
lastMessage: MessageSnapshot | null;
|
|
48
|
+
lastPrompt?: string;
|
|
49
|
+
}
|
|
50
|
+
interface TreeConversation extends ConversationMeta {
|
|
51
|
+
subagents: ConversationMeta[];
|
|
52
|
+
}
|
|
53
|
+
interface GroupedConversations {
|
|
54
|
+
[groupKey: string]: ConversationMeta[];
|
|
55
|
+
}
|
|
56
|
+
interface FileStatEntry {
|
|
57
|
+
mtimeMs: number;
|
|
58
|
+
size: number;
|
|
59
|
+
}
|
|
60
|
+
interface ScanOptions {
|
|
61
|
+
profiles?: Profile[];
|
|
62
|
+
tier?: string;
|
|
63
|
+
tiers?: Record<string, ContentTier>;
|
|
64
|
+
include?: Include;
|
|
65
|
+
view?: View;
|
|
66
|
+
sort?: SortOrder;
|
|
67
|
+
since?: string;
|
|
68
|
+
project?: string;
|
|
69
|
+
account?: string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
offset?: number;
|
|
72
|
+
onProgress?: (scanned: number, total: number) => void;
|
|
73
|
+
onBatch?: (metas: ConversationMeta[]) => void;
|
|
74
|
+
/** Known file stats from a previous scan. Files whose (mtimeMs, size) match
|
|
75
|
+
* are skipped — the cached ConversationMeta is reused instead. */
|
|
76
|
+
statCache?: Map<string, {
|
|
77
|
+
stat: FileStatEntry;
|
|
78
|
+
meta: ConversationMeta;
|
|
79
|
+
}>;
|
|
80
|
+
}
|
|
81
|
+
interface ScanResult {
|
|
82
|
+
conversations: ConversationMeta[] | TreeConversation[] | GroupedConversations;
|
|
83
|
+
total: number;
|
|
84
|
+
scanned: number;
|
|
85
|
+
}
|
|
86
|
+
interface SearchOptions extends ScanOptions {
|
|
87
|
+
fields?: string[];
|
|
88
|
+
}
|
|
89
|
+
interface SearchMatch {
|
|
90
|
+
field: string;
|
|
91
|
+
snippet: string;
|
|
92
|
+
}
|
|
93
|
+
interface SearchResult {
|
|
94
|
+
meta: ConversationMeta;
|
|
95
|
+
score: number;
|
|
96
|
+
matches: SearchMatch[];
|
|
97
|
+
}
|
|
98
|
+
interface GetConversationOptions {
|
|
99
|
+
profiles?: Profile[];
|
|
100
|
+
}
|
|
101
|
+
interface GetConversationPageOptions {
|
|
102
|
+
beforeIndex?: number;
|
|
103
|
+
limit: number;
|
|
104
|
+
}
|
|
105
|
+
interface ConversationPage {
|
|
106
|
+
messages: ConversationMessage[];
|
|
107
|
+
total: number;
|
|
108
|
+
fromIndex: number;
|
|
109
|
+
}
|
|
110
|
+
interface SingleFilePage extends ConversationPage {
|
|
111
|
+
conversation: Conversation;
|
|
112
|
+
}
|
|
113
|
+
interface TurnDuration {
|
|
114
|
+
durationMs: number;
|
|
115
|
+
messageCount: number;
|
|
116
|
+
uuid?: string;
|
|
117
|
+
}
|
|
118
|
+
interface DeferredToolsDeltaAttachment {
|
|
119
|
+
type: "deferred_tools_delta";
|
|
120
|
+
addedNames: string[];
|
|
121
|
+
addedLines: unknown[];
|
|
122
|
+
removedNames: string[];
|
|
123
|
+
}
|
|
124
|
+
type AttachmentSidecar = DeferredToolsDeltaAttachment | {
|
|
125
|
+
type: string;
|
|
126
|
+
[key: string]: unknown;
|
|
127
|
+
};
|
|
128
|
+
interface ToolUseBlock {
|
|
129
|
+
id: string;
|
|
130
|
+
name: string;
|
|
131
|
+
input: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
interface ToolResultBlock {
|
|
134
|
+
toolUseId: string;
|
|
135
|
+
type: "edit" | "write" | "read" | "bash" | "grep" | "glob" | "taskAgent" | "taskCreate" | "taskUpdate" | "generic";
|
|
136
|
+
content: Record<string, unknown>;
|
|
137
|
+
isError?: boolean;
|
|
138
|
+
}
|
|
139
|
+
interface TeamInfo {
|
|
140
|
+
teammateId: string;
|
|
141
|
+
summary?: string;
|
|
142
|
+
color?: string;
|
|
143
|
+
}
|
|
144
|
+
interface MessageMetadata {
|
|
145
|
+
model?: string;
|
|
146
|
+
stopReason?: string | null;
|
|
147
|
+
inputTokens?: number;
|
|
148
|
+
outputTokens?: number;
|
|
149
|
+
cacheReadTokens?: number;
|
|
150
|
+
cacheCreationTokens?: number;
|
|
151
|
+
gitBranch?: string;
|
|
152
|
+
version?: string;
|
|
153
|
+
toolUses?: string[];
|
|
154
|
+
toolUseBlocks?: ToolUseBlock[];
|
|
155
|
+
toolResults?: ToolResultBlock[];
|
|
156
|
+
teamName?: string;
|
|
157
|
+
teamInfo?: TeamInfo;
|
|
158
|
+
}
|
|
159
|
+
interface ConversationMessage {
|
|
160
|
+
role: MessageSender;
|
|
161
|
+
text: string;
|
|
162
|
+
timestamp: string;
|
|
163
|
+
uuid?: string;
|
|
164
|
+
metadata?: MessageMetadata;
|
|
165
|
+
isToolResult?: boolean;
|
|
166
|
+
isThinking?: boolean;
|
|
167
|
+
thinkingContent?: string;
|
|
168
|
+
thinkingSignature?: string;
|
|
169
|
+
parentUuid?: string | null;
|
|
170
|
+
requestId?: string;
|
|
171
|
+
promptId?: string;
|
|
172
|
+
isSidechain?: boolean;
|
|
173
|
+
permissionMode?: string;
|
|
174
|
+
hasImages?: boolean;
|
|
175
|
+
attachment?: AttachmentSidecar;
|
|
176
|
+
}
|
|
177
|
+
interface Conversation {
|
|
178
|
+
id: string;
|
|
179
|
+
filePath: string;
|
|
180
|
+
projectPath: string;
|
|
181
|
+
projectName: string;
|
|
182
|
+
sessionId: string;
|
|
183
|
+
sessionName: string;
|
|
184
|
+
messages: ConversationMessage[];
|
|
185
|
+
fullText: string;
|
|
186
|
+
timestamp: string;
|
|
187
|
+
messageCount: number;
|
|
188
|
+
account: string;
|
|
189
|
+
turnDurations?: TurnDuration[];
|
|
190
|
+
lastPrompt?: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
declare function applySort(metas: ConversationMeta[], order: SortOrder): ConversationMeta[];
|
|
194
|
+
declare function applySinceFilter(metas: ConversationMeta[], since: string): ConversationMeta[];
|
|
195
|
+
declare function applyIncludeFilter(metas: ConversationMeta[], include: Include): ConversationMeta[];
|
|
196
|
+
declare function applyProjectFilter(metas: ConversationMeta[], project: string): ConversationMeta[];
|
|
197
|
+
declare function applyAccountFilter(metas: ConversationMeta[], account: string): ConversationMeta[];
|
|
198
|
+
declare function applyPagination<T>(items: T[], limit: number, offset: number): {
|
|
199
|
+
items: T[];
|
|
200
|
+
total: number;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
declare function readGitBranch(projectPath: string): string | null;
|
|
204
|
+
|
|
205
|
+
declare class SearchIndexer {
|
|
206
|
+
private index;
|
|
207
|
+
private documents;
|
|
208
|
+
constructor();
|
|
209
|
+
private createIndex;
|
|
210
|
+
addDocument(meta: ConversationMeta): void;
|
|
211
|
+
buildIndex(metas: ConversationMeta[]): void;
|
|
212
|
+
search(query: string, options?: {
|
|
213
|
+
fields?: string[];
|
|
214
|
+
limit?: number;
|
|
215
|
+
}): SearchResult[];
|
|
216
|
+
private getRecent;
|
|
217
|
+
private generateMatches;
|
|
218
|
+
getDocumentCount(): number;
|
|
219
|
+
updateDocument(meta: ConversationMeta): void;
|
|
220
|
+
removeDocument(id: string): void;
|
|
221
|
+
clear(): void;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
declare function createLogger(options?: LoggerOptions | Logger): Logger;
|
|
225
|
+
declare function setLogger(logger: Logger): void;
|
|
226
|
+
declare function getLogger(): Logger;
|
|
227
|
+
|
|
228
|
+
declare function resolveConfigDir(configDir: string): string;
|
|
229
|
+
declare function getProjectsDir(profile: Profile): string;
|
|
230
|
+
declare function detectDefaultProfile(): Promise<Profile>;
|
|
231
|
+
declare function loadProfiles(configPath: string): Promise<Profile[]>;
|
|
232
|
+
declare function saveProfiles(profiles: Profile[], configPath: string): Promise<void>;
|
|
233
|
+
|
|
234
|
+
declare class ConversationScanner {
|
|
235
|
+
private metadataCache;
|
|
236
|
+
private conversationLRU;
|
|
237
|
+
private sessionIdIndex;
|
|
238
|
+
private projects;
|
|
239
|
+
private indexer;
|
|
240
|
+
private lastTier;
|
|
241
|
+
constructor(options?: {
|
|
242
|
+
metadataCacheSize?: number;
|
|
243
|
+
conversationCacheSize?: number;
|
|
244
|
+
});
|
|
245
|
+
scan(options?: ScanOptions): Promise<ScanResult>;
|
|
246
|
+
search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
|
|
247
|
+
getConversation(id: string, _options?: GetConversationOptions): Promise<Conversation | null>;
|
|
248
|
+
getConversationPage(id: string, options: GetConversationPageOptions): Promise<ConversationPage | null>;
|
|
249
|
+
parseSingleFilePage(filePath: string, account: string | undefined, options: GetConversationPageOptions): Promise<SingleFilePage | null>;
|
|
250
|
+
refreshFile(filePath: string, account?: string): Promise<ConversationMeta | null>;
|
|
251
|
+
getMetadataCache(): Map<string, ConversationMeta>;
|
|
252
|
+
getProjects(): string[];
|
|
253
|
+
private resolveProfiles;
|
|
254
|
+
private transformView;
|
|
255
|
+
private toTree;
|
|
256
|
+
private toGrouped;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
declare function cleanSystemTags(text: string): string;
|
|
260
|
+
|
|
261
|
+
declare const DEFAULT_TIERS: Record<string, ContentTier>;
|
|
262
|
+
declare function resolveTier(tierName: string, customTiers?: Record<string, ContentTier>): ContentTier;
|
|
263
|
+
|
|
264
|
+
declare function resetDefaultScanner(): void;
|
|
265
|
+
declare function scan(options?: ScanOptions, scanner?: ConversationScanner): Promise<ScanResult>;
|
|
266
|
+
declare function search(query: string, options?: SearchOptions, scanner?: ConversationScanner): Promise<SearchResult[]>;
|
|
267
|
+
declare function getConversation(id: string, options?: GetConversationOptions, scanner?: ConversationScanner): Promise<Conversation | null>;
|
|
268
|
+
|
|
269
|
+
export { type AttachmentSidecar, type ContentTier, type Conversation, type ConversationMessage, type ConversationMeta, type ConversationPage, ConversationScanner, DEFAULT_TIERS, type DeferredToolsDeltaAttachment, type FileStatEntry, type GetConversationOptions, type GetConversationPageOptions, type GroupedConversations, type Include, type MessageMetadata, type MessageSender, type MessageSnapshot, type Profile, type ScanOptions, type ScanResult, SearchIndexer, type SearchMatch, type SearchOptions, type SearchResult, type SingleFilePage, type SortOrder, type TeamInfo, type ToolResultBlock, type ToolUseBlock, type TreeConversation, type TurnDuration, VALID_SORT_ORDERS, type View, applyAccountFilter, applyIncludeFilter, applyPagination, applyProjectFilter, applySinceFilter, applySort, cleanSystemTags, createLogger, detectDefaultProfile, getConversation, getLogger, getProjectsDir, loadProfiles, readGitBranch, resetDefaultScanner, resolveConfigDir, resolveTier, saveProfiles, scan, search, setLogger };
|