7 Commits

Author SHA1 Message Date
Noah Masur
be6d6b0d35 fix add-on manifest 2026-01-31 11:53:43 -05:00
Noah Masur
0239a9925c try to prevent unnecessary firefox rebuidl 2026-01-31 11:53:43 -05:00
Noah Masur
49e35403b6 adjust daily summary to use browser extension for history 2026-01-31 11:53:43 -05:00
Noah Masur
430b522c61 enable daily browser summary on darwin 2026-01-31 11:53:43 -05:00
Noah Masur
a64488093c fix current build on darwin 2026-01-31 11:53:43 -05:00
Noah Masur
54d2376437 add alabaster theme to helix 2026-01-31 11:53:43 -05:00
Noah Masur
cd0a5d5de0 fix vertical pane navigation in zellij 2026-01-31 11:53:30 -05:00
13 changed files with 524 additions and 4 deletions

5
overlays/firefox.nix Normal file
View File

@@ -0,0 +1,5 @@
inputs: final: prev: {
firefox-unwrapped = final.stable.firefox-unwrapped;
}

View File

@@ -0,0 +1,41 @@
function exportHistory() {
const now = new Date();
const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0); // Beginning of today
browser.history.search({
text: '',
startTime: startTime,
endTime: now,
maxResults: 10000
}).then(historyItems => {
const historyData = JSON.stringify(historyItems, null, 2);
const blob = new Blob([historyData], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const date = now.toISOString().slice(0, 10); // YYYY-MM-DD
const filename = `firefox-history/history-${date}.json`;
browser.downloads.download({
url: url,
filename: filename,
conflictAction: 'overwrite',
saveAs: false
});
});
}
browser.alarms.create('daily-export', {
periodInMinutes: 60 // every 1 hour
});
browser.alarms.onAlarm.addListener(alarm => {
if (alarm.name === 'daily-export') {
exportHistory();
}
});
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.command === "exportHistory") {
exportHistory();
}
});

View File

@@ -0,0 +1,25 @@
{
"manifest_version": 3,
"name": "History Exporter",
"version": "1.0",
"description": "Automatically exports today's browsing history.",
"permissions": [
"history",
"downloads",
"alarms"
],
"background": {
"scripts": ["background.js"]
},
"action": {
"default_popup": "popup.html"
},
"browser_specific_settings": {
"gecko": {
"id": "firefox-history-exporter@nmasur.com",
"data_collection_permissions": {
"required": ["none"]
}
}
}
}

View File

@@ -0,0 +1,26 @@
{ pkgs, ... }:
pkgs.stdenv.mkDerivation rec {
pname = "firefox-history-exporter";
version = "1.0";
src = ./.;
nativeBuildInputs = [ pkgs.zip ];
dontUnpack = true;
installPhase = ''
dst="$out/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"
mkdir -p "$dst"
zip -j "$dst/firefox-history-exporter@nmasur.com.xpi" \
"${src}/manifest.json" \
"${src}/background.js" \
"${src}/popup.html" \
"${src}/popup.js"
'';
meta = with pkgs.lib; {
description = "Automatically exports today's browsing history.";
license = licenses.mit;
};
}

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>History Exporter</title>
<style>
body {
width: 200px;
text-align: center;
font-family: sans-serif;
}
button {
margin-top: 10px;
padding: 10px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>History Exporter</h1>
<button id="export-button">Export Now</button>
<p id="status"></p>
<script src="popup.js"></script>
</body>
</html>

View File

@@ -0,0 +1,9 @@
document.getElementById('export-button').addEventListener('click', () => {
browser.runtime.sendMessage({command: "exportHistory"});
const statusElement = document.getElementById('status');
statusElement.textContent = 'Exporting...';
setTimeout(() => {
statusElement.textContent = 'Export complete!';
}, 2000);
});

View File

@@ -46,6 +46,8 @@ in
ublacklist ublacklist
vimium vimium
wappalyzer # TODO: only for work profile wappalyzer # TODO: only for work profile
pkgs.nmasur.firefox-history-exporter
# copy-as-markdown
# saml-tracer # saml-tracer
# text-fragment # text-fragment
]; ];
@@ -61,6 +63,7 @@ in
"trailhead.firstrun.didSeeAboutWelcome" = true; # Disable welcome splash "trailhead.firstrun.didSeeAboutWelcome" = true; # Disable welcome splash
"dom.forms.autocomplete.formautofill" = false; # Disable autofill "dom.forms.autocomplete.formautofill" = false; # Disable autofill
"extensions.formautofill.creditCards.enabled" = false; # Disable credit cards "extensions.formautofill.creditCards.enabled" = false; # Disable credit cards
"extensions.autoDisableScopes" = false; # Enable extensions automatically
"dom.payments.defaults.saveAddress" = false; # Disable address save "dom.payments.defaults.saveAddress" = false; # Disable address save
"general.autoScroll" = true; # Drag middle-mouse to scroll "general.autoScroll" = true; # Drag middle-mouse to scroll
"services.sync.prefs.sync.general.autoScroll" = false; # Prevent disabling autoscroll "services.sync.prefs.sync.general.autoScroll" = false; # Prevent disabling autoscroll
@@ -187,7 +190,7 @@ in
xsession.windowManager.i3.config.keybindings = lib.mkIf pkgs.stdenv.isLinux { xsession.windowManager.i3.config.keybindings = lib.mkIf pkgs.stdenv.isLinux {
"${config.xsession.windowManager.i3.config.modifier}+Shift+b" = "exec ${ "${config.xsession.windowManager.i3.config.modifier}+Shift+b" = "exec ${
# Don't name the script `firefox` or it will affect grep # Don't name the script `firefox` or it will affect grep
builtins.toString ( toString (
pkgs.writeShellScript "focus-ff.sh" '' pkgs.writeShellScript "focus-ff.sh" ''
count=$(ps aux | grep -c firefox) count=$(ps aux | grep -c firefox)
if [ "$count" -eq 1 ]; then if [ "$count" -eq 1 ]; then

View File

@@ -22,7 +22,6 @@ in
extensions = [ extensions = [
pkgs.nmasur.gh-collaborators pkgs.nmasur.gh-collaborators
pkgs.gh-dash pkgs.gh-dash
pkgs.gh-copilot
]; ];
}; };

View File

@@ -347,7 +347,7 @@ in
}; };
themes."${config.programs.helix.settings.theme}" = { themes.base16 = {
"attributes" = config.theme.colors.base09; "attributes" = config.theme.colors.base09;
"comment" = { "comment" = {
fg = config.theme.colors.base03; fg = config.theme.colors.base03;
@@ -527,6 +527,197 @@ in
}; };
}; };
themes.alabaster-style = {
"attribute" = config.theme.colors.base05;
"comment" = {
fg = config.theme.colors.base0A;
# modifiers = [ "italic" ];
};
"constant" = config.theme.colors.base0E;
"constant.numeric" = config.theme.colors.base0E;
"constant.builtin" = config.theme.colors.base0E;
"constant.character" = config.theme.colors.base0E;
"constant.character.escape" = config.theme.colors.base0C;
"constructor" = config.theme.colors.base0D;
"debug" = config.theme.colors.base03;
"diagnostic" = {
modifiers = [ "underlined" ];
};
"diff.delta" = config.theme.colors.base09;
"diff.minus" = config.theme.colors.base08;
"diff.plus" = config.theme.colors.base0B;
"error" = config.theme.colors.base08;
"function" = config.theme.colors.base0D;
"hint" = config.theme.colors.base03;
"info" = config.theme.colors.base0D;
"keyword" = config.theme.colors.base05;
"keyword.control" = config.theme.colors.base05;
"keyword.operator" = config.theme.colors.base05;
"label" = config.theme.colors.base0E;
"namespace" = config.theme.colors.base0E;
"operator" = config.theme.colors.base05;
"punctuation" = config.theme.colors.base04;
"punctuation.bracket" = config.theme.colors.base04;
"punctuation.delimiter" = config.theme.colors.base04;
"special" = config.theme.colors.base0D;
"string" = config.theme.colors.base0B;
"string.regexp" = config.theme.colors.base0B;
"string.special" = config.theme.colors.base0C;
"type" = config.theme.colors.base0A;
"variable" = config.theme.colors.base05;
"variable.parameter" = config.theme.colors.base05;
"variable.builtin" = config.theme.colors.base05;
"variable.other.member" = config.theme.colors.base05;
"warning" = config.theme.colors.base09;
"markup.bold" = {
fg = config.theme.colors.base0A;
modifiers = [ "bold" ];
};
"markup.heading" = config.theme.colors.base0D;
"markup.italic" = {
fg = config.theme.colors.base0E;
modifiers = [ "italic" ];
};
"markup.link.text" = config.theme.colors.base08;
"markup.link.url" = {
fg = config.theme.colors.base09;
modifiers = [ "underlined" ];
};
"markup.list" = config.theme.colors.base08;
"markup.quote" = config.theme.colors.base0C;
"markup.raw" = config.theme.colors.base0B;
"markup.strikethrough" = {
modifiers = [ "crossed_out" ];
};
"diagnostic.hint" = {
underline = {
style = "curl";
};
};
"diagnostic.info" = {
underline = {
style = "curl";
};
};
"diagnostic.warning" = {
underline = {
style = "curl";
};
};
"diagnostic.error" = {
underline = {
style = "curl";
};
};
"ui.background" = {
bg = config.theme.colors.base00;
};
"ui.bufferline.active" = {
fg = config.theme.colors.base00;
bg = config.theme.colors.base03;
modifiers = [ "bold" ];
};
"ui.bufferline" = {
fg = config.theme.colors.base04;
bg = config.theme.colors.base00;
};
"ui.cursor" = {
fg = config.theme.colors.base04;
modifiers = [ "reversed" ];
};
"ui.cursor.insert" = {
fg = config.theme.colors.base0A;
modifiers = [ "reversed" ];
};
"ui.cursorline.primary" = {
fg = config.theme.colors.base05;
bg = config.theme.colors.base01;
};
"ui.cursor.match" = {
fg = config.theme.colors.base03;
modifiers = [ "reversed" ];
};
"ui.cursor.select" = {
fg = config.theme.colors.base04;
modifiers = [ "reversed" ];
};
"ui.gutter" = {
bg = config.theme.colors.base00;
};
"ui.help" = {
fg = config.theme.colors.base06;
bg = config.theme.colors.base01;
};
"ui.linenr" = {
fg = config.theme.colors.base03;
bg = config.theme.colors.base00;
};
"ui.linenr.selected" = {
fg = config.theme.colors.base04;
bg = config.theme.colors.base01;
modifiers = [ "bold" ];
};
"ui.menu" = {
fg = config.theme.colors.base05;
bg = config.theme.colors.base01;
};
"ui.menu.scroll" = {
fg = config.theme.colors.base03;
bg = config.theme.colors.base01;
};
"ui.menu.selected" = {
fg = config.theme.colors.base01;
bg = config.theme.colors.base04;
};
"ui.popup" = {
bg = config.theme.colors.base01;
};
"ui.selection" = {
bg = config.theme.colors.base01;
};
"ui.selection.primary" = {
bg = config.theme.colors.base02;
};
"ui.statusline" = {
fg = config.theme.colors.base04;
bg = config.theme.colors.base01;
};
"ui.statusline.inactive" = {
bg = config.theme.colors.base01;
fg = config.theme.colors.base03;
};
"ui.statusline.insert" = {
fg = config.theme.colors.base00;
bg = config.theme.colors.base0B;
};
"ui.statusline.normal" = {
fg = config.theme.colors.base00;
bg = config.theme.colors.base03;
};
"ui.statusline.select" = {
fg = config.theme.colors.base00;
bg = config.theme.colors.base0F;
};
"ui.text" = config.theme.colors.base05;
"ui.text.focus" = config.theme.colors.base05;
"ui.virtual.indent-guide" = {
fg = config.theme.colors.base03;
};
"ui.virtual.inlay-hint" = {
fg = config.theme.colors.base03;
};
"ui.virtual.ruler" = {
bg = config.theme.colors.base01;
};
"ui.virtual.jump-label" = {
fg = config.theme.colors.base0A;
modifiers = [ "bold" ];
};
"ui.window" = {
bg = config.theme.colors.base01;
};
};
}; };
# Create a desktop option for launching Helix from a file manager # Create a desktop option for launching Helix from a file manager

View File

@@ -128,7 +128,7 @@ in
if pkgs.stdenv.isDarwin then if pkgs.stdenv.isDarwin then
[ [
"env" "env"
"PATH=${config.home.homeDirectory}/.nix-profile/bin:/usr/bin:/bin" "PATH=/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin"
(lib.getExe zellij-switch-to-last) (lib.getExe zellij-switch-to-last)
] ]
else else
@@ -271,6 +271,16 @@ in
_args = [ "Left" ]; _args = [ "Left" ];
}; };
}; };
"bind \"Alt j\"" = {
MoveFocus = {
_args = [ "Down" ];
};
};
"bind \"Alt k\"" = {
MoveFocus = {
_args = [ "Up" ];
};
};
}; };
}; };

View File

@@ -0,0 +1,70 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (config.nmasur.settings) username;
cfg = config.nmasur.presets.services.daily-summary;
# Remove process urls in favor of using extention
# process_urls = pkgs.writers.writePython3Bin "process-urls" {
# libraries = [
# pkgs.python3Packages.requests
# pkgs.python3Packages.beautifulsoup4
# ];
# } (builtins.readFile ./process-urls.py);
# prompt = "Based on my browser usage for today from the markdown file located in /Users/${username}/Downloads/Sidebery/todays_urls.md, create or update a daily summary markdown file in the generated notes directory located in /Users/${username}/dev/personal/notes/generated/ with the filename format 'YYYY-MM-DD Daily Summary.md'. The resulting markdown file should use /Users/${username}/dev/personal/notes/templates/generated-summary.md as a format template, and it should summarize where I have spent my time today and highlight any notable links that I have visited. Please create markdown links to other relevant notes in /Users/${username}/dev/personal/notes/. If there is an existing markdown file for today, update it to include the newest information.";
prompt = "Based on my browser usage for today from the JSON file located in /Users/${username}/Downloads/firefox-history/history-YYYY-MM-DD.json, create or update a daily summary markdown file in the generated notes directory located in /Users/${username}/dev/personal/notes/generated/ with the filename format 'YYYY-MM-DD Daily Summary.md'. The resulting markdown file should use /Users/${username}/dev/personal/notes/templates/generated-summary.md as a format template, and it should summarize where I have spent my time today and highlight any notable pages that I have visited, using the titles of each URL in the JSON file for markdown links. Please create markdown links to other relevant notes in /Users/${username}/dev/personal/notes/ and explain why they are being referenced. If there is an existing markdown file for today, update it to include the newest information.";
in
{
options.nmasur.presets.services.daily-summary.enable = lib.mkEnableOption "Daily work summary";
config = lib.mkIf cfg.enable {
launchd.user.agents.daily-summary = {
# This replaces program and args entirely
# script = ''
# ${process_urls}/bin/process-urls /Users/${username}/Downloads/Sidebery/
# GEMINI_API_KEY=$(cat /Users/${username}/.config/gemini/.gemini_api_key) ${pkgs.gemini-cli}/bin/gemini --allowed-tools all --yolo --include-directories /Users/${username}/Downloads/Sidebery/ --include-directories /Users/${username}/dev/personal/notes/ "${prompt}"
# '';
script = ''
GEMINI_API_KEY=$(cat /Users/${username}/.config/gemini/.gemini_api_key) ${pkgs.gemini-cli}/bin/gemini --allowed-tools all --yolo --include-directories /Users/${username}/Downloads/firefox-history/ --include-directories /Users/${username}/dev/personal/notes/ "${prompt}"
'';
path = [
pkgs.bash
pkgs.coreutils
];
serviceConfig = {
Label = "com.example.daily-summary";
# Runs the script through /bin/sh automatically
# RunAtLoad = true;
StartCalendarInterval = [
{
Hour = 4;
Minute = 45;
}
{
Hour = 6;
Minute = 0;
}
{
Hour = 9;
Minute = 0;
}
{
Hour = 11;
Minute = 0;
}
];
};
};
};
}

View File

@@ -0,0 +1,115 @@
# Temporarily disabled in favor of using an extension to save history
import requests
from bs4 import BeautifulSoup
import json
import os
import sys
from datetime import datetime
from urllib.parse import urlparse
def find_urls(data):
urls = []
if isinstance(data, dict):
for key, value in data.items():
if key == 'url' and isinstance(value, str):
urls.append(value)
else:
urls.extend(find_urls(value))
elif isinstance(data, list):
for item in data:
urls.extend(find_urls(item))
return urls
def main():
if len(sys.argv) > 1:
base_dir = sys.argv[1]
if not os.path.isdir(base_dir):
print(f"Error: Directory '{base_dir}' not found.")
sys.exit(1)
else:
base_dir = '.'
today = datetime.now().strftime('%Y.%m.%d')
output_filename = 'todays_urls.md'
output_filepath = os.path.join(base_dir, output_filename)
url_titles = {}
print(f"Searching for files in '{base_dir}' "
f"starting with 'snapshot-{today}-'")
with open(output_filepath, 'w') as md_file:
md_file.write(f'# URLs from Sidebery Snapshots for '
f'{today.replace(".", "-")}\n\n')
files_processed = 0
for filename in sorted(os.listdir(base_dir)):
# Debugging print removed
if (filename.startswith(f'snapshot-{today}-')
and filename.endswith('.json')):
files_processed += 1
print(f"Processing file: "
f"{os.path.join(base_dir, filename)}")
# Extract and format date and time from filename
# Example: snapshot-2026.01.25-13.19.29.json
clean_filename = (filename.replace('snapshot-', '')
.replace('.json', ''))
date_time_parts = clean_filename.split('-', 1)
formatted_date = date_time_parts[0].replace('.', '-')
formatted_time = date_time_parts[1].replace('.', ':')
datetime_str = f"{formatted_date} {formatted_time}"
md_file.write(f'## {datetime_str}\n\n')
with open(os.path.join(base_dir, filename), 'r') as json_file:
try:
data = json.load(json_file)
urls = find_urls(data)
print(f" Found {len(urls)} URLs")
for url in urls:
if url not in url_titles:
try:
# Get title of URL
res = requests.get(
url,
timeout=10,
allow_redirects=True
)
soup = BeautifulSoup(
res.text,
'html.parser'
)
if soup.title and soup.title.string:
title = soup.title.string.strip()
else:
domain = urlparse(url).netloc
title = domain if domain else url
url_titles[url] = title
except requests.exceptions.InvalidSchema:
continue
except Exception:
domain = urlparse(url).netloc
title = domain if domain else url
url_titles[url] = title
if url in url_titles:
title = url_titles[url]
md_file.write(f'- [{title}]({url})\n')
md_file.write('\n')
except json.JSONDecodeError:
print(f" Error decoding JSON in {filename}")
md_file.write('- Error decoding JSON\n\n')
if files_processed == 0:
print("No files found for today.")
print(f"Processing complete. Output written to {output_filepath}")
if __name__ == '__main__':
main()

View File

@@ -22,6 +22,7 @@ in
homebrew.enable = lib.mkDefault true; homebrew.enable = lib.mkDefault true;
}; };
services = { services = {
daily-summary.enable = lib.mkDefault true;
dock.enable = lib.mkDefault true; dock.enable = lib.mkDefault true;
finder.enable = lib.mkDefault true; finder.enable = lib.mkDefault true;
hammerspoon.enable = lib.mkDefault true; hammerspoon.enable = lib.mkDefault true;