This is the full internal spec — every type, every endpoint, every component. If you're new here, start with the README for the elevator pitch, then read docs/ARCHITECTURE.md for the architectural tour. Use this file once you need the exhaustive view.
AI Tab Optimizer is a Chrome Extension that acts as an intelligent browser workspace manager. It helps users with 100+ open tabs regain control of their browser without losing valuable context. The tool combines tab management, AI-driven analysis, and a persistent knowledge base in Obsidian to turn browser chaos into an organized, searchable archive of past work sessions.
The core promise: you can safely clean up your browser because nothing important will be lost.
- Cognitive overload — 100+ tabs create mental overhead and slow down focus
- Fear of closing — users hesitate to close tabs because they might need them later
- Lost context — after closing a session, users forget what they were working on
- Duplicate waste — same or similar pages open multiple times
- No structure — unrelated topics mixed together with no grouping
- No memory — browser has no searchable history of past work sessions
| Use Case | Description |
|---|---|
| Daily cleanup | User wants to reduce tab count at end of day |
| Topic grouping | User wants to organize research by theme |
| Session handoff | User saves context before going offline |
| Recovery | User reopens a work context from last week |
| Knowledge capture | User turns a set of tabs into an Obsidian note cluster |
| Deduplication | User removes near-identical tabs automatically |
| Read later | User defers tabs without losing them |
| Conversational recall | User asks the extension to find and summarize tabs from SQLite-backed memory |
- List all open tabs across all windows, grouped by window (collapsible)
- Display: title, URL, domain, favicon, pinned state, active state, Chrome tab group name
- Search/filter by title or domain
- Close single tab or multiple selected tabs (bulk action)
- Pin / unpin tab
- Mark tab as "important" (local flag, persisted in
chrome.storage.local) - Restore recently closed tabs (via
chrome.sessionsAPI)
- Detect exact duplicate URLs (string equality on normalized URL)
- Detect near-duplicate URLs (same domain + path similarity > 0.8)
- Flag stale tabs (not accessed in N days, configurable threshold)
- Group tabs by domain
- Badges on each tab indicating detected issues
- Total tab count, duplicate count, stale count, important count
- Top 5 domains by tab count
- FastAPI server (
agent.py) orchestrating local AI CLIs (claude_code,codex_cli) - Per-tab recommendations: keep / close / group / read_later / archive
- Theme-first topic clusters with names, descriptions, tags
- Session summary and stats (estimated closable, main themes)
- SQLite-backed per-URL caching: results stored in
tab_analysis.dbwith a 180-day TTL - SQLite-backed settings: provider choice, fallback chain, model, and retention settings persist server-side in
app_settings - Already-analyzed URLs are skipped server-side; only uncached tabs are sent to AI
- Automatic provider failover: the server can retry a batch with Codex CLI if Claude Code CLI is unavailable or rate-limited, then fall back to heuristic recommendations if both providers fail
- Provider timeouts ensure a stuck CLI is abandoned and the batch can continue through the next provider
- Incremental batch analysis: the service worker sends tabs in batches of 30 and updates the UI after every completed batch
- Fire-and-forget:
ANALYZE_TABSreturns immediately, results come via broadcast events (no message channel timeout on 1000+ tabs) - Progress UI: phase indicator, progress bar, elapsed timer, cached/new/saved/processed counts, current batch
- Runtime provider status card: active CLI/model, configured chain, and recorded failover errors during the current analysis run
- Per-tab coverage UI shows which current tabs are cached, newly analyzed, still pending, or failed
- Metadata tracking: accumulated duration, input/output tokens, cost across all batches
- "From cache" badge, "Re-analyze" button and amber ↻ Refresh button with confirmation dialog to force full re-analysis (bypasses server-side URL cache)
- Analytics section: ↻ Refresh button sends aggregated SQLite data (top domains, habits, clusters, recommendation stats) to LLM via
POST /analytics/refresh, displays AI Insight card with browsing patterns summary, improvement suggestions, cluster insights, and habits commentary - "Stop analysis" persists partial run state in SQLite and can resume later for the same tab fingerprint
- Intermediate recommendations appear as each completed batch is saved
- Client-side heuristic fallback results are imported back into SQLite so per-URL coverage remains accurate
- Server-side logging in
runtime_logsSQLite table + persisted session history inanalysis_sessions - LLM call logging in
llm_call_logsSQLite table with per-call details (provider, model, tokens, duration, status) - Server endpoints:
GET /stats,GET /cache-stats,GET /db-status,GET /runtime-logs,GET /tab-history,GET /snapshots,POST /snapshots/import,POST /tab-history/import,POST /db/clear,GET /llm-call-logs,GET /cache/urls,DELETE /cache/urls,GET /sessions,DELETE /sessions/{id},GET /insights,GET /habits-score,POST /recommendation-actions,GET /recommendation-stats,GET /activity-heatmap,GET /clusters,POST /clusters/merge,PUT /clusters/{id},DELETE /clusters/{id},POST /analysis-runs,PUT /analysis-runs/{id},GET /analysis-runs/{id},GET /analysis-runs/latest,POST /tab-analysis-status,POST /url-analysis/import,POST /chat,POST /analytics/refresh
- Dedicated Search tab with conversational dialog UI
- Retrieves candidates from
url_analysisandtab_history_eventsvia consolidated UNION ALL query, plus persistent topic clusters - Uses the same provider chain and model configured in Settings for AI Analysis (20s LLM timeout, up to 6 keyword patterns)
- Falls back to SQLite-only ranking if the model is disabled or unavailable
- Response includes a grounded answer, relevant URLs, provider/model metadata, and follow-up prompts
- Result cards can focus an already-open tab, open a URL, or close the matching open tab
- Search input stays active while query is processing — user can type the next query without waiting
- Pre-cleanup auto-snapshot for undo capability
- Step-by-step guided flow: one recommendation at a time
- Accept / Skip / Change Action per recommendation
- Completion screen with summary (closed, grouped, saved, skipped)
- Export cleanup report to Obsidian
- Track tab events: opened, closed, activated
- In-memory tab info cache for
onRemoved(tab data unavailable after close) - Persist tab history events in server-side SQLite with local fallback buffering when the server is unavailable
- History panel with timeframe filters (day / week / month)
- Search by title/domain, sort by recent or visit count
- Per-URL stats: activation count, first/last seen, last opened time, still-open badge
- Daily cleanup alarm to prune old entries (configurable retention)
- Manual snapshot creation with optional name
- Auto-snapshots via Chrome Alarms API (configurable interval 1–24 hours)
- Pre-cleanup snapshots (trigger:
pre-cleanup) - Snapshot stores: tab list, window layout, basic stats
- Snapshots persist in server-side SQLite and survive extension reload/removal
- Snapshots history screen (list + detail)
- Restore all tabs or selected tabs from snapshot
- Delete snapshot, max snapshots cap with auto-pruning
- LinkNote — single tab export to
TabOptimizer/Links/{domain}/{slug}.md - TopicCluster — AI cluster export to
TabOptimizer/Topics/{topic-slug}.md - TabSessionSnapshot — snapshot export to
TabOptimizer/Sessions/{date}-{name-slug}.md - CleanupReview — cleanup report export to
TabOptimizer/Cleanups/{date}-cleanup.md - Vault path configurable in Settings
- File System Access API for writing to vault
- Dedup check (don't export same URL twice)
- On-demand page extraction (
page-extractor.ts) - Extracts: meta description, first H1, text excerpt (500 chars)
- Injected via
chrome.scripting.executeScript
- AI provider selection: none / local_server
- Local server URL (default:
http://localhost:8765) + "Test Connection" button - Primary server AI provider: Claude Code CLI or Codex CLI
- Fallback server AI provider: none / Claude Code CLI / Codex CLI
- Codex model input (default:
gpt-5.4) - Optional CLI path overrides for
claudeandcodex - SQLite tools in Settings: refresh DB info, sync local extension buffers, clear server-side DB state, inspect recent runtime logs from
runtime_logstable - LLM call logs viewer: browse individual LLM API calls with provider, model, tokens, duration, and status
- URL cache browser: view and delete cached per-URL AI analysis entries
- Analysis sessions list: view and delete past analysis session records
- Obsidian vault path
- Protected domains list
- Stale tab threshold (days, default: 7)
- Max snapshots to keep (default: 30)
- Auto-snapshot toggle + interval (hours)
- History retention days (7–90, default: 30)
- Side Panel as primary UI (React + Zustand)
- Popup (minimal — opens side panel + shows tab count)
- Service worker with message routing (45 request types)
- 8 broadcast events
- 8 views in side panel
- 13 React components
- i18n (English / Russian, ~345 keys)
- Manifest V3 compliant
extension/src/
├── background/
│ ├── service-worker.ts # Chrome API bridge, listeners, router entrypoint
│ ├── transport.ts # Local server transport helpers
│ ├── persistence.ts # Settings/local persistence helpers
│ └── analysis-helpers.ts # Shared analysis state helpers
├── side-panel/
│ ├── index.html
│ ├── main.tsx # React app entry
│ ├── App.tsx # Root component (view routing, broadcast listener)
│ ├── store.ts # Zustand store (9 state slices incl. Search)
│ └── components/ # 13 React components
│ ├── AIRecommendations.tsx
│ ├── BulkActions.tsx
│ ├── ChatSearch.tsx
│ ├── CleanupSession.tsx
│ ├── Header.tsx
│ ├── HistoryPanel.tsx
│ ├── RecentlyClosed.tsx
│ ├── SettingsView.tsx
│ ├── SnapshotDetail.tsx
│ ├── SnapshotsList.tsx
│ ├── StatsBar.tsx
│ ├── TabItem.tsx
│ └── TabList.tsx
├── popup/
│ ├── index.html
│ └── main.tsx # Minimal popup (open side panel)
├── content/
│ └── page-extractor.ts # Extract page metadata on demand
├── shared/
│ ├── types/
│ │ ├── index.ts # Re-exports
│ │ ├── tab.ts # TabRecord, RuleFlags, WindowGroup
│ │ ├── snapshot.ts # SnapshotRecord, WindowSnapshot, TabSnapshot
│ │ ├── messages.ts # MessageRequest, BroadcastEvent, UserSettings
│ │ ├── ai.ts # AIAnalysisResult, TopicCluster, TabRecommendation, etc.
│ │ └── history.ts # TabHistoryEntry, TabHistoryStats, HistoryTimeframe
│ ├── utils/
│ │ ├── url.ts # URL normalization, domain extraction
│ │ ├── rules.ts # Rule engine (duplicates, stale, domain grouping)
│ │ └── obsidian.ts # Obsidian export functions (4 export types)
│ └── i18n/
│ ├── index.ts # useTranslation hook
│ └── translations.ts # en/ru translations (~345 keys)
└── styles.css # Tailwind entry
| Component | Role |
|---|---|
service-worker.ts |
Tab CRUD, Chrome API bridge, tab history logging, AI server proxy, snapshot management, auto-snapshot scheduler, content script injection, Smart Tab Groups via chrome.tabs.group(), message router (45 request types), SQLite-backed Search dialog bridge |
side-panel (React) |
Primary UI — 8 views, state management via Zustand, 8 broadcast event types, Tab Insights dashboard, Smart Tab Groups, SQLite-backed Search dialog |
popup |
Lightweight entry point — opens side panel |
page-extractor.ts |
Content script injected on demand — extracts meta description, H1, body text excerpt |
agent.py |
FastAPI server — routes AI requests to Claude Code CLI or Codex CLI, persists cache/history/snapshots/settings in SQLite, and powers Search over SQLite memory |
Side Panel (React/Zustand) ←→ Service Worker ←→ Chrome APIs (tabs, tabGroups, sessions, storage, alarms, scripting)
↕ ↕
Content Scripts Local AI Server
(on-demand) (localhost:8765)
All communication uses chrome.runtime.sendMessage / chrome.runtime.onMessage. Message types are a discriminated union in shared/types/messages.ts.
Requests (45):
GET_ALL_TABS, CLOSE_TABS, PIN_TAB, SET_USER_FLAG, CREATE_SNAPSHOT, GET_SNAPSHOTS, GET_SNAPSHOT, DELETE_SNAPSHOT, RESTORE_SNAPSHOT, GET_SETTINGS, SAVE_SETTINGS, GET_SERVER_DB_STATUS, GET_SERVER_RUNTIME_LOGS, SYNC_SERVER_PERSISTENCE, CLEAR_SERVER_DB, GET_TAB_HISTORY, GET_TAB_ANALYSIS_STATUS, ANALYZE_TABS, STOP_AI_ANALYSIS, GET_AI_RESULT, EXTRACT_PAGE, START_CLEANUP_SESSION, APPLY_CLEANUP_ACTION, GET_LLM_CALL_LOGS, GET_URL_CACHE_LIST, DELETE_URL_CACHE, GET_ANALYSIS_SESSIONS, DELETE_ANALYSIS_SESSION, GROUP_TABS_BY_CLUSTER, GET_TAB_INSIGHTS, GET_HABITS_SCORE, TRACK_RECOMMENDATION, GET_RECOMMENDATION_STATS, GET_ACTIVITY_HEATMAP, GET_PERSISTENT_CLUSTERS, MERGE_AI_CLUSTERS, RENAME_CLUSTER, DELETE_CLUSTER, FOCUS_ON_CLUSTER, EXIT_FOCUS_MODE, GET_CLUSTER_TAB_MATCHES, OPEN_URL, FOCUS_TAB, CHAT_SEARCH, REFRESH_ANALYTICS
Broadcasts (8):
TABS_UPDATED, SNAPSHOT_CREATED, AI_ANALYSIS_COMPLETE, AI_ANALYSIS_ERROR, AI_ANALYSIS_CANCELED, AI_ANALYSIS_PROGRESS, AI_ANALYSIS_PARTIAL, HISTORY_UPDATED
The service worker remains the runtime entrypoint, but low-risk helper modules now isolate transport, settings persistence, and analysis-state utilities. The entry file still owns listeners, alarms, orchestration, and the message switch.
- In-memory tab cache —
Map<number, {url, title, domain}>foronRemovedevents (tab data unavailable after close) - Tab helpers —
getAllTabs()queries all tabs/windows/groups, normalizes intoWindowGroup[] - Snapshots —
createSnapshot(),getSnapshots(),saveSnapshots()with max-cap pruning - Settings —
getSettings(),saveSettings()with defaults merge - User flags —
getUserFlags(),setUserFlag()for important/read_later/protected marks - Tab history —
logTabEvent(),getTabHistory(),cleanupOldHistory()with timeframe filtering and per-URL stats aggregation - AI analysis —
analyzeTabsViaServer()buildsAISessionInput, POSTs to local server, broadcasts progress, persists analysis runs to SQLite for resume, auto-merges topic clusters on completion - Analysis run persistence —
analysis_runstable stores full run state (pending tabs, per-tab statuses, result, metadata) for stop/resume across extension reloads - Per-tab analysis status —
getTabAnalysisStatus()returns per-tab coverage from server SQLite, used by TabStatusCoverageCard - Content script injection —
extractPageContent()injects and messages the content script - Auto-snapshots —
setupAutoSnapshot()manages Chrome Alarm - Analytics helpers — habits score, recommendation tracking, activity heatmap, persistent clusters, focus mode
- Search dialog — conversational retrieval over SQLite-backed AI results, history, and clusters using the same provider chain as AI analysis
- Message handler —
handleMessage()switch on 45 request types - Tab event listeners —
onCreated,onRemoved,onActivated,onUpdated,onMoved,onAttached,onDetached - Startup — sets up auto-snapshot alarm + daily history cleanup alarm
'tabs' | 'snapshots' | 'snapshot-detail' | 'settings' | 'history' | 'ai-recommendations' | 'chat' | 'cleanup-session'
- Navigation —
currentView,setView() - Tabs —
windowGroups,totalTabs,selectedTabIds,searchQuery,loadTabs(),toggleTabSelection(),selectAll(),deselectAll() - Snapshots —
snapshots,selectedSnapshot,loadSnapshots(),selectSnapshot() - Recently closed —
recentlyClosed,loadRecentlyClosed() - History —
historyStats,historyTimeframe,historySearchQuery,historyLoading,loadHistory(),setHistoryTimeframe() - AI —
aiResult,aiAnalyzedAt,aiLoading,aiError,aiProgress,aiMetadata,aiFromCache,aiWasCanceled,aiResumeAvailable,aiRunId,aiTabStatuses,aiStatusSummary,analyzeTabs(),resumeAIAnalysis(),stopAIAnalysis(),loadAIResult(),loadAITabStatuses(),setAIResult(),setAIPartialResult(),setAIError(),setAIProgress(),setAIStopped(),setAITabStatuses() - Cleanup —
cleanupStep,cleanupActions,cleanupRecommendations,startCleanupSession(),applyCleanupAction(),skipCleanupStep(),finishCleanup() - Focus Mode —
focusClusterId,focusClusterName,focusMatchedTabIds,setFocusMode(),exitFocusMode() - Search —
chatMessages,chatLoading,sendChatQuery(),clearChat()
interface TabRecord {
id: number;
windowId: number;
index: number;
url: string;
title: string;
domain: string;
favIconUrl?: string;
pinned: boolean;
active: boolean;
groupId?: number;
groupName?: string;
lastAccessed?: number;
ruleFlags?: RuleFlags;
userFlag?: 'important' | 'read_later' | 'protected';
}
interface RuleFlags {
isExactDuplicate: boolean;
duplicateOfTabId?: number;
isNearDuplicate: boolean;
isStale: boolean;
domainGroup: string;
}
interface WindowGroup {
windowId: number;
focused: boolean;
tabs: TabRecord[];
}interface SnapshotRecord {
id: string;
name: string;
createdAt: number;
trigger: 'manual' | 'auto' | 'pre-cleanup';
windows: WindowSnapshot[];
stats: {
totalTabs: number;
totalWindows: number;
topDomains: string[];
};
}
interface WindowSnapshot {
windowId: number;
focused: boolean;
tabs: TabSnapshot[];
}
interface TabSnapshot {
url: string;
title: string;
domain: string;
pinned: boolean;
favIconUrl?: string;
groupName?: string;
}interface AISessionInput {
tabs: {
id: number;
title: string;
url: string;
domain: string;
pinned: boolean;
active: boolean;
groupId?: number;
groupName?: string;
pageExcerpt?: string;
metaDescription?: string;
}[];
}
interface AIAnalysisResult {
summary: string;
topicClusters: TopicCluster[];
tabRecommendations: TabRecommendation[];
duplicateGroups: DuplicateGroup[];
staleTabIds: number[];
sessionStats: {
estimatedClosable: number;
mainThemes: string[];
urgentItems: number;
actionBreakdown?: Partial<Record<RecommendedAction, number>>;
};
}
type RecommendedAction = 'keep' | 'group' | 'read_later' | 'archive' | 'close';
interface TabRecommendation {
tabId: number;
action: RecommendedAction;
confidence: number;
reason: string;
suggestedGroupName?: string;
}
type AIProviderId = 'claude_code' | 'codex_cli';
interface AIProviderAttempt {
provider: AIProviderId;
model: string | null;
status: 'succeeded' | 'failed';
error?: string | null;
}
interface AIProviderRuntimeStatus {
primaryProvider: AIProviderId | 'none';
fallbackProvider: AIProviderId | 'none';
currentProvider: AIProviderId | null;
currentModel: string | null;
attempts: AIProviderAttempt[];
lastError: string | null;
servedFromCacheOnly: boolean;
}
interface AIAnalysisMetadata {
durationMs: number;
durationApiMs: number;
totalCostUsd: number | null;
inputTokens: number;
outputTokens: number;
tabCount: number;
providerUsed?: AIProviderId | null;
modelUsed?: string | null;
providerAttempts?: AIProviderAttempt[];
providerStatus?: AIProviderRuntimeStatus;
}
interface AIProgress {
phase: 'preparing' | 'sending' | 'analyzing' | 'persisting' | 'processing' | 'stopping' | 'stopped';
tabsTotal: number;
tabsCached: number;
tabsNew: number;
tabsAnalyzed: number;
tabsProcessed: number;
tabsRemaining: number;
tabsSaved: number;
batchesTotal: number;
batchesCompleted: number;
currentBatch: number;
startedAt: number;
providerStatus?: AIProviderRuntimeStatus;
}
type TabAnalysisState = 'pending' | 'cached' | 'analyzed' | 'failed';
type TabAnalysisSource = 'pending' | 'database' | 'provider' | 'heuristic';
interface TabAnalysisStatus {
tabId: number;
url: string;
title: string;
domain: string;
status: TabAnalysisState;
source: TabAnalysisSource;
action?: RecommendedAction | null;
confidence?: number | null;
reason?: string | null;
analyzedAt?: number | null;
provider?: AIProviderId | null;
model?: string | null;
}
interface TabAnalysisStatusSummary {
total: number;
cached: number;
analyzed: number;
pending: number;
failed: number;
}interface HabitsScore {
score: number;
trend: 'improving' | 'stable' | 'declining';
components: HabitsScoreComponent[];
computedAt: number;
}
interface HabitsScoreComponent {
name: string;
value: number;
normalizedScore: number;
weight: number;
}
interface RecommendationActionStats {
totalActions: number;
acceptanceRate: number;
byAiAction: Record<string, { total: number; accepted: number; skipped: number; modified: number; avgConfidence: number }>;
confidenceCorrelation: { bucket: string; acceptanceRate: number }[];
}
interface ActivityHeatmapData {
grid: number[][]; // 7×24 (days × hours)
domains: string[];
}
interface PersistentCluster {
id: number;
name: string;
description: string;
tags: string[];
tabUrls: string[];
createdAt: number;
updatedAt: number;
}interface LLMCallLogEntry {
id: number;
sessionId: string;
provider: string;
model: string;
inputTokens: number;
outputTokens: number;
durationMs: number;
status: 'succeeded' | 'failed';
error?: string;
createdAt: string;
}
interface UrlCacheEntry {
url: string;
domain: string;
provider: string;
model: string;
analyzedAt: string;
expiresAt: string;
}
interface AnalysisSessionEntry {
id: string;
tabCount: number;
provider: string;
model: string;
durationMs: number;
totalCostUsd: number | null;
inputTokens: number;
outputTokens: number;
createdAt: string;
}
interface TabInsights {
topDomains: { domain: string; count: number }[];
avgTabsPerWindow: number;
avgSessionDuration: number;
snapshotTrend: { date: string; count: number }[];
}interface UserSettings {
obsidianVaultPath: string;
protectedDomains: string[];
staleDaysThreshold: number;
maxStoredSnapshots: number;
aiProvider: 'anthropic' | 'openai' | 'ollama' | 'local_server' | 'none';
serverAiProvider: 'none' | 'claude_code' | 'codex_cli';
fallbackAiProvider: 'none' | 'claude_code' | 'codex_cli';
apiKey: string;
ollamaEndpoint: string;
localServerUrl: string;
claudeCliPath: string;
codexCliPath: string;
codexModel: string;
autoSnapshotEnabled: boolean;
autoSnapshotIntervalHours: number;
historyRetentionDays: number;
}| Key | Type | Purpose |
|---|---|---|
snapshots |
SnapshotRecord[] |
Temporary offline snapshot buffer before sync to server SQLite |
settings |
UserSettings |
Local mirror of server-backed settings persisted in SQLite |
userFlags |
Record<number, string> |
Per-tab user flags (important/read_later/protected) |
tabHistory |
TabHistoryEntry[] |
Temporary offline history buffer before sync to server SQLite |
lastAIResult |
CachedAIResult |
Cached aggregated AI analysis with fingerprint and metadata |
exportedUrls |
string[] |
Dedup guard for Obsidian exports |
FastAPI server wrapping local AI CLIs. Runs on port 8765 (configurable via PORT env var).
Endpoints:
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Connection test → { status: "ok" } |
| GET | /db-status |
Database statistics (table counts, DB size, timestamps) |
| GET | /runtime-logs |
Recent provider/analysis/database log entries |
| GET | /cache-stats |
Current SQLite URL-cache counts and timestamps |
| GET | /settings |
Load persisted server/extension settings from SQLite |
| POST | /settings |
Save merged server/extension settings to SQLite |
| POST | /analyze |
Accepts tab list, returns { result, metadata, cacheStats } with tabsFromCache, tabsAnalyzed, tabsSaved |
| POST | /analyze/cancel |
Cancel a running analysis — kills CLI subprocess, sets cancel event, returns { cancelled } |
| GET | /stats |
Cumulative usage stats (total analyses, cost, tokens, avg duration) |
| POST | /tab-history/events |
Append a single tab history event |
| POST | /tab-history/import |
Bulk import locally buffered history events |
| GET | /tab-history |
Aggregated tab history stats by timeframe |
| POST | /tab-history/prune |
Prune old history entries by retention days |
| GET | /snapshots |
List all persisted snapshots |
| GET | /snapshots/{id} |
Get a single snapshot by ID |
| POST | /snapshots |
Save a snapshot to SQLite |
| POST | /snapshots/import |
Bulk import locally buffered snapshots |
| DELETE | /snapshots/{id} |
Delete a snapshot |
| POST | /db/clear |
Clear all tables (optionally preserve settings) |
| GET | /llm-call-logs |
Individual LLM call log entries (provider, model, tokens, duration, status) |
| GET | /cache/urls |
List cached per-URL AI analysis entries |
| DELETE | /cache/urls |
Delete cached URL analysis entries |
| GET | /sessions |
List analysis session records |
| DELETE | /sessions/{id} |
Delete a specific analysis session |
| GET | /insights |
Tab insights dashboard (top domains, avg stats, snapshot trend) |
| GET | /habits-score |
Composite tab habits/health score with weighted components and trend |
| POST | /recommendation-actions |
Log accepted / skipped / modified cleanup actions |
| GET | /recommendation-stats |
Aggregate recommendation acceptance and confidence stats |
| GET | /activity-heatmap |
7x24 activity heatmap from persisted tab history events |
| GET | /clusters |
List persistent topic clusters |
| POST | /clusters/merge |
Merge AI clusters into persistent storage |
| PUT | /clusters/{id} |
Rename a persistent cluster |
| DELETE | /clusters/{id} |
Delete a persistent cluster |
| POST | /analysis-runs |
Persist a new partial/final analysis run snapshot |
| PUT | /analysis-runs/{id} |
Update a server-backed analysis run during batching |
| GET | /analysis-runs/{id} |
Read one persisted analysis run |
| GET | /analysis-runs/latest |
Read the latest persisted analysis run |
| POST | /tab-analysis-status |
Return per-tab SQLite-backed analysis coverage for the current tab set |
| POST | /url-analysis/import |
Import heuristic/client-side per-URL analysis rows into SQLite |
| POST | /chat |
Search and summarize SQLite-backed browser memory via the configured provider chain |
| POST | /analytics/refresh |
Send aggregated SQLite analytics to LLM, returns AI insight (browsing patterns, suggestions, cluster insights, habits commentary) |
How it works:
- Extension service worker preflights
/tab-analysis-statusto learn which current tabs already have fresh SQLite results - Server loads persisted app settings from SQLite to determine provider chain and model
- Server looks up per-URL cache entries in
tab_analysis.dband filters out fresh hits - Only tabs without fresh coverage are formatted into compact prompts and analyzed in batches of 30 via the configured CLI provider
- If the primary CLI fails or hits a usage limit, the server retries the batch with the fallback CLI provider
- The extension persists partial/final run state with per-tab statuses in
analysis_runs, so stop/resume survives reloads - If a batch falls back to heuristics, those per-URL results are still persisted to
url_analysiseither directly on the server or via/url-analysis/import, so per-tab coverage stays in sync with what the user just analyzed - The server stores per-URL results + session metrics in SQLite, then returns the aggregated
AnalyzeResponse - The Search dialog uses
/chatto retrieve SQLite candidates and, when useful, summarize/rank them with the same provider/model chain as AI Analysis
Prompt strategy:
- System prompt: role definition + exact JSON output schema
- User prompt: compact tab list (not raw JSON, for token efficiency)
{
"permissions": [
"tabs", // read tab info, close/pin/move tabs
"tabGroups", // read and manage tab groups
"storage", // store settings, snapshots, history
"alarms", // scheduled auto-snapshots + history cleanup
"sessions", // access recently closed tabs
"sidePanel", // use side panel UI
"scripting" // inject content script on demand
],
"host_permissions": [
"http://localhost/*" // for local AI server communication
],
"optional_host_permissions": [
"<all_urls>" // for content script injection (page extraction)
]
}<all_urls>is optional and only used when user explicitly triggers page content extraction- No tab data is sent anywhere without user action
- The extension only talks to the localhost server; downstream CLI providers may still use cloud-backed inference depending on the configured provider
- API keys stored in
chrome.storage.local(not sync) - No analytics or telemetry
Side Panel
├── Tabs # Main tab list with search, filters, bulk actions
├── History # Tab activity history with timeframe filters
├── AI Recommendations # AI analysis results + topic clusters (↻ refresh buttons on both Analysis/Analytics)
│ └── Cleanup Session # Guided cleanup flow (step-by-step)
├── Search # SQLite-backed dialog over AI results, history, and theme clusters (non-blocking input)
├── Snapshots History # List of saved snapshots
│ └── Snapshot Detail # View / restore / export individual snapshot
└── Settings # AI provider, vault path, preferences
Top bar with 6 nav items: Tabs, History, AI, Search, Snapshots, Settings. Sub-views (snapshot-detail, cleanup-session) alias back to their parent nav item.
Each tab shows:
- Favicon + title (truncated) + domain
- Status badges:
duplicate,stale,pinned, user flags - Quick actions: close, pin, flag, Obsidian export
- Checkbox for bulk selection
| Entity | Status | Output Path |
|---|---|---|
LinkNote |
Implemented | TabOptimizer/Links/{domain}/{slug}.md |
TopicCluster |
Implemented | TabOptimizer/Topics/{topic-slug}.md |
TabSessionSnapshot |
Implemented | TabOptimizer/Sessions/{date}-{name-slug}.md |
CleanupReview |
Implemented | TabOptimizer/Cleanups/{date}-cleanup.md |
ReadingList |
Planned (v0.3) | — |
WorkContext |
Planned (v0.3) | — |
All exports use YAML frontmatter + Markdown body with Obsidian tags.
- Tab list view across all windows
- Rule-based deduplication + stale detection
- Manual snapshot creation + restore
- Basic Obsidian export (LinkNote)
- Settings: vault path, protected domains
- i18n (English / Russian)
- Tab History panel (event tracking, stats, timeframe filters)
- AI analysis via local Python server (FastAPI + Claude Code CLI / Codex CLI)
- AI recommendations panel with per-tab actions, action breakdown pills, model display, per-tab coverage, and stop/resume
- Richer AI summary with action breakdown, yellow banner for partial results after stop
- Topic clusters from AI with Obsidian export, theme-first clustering, and tag-based comparison against persistent clusters
- Smart Tab Groups: create Chrome Tab Groups from AI topic clusters via
chrome.tabs.group() - Tab Insights dashboard: top domains, average stats, snapshot trend in AI panel
- Analytics & Focus Mode: habits score, recommendation tracking, activity heatmap, persistent clusters, focus mode
- Search dialog over SQLite-backed AI results, history, and saved clusters using the same provider chain as AI analysis
- Cleanup Session mode (guided step-by-step flow)
- Content script for page metadata extraction
- Auto-snapshots via Chrome Alarms
- Obsidian exports: TopicCluster, TabSessionSnapshot, CleanupReview
- LLM call logging with
llm_call_logsSQLite table and viewer in Settings - URL cache browser and analysis sessions management in Settings
- Provider health/status view in the UI
- Additional CLI/local-model adapters
- Snapshot comparison view
- ReadingList + WorkContext Obsidian entities
- Drag and drop tab reordering
- Polish + performance optimization
- Onboarding flow
- Keyboard shortcuts
- Options page (standalone)
- Extension packaging + Chrome Web Store submission
- Broader local-model ecosystem support
- Cross-device snapshot sync (chrome.storage.sync)
- Obsidian plugin counterpart (read-only dashboard inside Obsidian)
- Tab usage heatmap + analytics over time