Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@ npm install # Install dependencies
npm run build # Build for Firefox (default)
npm run build:firefox # Build for Firefox
npm run build:chrome # Build for Chrome
npm run build:safari # Build for Safari
npm run dev # Run in Firefox Developer Edition
npm run watch:chrome # Watch mode for Chrome development
npm run test # Run unit tests (vitest)
npm run lint # ESLint check
npm run build:types # TypeScript type check
npm run package # Package for Firefox (web-ext-artifacts/)
npm run package:chrome # Package for Chrome (ZIP)
npm run package:safari # Build for Safari + run Apple's Safari Web Extension packager (outputs to web-ext-artifacts/safari/)
```

## Architecture

**Cross-browser extension (Manifest V3)** for Firefox and Chrome that uses Fetch Proxy to trim ChatGPT conversations before React renders.
**Cross-browser extension (Manifest V3)** for Firefox, Chrome, and Safari that uses Fetch Proxy to trim ChatGPT conversations before React renders.

### Core Components

Expand Down Expand Up @@ -60,17 +62,18 @@ LightSession Pro counts **messages** (role changes) instead of nodes:

```
extension/
├── manifest.json # Symlink → manifest.firefox.json (or chrome copy)
├── manifest.json # Symlink → manifest.firefox.json (or chrome/safari copy)
├── manifest.firefox.json # Firefox-specific manifest
├── manifest.chrome.json # Chrome-specific manifest
├── manifest.safari.json # Safari-specific manifest (MV3, no declarativeContent)
└── src/
├── page/ # Page script (Fetch Proxy, runs in page context)
├── content/ # Content scripts (settings, status bar)
├── background/ # Background service worker
├── popup/ # Popup HTML/CSS/TS
└── shared/ # Types, constants, storage, logger
tests/ # Unit tests (vitest + happy-dom)
build.cjs # esbuild build script (supports --target=firefox|chrome)
build.cjs # esbuild build script (supports --target=firefox|chrome|safari)
```

## Conventions
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ Built after too many coding sessions where a single ChatGPT tab would start eati

**[Install from Chrome Web Store](https://chromewebstore.google.com/detail/lightsession-pro/cenmillohidhjjjjiocmddkgmlonaigp)**

### Safari

Currently manual install only.

### After installation

1. Open any ChatGPT conversation.
Expand All @@ -84,6 +88,9 @@ npm run build:firefox

# Build for Chrome
npm run build:chrome

# Build for Safari
npm run build:safari
```

**Firefox:**
Expand All @@ -97,6 +104,14 @@ npm run build:chrome
3. Click **Load unpacked**
4. Select the `extension/` folder

**Safari:**
1. Run `npm run build:safari`
2. Open **Safari > Settings > Developer**
3. Click **Add Temporary Extension...**
4. Select the `extension/` folder

Temporary Safari extensions are for local testing/use. Safari may remove them after 24 hours or when Safari quits, so repeat the last steps after rebuilding or restarting Safari.

---

## 🚀 Usage
Expand Down Expand Up @@ -177,7 +192,7 @@ Trimming only affects what the browser renders. The conversation itself remains

- Node.js >= 24.10.0 (see `.node-version`)
- npm >= 10
- Firefox >= 115 or Chrome >= 120
- Firefox >= 115, Chrome >= 120, or Safari >= 15.4

### Scripts

Expand All @@ -188,6 +203,7 @@ npm install # Install dependencies
npm run build # Build for Firefox (default)
npm run build:firefox # Build for Firefox
npm run build:chrome # Build for Chrome
npm run build:safari # Build for Safari

# Development
npm run dev # Run in Firefox Developer Edition
Expand All @@ -201,6 +217,7 @@ npm run build:types # Type check
# Package
npm run package # Package for Firefox (web-ext-artifacts/)
npm run package:chrome # Package for Chrome (ZIP)
npm run package:safari # Package for Safari with Xcode tooling (optional)
```

### Project structure
Expand All @@ -217,6 +234,7 @@ extension/
├── icons/ # Extension icons
├── manifest.firefox.json # Firefox manifest (MV3)
├── manifest.chrome.json # Chrome manifest (MV3)
├── manifest.safari.json # Safari manifest (MV3)
└── manifest.json # Active manifest (symlink/copy from build)
```

Expand All @@ -231,7 +249,7 @@ extension/

## 🌐 Compatibility

- **Browsers:** Firefox >= 115, Chrome >= 120 (Manifest V3)
- **Browsers:** Firefox >= 115, Chrome >= 120, Safari >= 15.4 (Manifest V3)
- **OS:** Windows, macOS, Linux
- **ChatGPT:** Optimized for the current UI (2025–2026), resilient to small layout changes

Expand Down
9 changes: 5 additions & 4 deletions build.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* node build.cjs - Development build for Firefox (default)
* node build.cjs --target=firefox - Build for Firefox
* node build.cjs --target=chrome - Build for Chrome
* node build.cjs --target=safari - Build for Safari
* node build.cjs --watch - Watch mode for development
* NODE_ENV=production node build.cjs - Production build (minified, no sourcemaps)
*/
Expand All @@ -18,10 +19,10 @@ const path = require('path');
const isWatch = process.argv.includes('--watch');
const isProduction = process.env.NODE_ENV === 'production';

// Parse --target=firefox|chrome (default: firefox)
// Parse --target=firefox|chrome|safari (default: firefox)
const targetArg = process.argv.find((arg) => arg.startsWith('--target='));
const target = targetArg ? targetArg.split('=')[1] : 'firefox';
const validTargets = ['firefox', 'chrome'];
const validTargets = ['firefox', 'chrome', 'safari'];
if (!validTargets.includes(target)) {
console.error(`❌ Invalid target: ${target}. Use: ${validTargets.join(', ')}`);
process.exit(1);
Expand All @@ -39,8 +40,8 @@ function copyManifest() {
fs.unlinkSync(manifestDest);
}

if (target === 'chrome') {
// For Chrome, copy manifest.chrome.json
if (target === 'chrome' || target === 'safari') {
// For Chrome/Safari, copy the target-specific manifest
fs.copyFileSync(manifestSrc, manifestDest);
console.log(`✓ Copied manifest.${target}.json → manifest.json`);
} else {
Expand Down
53 changes: 53 additions & 0 deletions extension/manifest.safari.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"manifest_version": 3,
"name": "LightSession Pro for ChatGPT",
"version": "1.7.4",
"description": "Keep ChatGPT fast by keeping only the last N messages in the DOM. Local-only.",
"icons": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"48": "icons/icon-48.png",
"128": "icons/icon-128.png"
},
"action": {
"default_title": "LightSession Pro",
"default_popup": "popup/popup.html"
},
"permissions": ["storage", "tabs"],
"host_permissions": [
"*://chat.openai.com/*",
"*://chatgpt.com/*"
],
"content_scripts": [
{
"matches": [
"*://chat.openai.com/*",
"*://chatgpt.com/*"
],
"js": ["dist/page-inject.js"],
"run_at": "document_start"
},
{
"matches": [
"*://chat.openai.com/*",
"*://chatgpt.com/*"
],
"js": ["dist/content.js"],
"run_at": "document_idle"
}
],
"background": {
"service_worker": "dist/background.js"
},
"web_accessible_resources": [
{
"resources": ["dist/page-script.js", ".dev"],
"matches": ["*://chat.openai.com/*", "*://chatgpt.com/*"]
}
],
"browser_specific_settings": {
"safari": {
"strict_min_version": "15.4"
}
}
}
1 change: 1 addition & 0 deletions extension/src/shared/browser-polyfill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Cross-browser compatibility layer for WebExtension APIs.
* - Firefox: uses global `browser` object (Promise-based)
* - Chrome: uses global `chrome` object (callback-based, but MV3 supports Promises)
* - Safari: Web Extensions API (browser/chrome); MV3 from Safari 15.4+
*
* Modern Chrome (MV3) supports Promise-based APIs similar to Firefox,
* so we can use `chrome` directly as a drop-in replacement for `browser`.
Expand Down
56 changes: 56 additions & 0 deletions package-safari.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env node
/**
* Package the built extension as a Safari Web Extension Xcode project.
*
* Apple renamed safari-web-extension-converter to safari-web-extension-packager.
* Prefer the current tool name, but keep the old name as a fallback for older
* Xcode installs.
*/

const { spawnSync } = require('child_process');

const TOOLS = ['safari-web-extension-packager', 'safari-web-extension-converter'];
const args = [
'extension',
'--project-location',
'web-ext-artifacts/safari',
'--app-name',
'LightSession Pro',
'--bundle-identifier',
'com.lightsession.pro',
'--macos-only',
'--swift',
'--copy-resources',
'--no-open',
'--no-prompt',
'--force',
];

function findTool() {
for (const tool of TOOLS) {
const result = spawnSync('xcrun', ['--find', tool], { stdio: 'ignore' });
if (result.status === 0) {
return tool;
}
}

return undefined;
}

const tool = findTool();

if (!tool) {
console.error(
'Safari packaging requires full Xcode with safari-web-extension-packager installed.'
);
process.exit(1);
}

const result = spawnSync('xcrun', [tool, ...args], { stdio: 'inherit' });

if (result.error) {
console.error(result.error.message);
process.exit(1);
}

process.exit(result.status ?? 1);
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
"build": "node build.cjs",
"build:firefox": "node build.cjs --target=firefox",
"build:chrome": "node build.cjs --target=chrome",
"build:safari": "node build.cjs --target=safari",
"build:types": "tsc --noEmit",
"build:prod": "rm -f extension/.dev && NODE_ENV=production node build.cjs",
"build:prod:firefox": "rm -f extension/.dev && NODE_ENV=production node build.cjs --target=firefox",
"build:prod:chrome": "rm -f extension/.dev && NODE_ENV=production node build.cjs --target=chrome",
"build:prod:safari": "rm -f extension/.dev && NODE_ENV=production node build.cjs --target=safari",
"watch": "node build.cjs --watch",
"watch:chrome": "node build.cjs --target=chrome --watch",
"lint": "eslint extension/src",
Expand All @@ -27,6 +29,7 @@
"dev:stable": "npm run build:firefox && web-ext run --source-dir=extension --firefox=firefox --start-url='https://chat.openai.com'",
"package": "npm run build:prod:firefox && web-ext build --source-dir=extension --artifacts-dir=web-ext-artifacts",
"package:chrome": "npm run build:prod:chrome && cd extension && zip -r ../web-ext-artifacts/lightsession-chrome.zip . -x '*.ts' -x 'src/*' -x 'manifest.*.json'",
"package:safari": "npm run build:prod:safari && node package-safari.cjs",
"clean": "rm -rf extension/dist extension/popup/popup.js web-ext-artifacts"
},
"keywords": [
Expand Down
Loading