# Project export: Truely

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: Never be lied to again.
- Devpost: https://devpost.com/software/truely-7cys9i
- GitHub: https://github.com/nirpechuk/calhacksai
- Video: https://www.youtube.com/embed/wntHBhzt6_0?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Letta: Most Creative Usage of Letta)
- Team: 3 GitHub contributor(s) — Ron Pechuk (14 commits), nirpechuk (12 commits), itsannbat (8 commits)

## Devpost submission (written by the team)

### Inspiration

“When we abandon facts, we abandon freedom.” — Timothy Snyder. Misinformation is lethal: 232 000 Americans died in 15 months after vaccine-hoax posts deterred them from a free COVID shot. 330 000 South Africans lost their lives—and 35 000 babies were needlessly infected—because one speech denied the HIV-AIDS link. The viral Cluely “cheat-on-everything” AI now pumps out polished essays, deepfakes, even fake lab data, eroding trust in all AI. Every new pandemic and every election is a bigger target. That’s the blaze—Truely is our fire-extinguisher. What It Does Truely is a plug-and-play browser extension that unleashes a fleet of AI agents the instant a page loads: How We Built It Frontend: WXT (Web Extension Toolkit) for modern Chrome extension development with TypeScript Svelte 5 for reactive, component-based UI with excellent performance TailwindCSS for utility-first styling and responsive design Backend: Chrome Extension Manifest V3 for secure background service workers and content scripts LETTA AI API for intelligent fact-checking and misinformation detection Anthropic Claude API as a secondary AI agent for robust analysis Custom DOM parsing engine to extract and clean webpage content for AI analysis WebExtension Messaging API for secure communication between extension components UUID-based action tracking for managing multiple fact-checking annotations Architecture Accomplishments We’re Proud Of Built a real-time AI fact-checker that flags misinformation directly in the browser Designed interactive annotations with clear, cited corrections that don’t interrupt reading Engineered a smart DOM pipeline that filters only essential user-visible content Shipped a performant and secure Manifest V3 Chrome Extension with advanced background capabilities Learnings & Challenges Parsing complex, inconsistent DOM structures across the web Compressing large webpage text into limited AI context windows without losing key facts Building a seamless system to highlight and annotate dynamic content without breaking page layout Coordinating responses across multiple AI agents with different output formats What’s Next Cross-page memory: Agents remember what you’ve seen and flag contradictions or recurring misinformation across sites. Custom AI agents, made by users: Anyone can deploy browser-native agents using just a prompt Monetization through agent marketplaces: Users can create, share, and even monetize their own agents via blockchain-powered rewards Truth has a new home address: your browser. We’re not just fighting misinformation—we’re making honesty the default experience on the internet. Join us.

## README (from the GitHub repository)

# Truely AI

An agentic browser extension to tell you the whole truth. See our devpost [here](https://devpost.com/software/truely-7cys9i).

## Setup

We used [WXT](https://wxt.dev/), [Svelte](https://svelte.dev/) and [PNPM](https://pnpm.io/) to build this project.

To get started, run `pnpm install` to install the dependencies and `pnpm dev` to start the development server.

## Project Structure

This project uses [WXT](https://wxt.dev/)'s standard project structure, with all source code located inside the `src` directory.

- `src/`: Contains all the source code for the extension.
- `src/assets/`: This directory is for static assets that are processed by the build tool (Vite), like CSS, fonts, or images that are imported into your UI components.
- `src/entrypoints/`: This is where the main files for your extension live. Each subdirectory represents a different part of your extension.
  - `background/`: The background service worker for the extension.
  - `content/`: Content scripts that run in the context of web pages.
  - `sidepanel/`: The sidepanel for the extension with configuration and a chat interface.
- `src/lib/components/`: Contains reusable Svelte components that are used across different parts of the extension's UI.
- `src/public/`: Any files in this directory are copied directly to the root of the extension's build output without being processed.
  -  `icon/`: Contains the extension icons in various sizes.
- `wxt.config.ts`: The main configuration file for the WXT framework, where you can customize the build process and extension manifest.
- `package.json`: The standard Node.js manifest file containing project metadata, dependencies, and scripts.

## Recommended IDE Setup

[VS Code](https://code.visualstudio.com/) with the following extensions:

- [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode)
- [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)


## Detected evidence (automated analysis)

Indexed codebase: 22 recognized source files, 216 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Svelte (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Cursor — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (27 of 27)

```
.cursor/rules/chrome-extension-development.mdc
.cursor/rules/front-end-cursor-rules.mdc
.gitignore
.vscode/extensions.json
package.json
README.md
src/app.css
src/entrypoints/background/agents/agent.ts
src/entrypoints/background/agents/anthropicAgent.ts
src/entrypoints/background/agents/index.ts
src/entrypoints/background/agents/lettaAgent.ts
src/entrypoints/background/agents/system-prompt.ts
src/entrypoints/background/agents/tests/lettaAgentTest.ts
src/entrypoints/background/index.ts
src/entrypoints/content/index.ts
src/lib/components/Highlight.svelte
src/lib/components/index.ts
src/lib/components/Note.svelte
src/lib/components/Popup.svelte
src/lib/components/Summary.svelte
src/utils/constants.ts
src/utils/messaging.ts
src/utils/types/agent.ts
src/utils/types/index.ts
tsconfig.json
wxt-env.d.ts
wxt.config.ts
```

### Dependencies

- package.json: @anthropic-ai/sdk@^0.54.0, @letta-ai/letta-client@^0.1.132, @sveltejs/vite-plugin-svelte@^5.1.0, @tailwindcss/forms@^0.5.10, @tailwindcss/typography@^0.5.16, @tailwindcss/vite@^4.1.10, @tsconfig/svelte@^5.0.4, @types/chrome@^0.0.326, @webext-core/messaging@^2.3.0, @wxt-dev/module-svelte@^2.0.3, dotenv@^16.5.0, mode-watcher@^1.0.8, prettier@^3.5.3, prettier-plugin-svelte@^3.4.0, prettier-plugin-tailwindcss@^0.6.13, runed@^0.29.0, svelte@^5.28.2, svelte-check@^4.1.6, tailwindcss@^4.1.10, tslib@^2.8.1, typescript@^5.8.3, uuid@^11.1.0, vite@^6.3.5, vite-plugin-top-level-await@^1.5.0, vite-plugin-wasm@^3.4.1, wxt@^0.20.6

### Recent commits (newest first)

- Added devpost to readme
- final touches
- Add files via upload
- Demo done
- demo
- Merge branch 'main' of https://github.com/nirpechuk/calhacksai
- add notification + popup
- added loading animation
- Adding anthropic agent
- Merge branch 'main' of https://github.com/nirpechuk/calhacksai
- mvp done?
- quick readme fix
- Merge branch 'main' of https://github.com/nirpechuk/calhacksai
- update highlight component
- Merge pull request #12 from nirpechuk/ann
- adding HTML cleaning code
- fix agents class
- fixing issues
- WIP!
- Made agent cleaner and generalized.

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "wxt-svelte-starter",
  "description": "manifest.json description",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "wxt",
    "dev:firefox": "wxt -b firefox",
    "build": "wxt build",
    "build:firefox": "wxt build -b firefox",
    "zip": "wxt zip",
    "zip:firefox": "wxt zip -b firefox",
    "check": "svelte-check --tsconfig ./tsconfig.json",
    "postinstall": "wxt prepare",
    "test:letta": "npx tsx src/entrypoints/background/agents/lettaAgentTest.ts"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.54.0",
    "@letta-ai/letta-client": "^0.1.132",
    "@webext-core/messaging": "^2.3.0",
    "dotenv": "^16.5.0",
    "mode-watcher": "^1.0.8",
    "runed": "^0.29.0",
    "uuid": "^11.1.0"
  },
  "devDependencies": {
    "@sveltejs/vite-plugin-svelte": "^5.1.0",
    "@tailwindcss/forms": "^0.5.10",
    "@tailwindcss/typography": "^0.5.16",
    "@tailwindcss/vite": "^4.1.10",
    "@tsconfig/svelte": "^5.0.4",
    "@types/chrome": "^0.0.326",
    "@wxt-dev/module-svelte": "^2.0.3",
    "prettier": "^3.5.3",
    "prettier-plugin-svelte": "^3.4.0",
    "prettier-plugin-tailwindcss": "^0.6.13",
    "svelte": "^5.28.2",
    "svelte-check": "^4.1.6",
    "tailwindcss": "^4.1.10",
    "tslib": "^2.8.1",
    "typescript": "^5.8.3",
    "vite": "^6.3.5",
    "vite-plugin-top-level-await": "^1.5.0",
    "vite-plugin-wasm": "^3.4.1",
    "wxt": "^0.20.6"
  }
}

```

### src/utils/types/index.ts

```typescript
export * from './agent';
```

### src/lib/components/index.ts

```typescript
export { default as Highlight } from './Highlight.svelte';
export { default as Note } from './Note.svelte';
```

### src/entrypoints/background/index.ts

```typescript
import { extensionMessenger } from "@/utils/messaging";
import { createAgent, LettaAgent, AnthropicAgent } from "./agents";

const prompt = `You are a fact checker. You will investigate all facts that are even slightly non-reputable by using
the web_search tool and finding respectable, trusted sources (not forums or blogs) speaking on the
matter. However, only run at most 1-2 successful web_search functions for each fact, because you
want to return results as fast and as soon as possible. Make sure to use the tool properly and pay
special attention to your request. ONLY RETURN THE JSON ARRAY OF ACTIONS, NOTHING ELSE. AND ONLY HIGHLIGHT TEXT`
// const prompt = `Return the container of the first instance of the word "svelte". It is very important that you do not use ANY tools under any circumstances.`

// const prompt = `You are a cool facts agent. You will find whatever the top 3 coolest facts from the text on the page are and return whatever annotations you think are relvent to the surrounding elements.`

export default defineBackground(() => {
  console.log('Background script loaded', { id: browser.runtime.id });

  const agent = createAgent(LettaAgent, import.meta.env.VITE_LETTA_AGENT_ID || '', prompt);
  agent.initialize(import.meta.env.VITE_LETTA_API_KEY || '');
  // const agent = createAgent(AnthropicAgent, prompt);
  // agent.initialize(import.meta.env.VITE_ANTHROPIC_API_KEY || '');

  extensionMessenger.onMessage('activate', async (message) => {
    console.log('activate', message);

    const result = await agent.getActions(message.data);

    return [result];
  });
});
```

### src/entrypoints/content/index.ts

```typescript
import '@/app.css';
import { mount } from "svelte";
import { Highlight, Note } from "@/lib/components";
import { extensionMessenger, websiteMessenger } from '@/utils/messaging';
import { ActionType, AgentResult } from '@/utils/types';
import { HIGHLIGHT_MARK_CLASS } from '@/utils/constants';

export default defineContentScript({
  /* NOTE [RON]: we need to be careful setting this to true because many
   * sites add/remove iframes on the fly making this script run over and over.
   */
  allFrames: false,
  matches: ['*://*/*'],
  async main(ctx) {
    websiteMessenger.removeAllListeners();
    injectAnimationStyles();
    console.log('Content script loaded', { id: browser.runtime.id, manifest: browser.runtime.getManifest() });

    const annotations = document.createElement('div');
    annotations.id = 'truely-annotations';
    document.body.appendChild(annotations);

    // Show loading animation
    showGeminiLoader();

    // ================================
    // Send Message to Background to Run
    // ================================
    const results = await extensionMessenger.sendMessage('activate', extractVisibleDOM());
    // Hide loading animation
    hideGeminiLoader();

    results.forEach((result) => {
      console.log('result', result);

      result.actions.forEach((action) => {
        switch (action.type) {
          case ActionType.HIGHLIGHT:
          case ActionType.UNDERLINE:
            mount(Highlight, {
              target: annotations,
              context: new Map([["wxt:context", ctx]]),
              props: {
                action,
                underline_only: action.type === ActionType.UNDERLINE
              }
            });
            break;
          case ActionType.ADD_NOTE:
            mount(Note, {
              target: annotations,
              context: new Map([["wxt:context", ctx]]),
              props: { action }
            });
            break;
          default:
            console.log(`Unsupported action type: ${action.type}\nAction: ${JSON.stringify(action, null, 2)}`);
            break;
        }
      });
    });
  }
});

const showGeminiLoader = () => {
  const loader = document.createElement('div');
  loader.id = 'truely-gemini-loader';
  loader.innerHTML = `
    <div class="truely-loader-edge truely-loader-top"></div>
    <div class="truely-loader-edge truely-loader-right"></div>
    <div class="truely-loader-edge truely-loader-bottom"></div>
    <div class="truely-loader-edge truely-loader-left"></div>
    <div class="truely-loader-corner truely-loader-top-left"></div>
    <div class="truely-loader-corner truely-loader-top-right"></div>
    <div class="truely-loader-corner truely-loader-bottom-left"></div>
    <div class="truely-loader-corner truely-loader-bottom-right"></div>
  `;
  document.body.appendChild(loader);
};

const hideGeminiLoader = () => {
  const loader = document.getElementById('truely-gemini-loader');
  if (loader) {
    loader.classList.add('truely-loader-fadeout');
    setTimeout(() => {
      loader.remove();
    }, 500);
  }
};

const injectAnimationStyles = () => {
  const style = document.createElement('style');
  style.textContent = `
    @keyframes highlight-wipe-in {
      from { background-size: 0% 100%; }
      to { background-size: 100% 100%; }
    }

    @keyframes underline-wipe-in {
      from { transform: scaleX(0); }
      to   { transform: scaleX(1); }
    }

    @keyframes truely-gemini-pulse {
      0%, 100% {
        opacity: 0.3;
        transform: scale(1);
      }
      50% {
        opacity: 0.8;
        transform: scale(1.05);
      }
    }

    @keyframes truely-gemini-color {
      0% {
        background: linear-gradient(45deg, #667eea, #764ba2);
        box-shadow: 0 0 20px rgba(102, 126, 234, 0.44);
      }
      25% {
        background: linear-gradient(45deg, #6366f1, #7c3aed);
        box-shadow: 0 0 20px rgba(99, 102, 241, 0.44);
      }
      50% {
        background: linear-gradient(45deg, #6d28d9, #8b5cf6);
        box-shadow: 0 0 20px rgba(109, 40, 217, 0.44);
      }
      75% {
        background: linear-gradient(45deg, #7c3aed, #6366f1);
        box-shadow: 0 0 20px rgba(124, 58, 237, 0.44);
      }
      100% {
        background: linear-gradient(45deg, #667eea, #764ba2);
        box-shadow: 0 0 20px rgba(102, 126, 234, 0.44);
      }
    }

    @keyframes truely-loader-fadeout {
      from { opacity: 1; }
      to { opacity: 0; }
    }

    #truely-gemini-loader {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      pointer-events: none;
      z-index: 999999;
      overflow: hidden;
    }

    #truely-gemini-loader.truely-loader-fadeout {
      animation: truely-loader-fadeout 0.5s ease-out forwards;
    }

    .truely-loader-edge {
      position: absolute;
      filter: blur(8px);
      animation: truely-gemini-pulse 2s ease-in-out infinite,
                 truely-gemini-color 6s ease-in-out infinite;
    }

    .truely-loader-top {
      top: -12px;
      left: 0;
      width: 100%;
      height: 15px;
      background: linear-gradient(45deg, #667eea, #764ba2);
    }

    .truely-loader-bottom {
      bottom: -11px;
      left: 0;
      width: 100%;
      height: 15px;
      background: linear-gradient(45deg, #667eea, #764ba2);
      animation-delay: 0.25s;
    }

    .truely-loader-left {
      left: -11px;
      top: 0;
      width: 15px;
      height: 100%;
      background: linear-gradient(45deg, #667eea, #764ba2);
      animation-delay: 0.5s;
    }

    .truely-loader-right {
      right: -11px;
      top: 0;
      width: 15px;
      height: 100%;
      background: linear-gradient(45deg, #667eea, #764ba2);
      animation-delay: 0.75s;
    }

    .truely-loader-corner {
      position: absolute;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      filter: blur(12px);
      animation: truely-gemini-pulse 1.5s ease-in-out infinite,
                 truely-gemini-color 6s ease-in-out infinite;

[truncated — 4161 more characters]
```

### src/entrypoints/background/agents/index.ts

```typescript
export * from './agent';
export * from './lettaAgent';
export * from './anthropicAgent';
```

### wxt-env.d.ts

```typescript
/// <reference types="svelte" />

```

### wxt.config.ts

```typescript
import { defineConfig } from 'wxt';
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import tailwindcss from '@tailwindcss/vite';

// See https://wxt.dev/api/config.html
export default defineConfig({
  srcDir: 'src',
  modules: ['@wxt-dev/module-svelte'],
  manifest: {
    name: "Name",
    description: "My extension description",
    permissions: ["storage"],
    web_accessible_resources: [
      {
        resources: ["dashboard.html"],
        matches: ["*://*/*"]
      }
    ],
    // entrypoints: {
    //   // Existing entry points
    //   background: 'src/entrypoints/background.ts',
    //   popup: 'src/entrypoints/popup/popup.ts',
    //   dashboard: 'src/entrypoints/dashboard/dashboard.ts',
    //   // Updated content script entry point
    //   content: 'src/entrypoints/content/content.ts'
    // },
  },
  vite: () => ({
    plugins: [
      wasm(),
      topLevelAwait(),
      tailwindcss()
    ],
  }),

  webExt: {
    // startUrls: ['https://www.nbcnews.com/news/us-news/minnesota-lobbyist-arrested-allegedly-sending-threatening-texts-rcna214283']
    // startUrls: ['https://www.google.com/search?q=svelte+&sca_esv=bceb17660d299197&sxsrf=AE3TifPTf4yzQBFL2e4CB2J9kR_SevQN8A%3A1750560224977&ei=4G1XaOu4O_6U5OMP2fO1iQw&ved=0ahUKEwjrqPiCgYSOAxV-CnkGHdl5LcEQ4dUDCBA&uact=5&oq=svelte+&gs_lp=Egxnd3Mtd2l6LXNlcnAiB3N2ZWx0ZSAyBBAjGCcyChAjGIAEGCcYigUyCxAAGIAEGJECGIoFMgoQABiABBgUGIcCMgsQABiABBiRAhiKBTILEAAYgAQYsQMYgwEyCxAAGIAEGJECGIoFMgoQABiABBgUGIcCMgsQABiABBiRAhiKBTILEAAYgAQYsQMYgwFIpQtQ_QZY6AhwAngBkAEAmAHdAaAB9gKqAQUwLjEuMbgBA8gBAPgBAZgCBKAC_wLCAgoQABiwAxjWBBhHwgIGEAAYFhgewgILEAAYgAQYhgMYigXCAggQABiABBiiBMICBRAAGO8FmAMAiAYBkAYIkgcFMi4xLjGgB6gOsgcFMC4xLjG4B_sCwgcDMC40yAcH&sclient=gws-wiz-serp']
    startUrls: ['https://www.unz.com/article/was-coronavirus-a-biowarfare-attack-against-china/']
    // startUrls: ['https://kirschsubstack.com/p/should-you-get-any-vaccines-the-data']
  }
});

```

### src/app.css

```css
@import "tailwindcss";
@source "**/*.{html,js,svelte,ts,tsx}";
@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";

@theme {
  --color-neon-pink: #f535aa;
  --color-neon-blue: #05d9ff;
}

/* Base
*************************************************/
@layer base {
  /* * {
    @apply outline-neon-pink outline-3 border-neon-blue border-4;
  } */
}

```

### src/utils/messaging.ts

```typescript
import { defineExtensionMessaging, GetDataType } from '@webext-core/messaging';
import { defineWindowMessaging } from '@webext-core/messaging/page';
import { AgentResult } from './types';

interface ProtocolMap {
    // TODO: add api schema here
    activate(dom: string): AgentResult[];
    // highlight(selector: string): void;
}

// NOTE: this is to send messages between the background script and the content script
export const extensionMessenger = defineExtensionMessaging<ProtocolMap>();

// NOTE: this is to send messages between the injected script and the content script
export const websiteMessenger = defineWindowMessaging<ProtocolMap>({
    namespace: 'website-messenger',
});
```

[11 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]