Compare commits

..

3 Commits

Author SHA1 Message Date
af9e0a5a92 up
Signed-off-by: Christoph Schmatzler <christoph@schmatzler.com>
2026-03-01 21:37:04 +00:00
a25b1a4f20 inbox-triage: harden prompt with pagination, explicit commands, and tighter classification 2026-03-01 21:11:10 +00:00
e8f1d597e3 up
Signed-off-by: Christoph Schmatzler <christoph@schmatzler.com>
2026-03-01 21:01:22 +00:00
8 changed files with 41 additions and 182 deletions

101
flake.lock generated
View File

@@ -142,28 +142,6 @@
"type": "github" "type": "github"
} }
}, },
"fenix_2": {
"inputs": {
"nixpkgs": [
"neverest",
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src_2"
},
"locked": {
"lastModified": 1732405626,
"narHash": "sha256-uDbQrdOyqa2679kKPzoztMxesOV7DG2+FuX/TZdpxD0=",
"owner": "soywod",
"repo": "fenix",
"rev": "c7af381484169a78fb79a11652321ae80b0f92a6",
"type": "github"
},
"original": {
"owner": "soywod",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": { "flake-compat": {
"flake": false, "flake": false,
"locked": { "locked": {
@@ -397,26 +375,6 @@
"type": "github" "type": "github"
} }
}, },
"neverest": {
"inputs": {
"fenix": "fenix_2",
"nixpkgs": "nixpkgs_4",
"pimalaya": "pimalaya_2"
},
"locked": {
"lastModified": 1734595002,
"narHash": "sha256-MXQDy5b/ooSkISEVM9KxHeqkkhPimZJtX3j9jqTP5aU=",
"owner": "pimalaya",
"repo": "neverest",
"rev": "cc5f5214d3bea064ed059116ac81e40a803faa7e",
"type": "github"
},
"original": {
"owner": "pimalaya",
"repo": "neverest",
"type": "github"
}
},
"nix-github-actions": { "nix-github-actions": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -520,21 +478,6 @@
} }
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": {
"lastModified": 1732539489,
"narHash": "sha256-EPstM4aUuClDkFdoyno1CSRZIfGdCfZKw/XzMlQB0NY=",
"owner": "soywod",
"repo": "nixpkgs",
"rev": "87b010196489f96e4acdcc804ed3d096476e46ad",
"type": "github"
},
"original": {
"owner": "soywod",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": { "locked": {
"lastModified": 1772370856, "lastModified": 1772370856,
"narHash": "sha256-OXf9tB+s1EPCzIBR4eIQTm1dbbfVgjPNEGChgwEvvIU=", "narHash": "sha256-OXf9tB+s1EPCzIBR4eIQTm1dbbfVgjPNEGChgwEvvIU=",
@@ -550,7 +493,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_6": { "nixpkgs_5": {
"locked": { "locked": {
"lastModified": 1770380644, "lastModified": 1770380644,
"narHash": "sha256-P7dWMHRUWG5m4G+06jDyThXO7kwSk46C1kgjEWcybkE=", "narHash": "sha256-P7dWMHRUWG5m4G+06jDyThXO7kwSk46C1kgjEWcybkE=",
@@ -566,7 +509,7 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_7": { "nixpkgs_6": {
"locked": { "locked": {
"lastModified": 1765934234, "lastModified": 1765934234,
"narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=", "narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=",
@@ -585,7 +528,7 @@
"nixvim": { "nixvim": {
"inputs": { "inputs": {
"flake-parts": "flake-parts_2", "flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_6", "nixpkgs": "nixpkgs_5",
"systems": "systems_3" "systems": "systems_3"
}, },
"locked": { "locked": {
@@ -634,22 +577,6 @@
"type": "github" "type": "github"
} }
}, },
"pimalaya_2": {
"flake": false,
"locked": {
"lastModified": 1732869386,
"narHash": "sha256-83tDRVfSBVZgIp6WOb6r/T6cHfP7lNsK1MUTdkGMi14=",
"owner": "pimalaya",
"repo": "nix",
"rev": "be23e0deeb014c6be5232322b892c9bee25dee77",
"type": "github"
},
"original": {
"owner": "pimalaya",
"repo": "nix",
"type": "github"
}
},
"root": { "root": {
"inputs": { "inputs": {
"colmena": "colmena", "colmena": "colmena",
@@ -663,9 +590,8 @@
"jj-ryu": "jj-ryu", "jj-ryu": "jj-ryu",
"jj-starship": "jj-starship", "jj-starship": "jj-starship",
"llm-agents": "llm-agents", "llm-agents": "llm-agents",
"neverest": "neverest",
"nix-homebrew": "nix-homebrew", "nix-homebrew": "nix-homebrew",
"nixpkgs": "nixpkgs_5", "nixpkgs": "nixpkgs_4",
"nixvim": "nixvim", "nixvim": "nixvim",
"overseer": "overseer", "overseer": "overseer",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
@@ -689,23 +615,6 @@
"type": "github" "type": "github"
} }
}, },
"rust-analyzer-src_2": {
"flake": false,
"locked": {
"lastModified": 1732050317,
"narHash": "sha256-G5LUEOC4kvB/Xbkglv0Noi04HnCfryur7dVjzlHkgpI=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "c0bbbb3e5d7d1d1d60308c8270bfd5b250032bb4",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"rust-overlay": { "rust-overlay": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
@@ -848,7 +757,7 @@
"inputs": { "inputs": {
"crane": "crane", "crane": "crane",
"flake-utils": "flake-utils_3", "flake-utils": "flake-utils_3",
"nixpkgs": "nixpkgs_7", "nixpkgs": "nixpkgs_6",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {

View File

@@ -46,7 +46,6 @@
flake = false; flake = false;
}; };
himalaya.url = "github:pimalaya/himalaya"; himalaya.url = "github:pimalaya/himalaya";
neverest.url = "github:pimalaya/neverest";
}; };
outputs = inputs @ {flake-parts, ...}: outputs = inputs @ {flake-parts, ...}:

View File

@@ -30,7 +30,7 @@
../../profiles/fzf.nix ../../profiles/fzf.nix
../../profiles/git.nix ../../profiles/git.nix
../../profiles/himalaya.nix ../../profiles/himalaya.nix
../../profiles/neverest.nix ../../profiles/mbsync.nix
../../profiles/home.nix ../../profiles/home.nix
../../profiles/jjui.nix ../../profiles/jjui.nix
../../profiles/jujutsu.nix ../../profiles/jujutsu.nix

View File

@@ -1,3 +0,0 @@
{inputs}: final: prev: {
neverest = inputs.neverest.packages.${prev.stdenv.hostPlatform.system}.default;
}

20
profiles/mbsync.nix Normal file
View File

@@ -0,0 +1,20 @@
{...}: {
programs.mbsync.enable = true;
services.mbsync = {
enable = true;
frequency = "*:0/5";
};
accounts.email.accounts."christoph@schmatzler.com" = {
mbsync = {
enable = true;
create = "both";
expunge = "both";
};
imap = {
host = "imap.mail.me.com";
port = 993;
tls.enable = true;
};
};
}

View File

@@ -1,71 +0,0 @@
{
config,
pkgs,
lib,
...
}:
with lib; let
tomlFormat = pkgs.formats.toml {};
account = config.accounts.email.accounts."christoph@schmatzler.com";
maildirPath = account.maildir.absPath;
in {
home.packages = [pkgs.neverest];
xdg.configFile."neverest/config.toml".source =
tomlFormat.generate "neverest-config.toml" {
accounts."christoph@schmatzler.com" = {
default = true;
folder.filters = "all";
left = {
backend = {
type = "maildir";
root-dir = maildirPath;
};
folder = {
aliases = {
inbox = "INBOX";
drafts = "Drafts";
sent = "Sent Messages";
trash = "Deleted Messages";
};
permissions = {
create = true;
delete = true;
};
};
flag.permissions.update = true;
message.permissions = {
create = true;
delete = true;
};
};
right = {
backend = {
type = "imap";
host = "imap.mail.me.com";
port = 993;
encryption = "tls";
login = account.userName;
auth = {
type = "password";
cmd = concatStringsSep " " account.passwordCommand;
};
clients-pool-size = 8;
};
folder = {
aliases = {
inbox = "INBOX";
drafts = "Drafts";
sent = "Sent Messages";
trash = "Deleted Messages";
};
permissions.delete = false;
};
message.permissions.delete = false;
};
};
};
}

View File

@@ -21,6 +21,7 @@
COLORTERM = "truecolor"; COLORTERM = "truecolor";
COLORFGBG = "15;0"; COLORFGBG = "15;0";
TERM_BACKGROUND = "light"; TERM_BACKGROUND = "light";
EDITOR = "nvim";
}; };
extraEnv = extraEnv =

View File

@@ -26,29 +26,32 @@ Workflow:
- Do not create new workflows that route mail to `Archive`. - Do not create new workflows that route mail to `Archive`.
- Existing messages already in `Archive` may remain there unchanged. - Existing messages already in `Archive` may remain there unchanged.
4. Determine source folder: 4. Determine source folder:
- If `$ARGUMENTS` contains a folder name, use that as source. - If `$ARGUMENTS` is a single known folder name (matches a folder from step 1), use that as source.
- Otherwise use `INBOX`. - Otherwise use `INBOX`.
5. Build a run scope safely: 5. Build a run scope safely:
- List with fixed page size `20`: `himalaya envelope list -f "<source>" -p 1 -s 20`. - List with fixed page size `20` and JSON output: `himalaya envelope list -f "<source>" -p 1 -s 20 --output json`.
- Enumerate IDs in returned order. - Start at page `1`. Enumerate IDs in returned order.
- Process each ID fully before touching the next ID. - Process each ID fully before touching the next ID.
- After each single-ID action, relist page `1` with `-s 20` and continue with the next unprocessed ID. - After each single-ID action that moves or deletes a message, relist the current page with `-s 20 --output json` and continue with the next unprocessed ID.
- Keep an in-memory reviewed set for this run to avoid reprocessing IDs already handled or intentionally left untouched. - Keep an in-memory reviewed set for this run to avoid reprocessing IDs already handled or intentionally left untouched.
- Stop when a fresh page-1 listing contains no unprocessed IDs. - When all IDs on the current page are in the reviewed set, advance to the next page.
- Stop when a page returns fewer results than the page size (end of folder) and all its IDs are in the reviewed set.
6. For each single envelope ID, do all checks before any move/delete: 6. For each single envelope ID, do all checks before any move/delete:
- Read the message: `himalaya message read -f "<source>" <id>`. - Read the message: `himalaya message read -f "<source>" <id>`.
- If needed for classification, inspect attachments with `himalaya attachment download -f "<source>" <id>`. - If needed for classification, inspect attachments: `himalaya attachment download -f "<source>" <id> --dir /tmp/himalaya-triage`.
- If attachments are downloaded, inspect them and remove temporary local files after use. - If attachments are downloaded, inspect them and `rm` the downloaded files from `/tmp/himalaya-triage` after use.
- Move: `himalaya message move -f "<source>" <id> "<destination>"`.
- Delete: `himalaya message delete -f "<source>" <id>`.
7. Classification precedence (higher rule wins on conflict): 7. Classification precedence (higher rule wins on conflict):
- Human communication from an actual person: do not delete, do not move, leave untouched. - Human communication — a message with freeform natural-language content written by a human, not templated or autogenerated. When in doubt whether a message is human or automated, leave it untouched.
- Clearly ephemeral automated/system message (alerts, bot/status updates, OTP/2FA, password reset codes, login codes) with no archival value: move to `Deleted Messages`. - Clearly ephemeral automated/system message (alerts, bot/status updates, OTP/2FA, password reset codes, login codes) with no archival value: move to `Deleted Messages`.
- Payment transaction correspondence (actual charge/payment confirmations, receipts, failed-payment notices, provider payment events such as Klarna/PayPal/Stripe): move to `Payments`. - Payment transaction correspondence (actual charge/payment confirmations, receipts, failed-payment notices, provider payment events such as Klarna/PayPal/Stripe): move to `Payments`.
- Subscription renewal notifications (auto-renew reminders, "will renew soon", price-change notices without a concrete transaction) are operational alerts, not payment records: move to `Deleted Messages`. - Subscription renewal notifications (auto-renew reminders, "will renew soon", price-change notices without a concrete transaction) are operational alerts, not payment records: move to `Deleted Messages`.
- "Kontoauszug verfügbar/ist online" notifications are availability alerts, not payment records: move to `Deleted Messages`. - "Kontoauszug verfügbar/ist online" notifications are availability alerts, not payment records: move to `Deleted Messages`.
- Orders/invoices/business records: move to `Orders and Invoices`. - Orders/invoices/business records: move to `Orders and Invoices`.
- Shipping-only notifications: do not move to `Orders and Invoices` unless there is actual invoice/receipt/order-document value. - Shipping/tracking notifications (dispatch confirmations, carrier updates, delivery ETAs) without invoice or order-document value: move to `Deleted Messages`.
- Marketing/newsletters: move to `Newsletters and Marketing`. - Marketing/newsletters: move to `Newsletters and Marketing`.
- Delivery/submission confirmations: move to `Outgoing Shipments` when appropriate. - Delivery/submission confirmations for items you shipped outbound: move to `Outgoing Shipments`.
- Long-term but uncategorized messages: create a concise new folder and move there. - Long-term but uncategorized messages: create a concise new folder and move there.
8. Folder creation rule: 8. Folder creation rule:
- Create a new folder only if no existing folder fits and the message should be kept. - Create a new folder only if no existing folder fits and the message should be kept.
@@ -58,6 +61,7 @@ Workflow:
Execution rules: Execution rules:
- Never perform bulk operations. One message ID per `read`, `move`, `delete`, and attachment command. - Never perform bulk operations. One message ID per `read`, `move`, `delete`, and attachment command.
- Always use page size 20 for envelope listing (`-s 20`). - Always use page size 20 for envelope listing (`-s 20`).
- If any single-ID command fails, log the error and continue with the next unreviewed ID.
- Never skip reading message content before deciding. - Never skip reading message content before deciding.
- Keep decisions conservative: only route clearly ephemeral automated/system messages to `Deleted Messages`. - Keep decisions conservative: only route clearly ephemeral automated/system messages to `Deleted Messages`.
- Never move or delete human communications via automation. - Never move or delete human communications via automation.