Skip to content

Latest commit

 

History

86 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Sietas — Phase 0 + 1 + 3 + 4

Executive inbox copilot. Built so far: the provider-agnostic brain (Phase 0), the triage engine (Phase 1), draft replies (Phase 3) and attachment classification (Phase 4). Triage classifies + summarizes an inbox and prints a report grouped by priority; optionally it drafts replies for messages that need one and classifies attachments. All exec-facing output is Lithuanian; code and comments are English. No Graph or Monday yet (Phase 2 / 5) — drafts and attachments currently run against offline fixtures.

Quick start

cd sietas
npm install

# Offline — no API key needed. Proves the pipeline + report end to end.
npm run triage:mock

# Offline, full pipeline: triage + draft replies + attachment classification.
npm run triage:full

# Live Claude. Put your key in .env first (cp .env.example .env).
ANTHROPIC_API_KEY=sk-ant-... npm run triage

# Flags (compose freely):
#   --drafts   draft a reply for emails that need one (Phase 3)
#   --files    classify attachments (Phase 4)
#   --json     machine-readable output for later phases
npm run triage:mock -- --drafts --files --json

Phase 3 — draft replies

--drafts makes the pipeline call draftReply (strong model) for every thread whose classification has needsReply: true. The draft is for human review, signed EXEC_NAME, and never sent. Tone/rules come from EXEC_RULES (free-form Lithuanian) and are injected into prompts/draft.lt.md.

Writing drafts back to Outlook

--write-drafts (requires --live) takes each generated draft and creates it as a reply draft in the mailbox's Drafts folder via Graph createReply + a body PATCH (createDraftReply, needs Mail.ReadWrite). The exec then reviews and sends it from Outlook. Sietas never sends — there is deliberately no Mail.Send call.

npm run triage:push   # = --live --drafts --files --write-drafts

Notes:

  • Safe by design: only creates drafts; it cannot send.
  • Guardrails (src/brain/risk.ts): a thread that looks legal, payment/invoice, commitment/signature, scam, or confidential is not drafted — it's flagged ⚠ Rankinė peržiūra with a reason. Keyword rules are easy to extend.
  • Idempotent: a draft already created for a message id is not recreated on re-runs (state in data/state.json, keyed by conversationId).
  • --dry-run: with --write-drafts, prints what would be created and writes nothing — use it before the first real run.
  • The draft body is plain text (the original is still quoted by createReply).

Phase 4 — attachment classification

--files makes the pipeline call classifyFile for each attachment it can resolve. Offline, attachments are text stand-ins in fixtures/files/ named <attachmentName>.txt; the loader (src/ingest/files.ts) also accepts real binaries (PDFs → base64) so live Claude vision drops in unchanged once Graph downloads real attachments (Phase 2). Output: document type, counterparty, status — all Lithuanian.

How the swap-proof brain works

Everything calls the LLMProvider interface (src/providers/types.ts); nothing else imports a vendor SDK. src/providers/factory.ts picks the implementation from LLM_PROVIDER:

LLM_PROVIDER Implementation Status
anthropic AnthropicProvider — cheap model classifies, strong model writes live
mock MockProvider — offline keyword heuristics live (dev/CI)
azure AzureOpenAIProvider stub until Phase 6

Honest caveat: method signatures are swap-proof; capabilities behind them are not. classifyFile uses native PDF/image vision today — an Azure swap will need a separate OCR step (Azure Document Intelligence). That divergence is contained to src/providers/azure.ts.

Two-tier models

Set in .env: ANTHROPIC_MODEL_CHEAP (classify/routing) and ANTHROPIC_MODEL_STRONG (summarize/draft/file). Mirrors the cost design.

Layout

src/providers/   types.ts (interface), anthropic.ts, azure.ts (stub), mock.ts, factory.ts
src/config/      env.ts, tenant.ts (TenantConfig — no hardcoding)
src/ingest/      fixtures.ts (emails; Graph mailReader drops in later), files.ts (attachments)
src/brain/       triage.ts (classify + summarize + optional draft + optional file classify)
src/prompts/     load.ts (prompt loader)
src/report.ts    console + JSON report
src/index.ts     runOnce() entrypoint (--drafts / --files / --json)
prompts/         classify.lt.md, summarize.lt.md, draft.lt.md, fileClassify.lt.md (versioned)
fixtures/        emails.json (11 Lithuanian sample emails; two share a conversationId = one thread)
fixtures/files/  text stand-ins for attachments (<name>.txt) — real PDFs drop in later

Demo — HTML morning briefing

--html writes a self-contained report.html (clean Lithuanian dashboard: priority cards, summaries, reply/thread badges, attachment tags, collapsible drafts) to the current folder. This is the exec-facing surface — open it in a browser, not the terminal.

npm run triage:demo    # = --live --drafts --files --html  (real inbox, OpenRouter)
npm run demo:mock      # offline preview from fixtures, no key

For the full "wow" moment, also push the drafts into Outlook once (npm run triage:push) so they're waiting in the exec's Drafts folder.

Phase 2 — live Outlook via Microsoft Graph

--live (or MAIL_SOURCE=graph) swaps the fixtures for a real read of the exec's mailbox using app-only (client-credentials) Graph auth — same EmailInput / FileInput shapes, so triage/drafts/files are unchanged. src/ingest/graph.ts fetches the inbox and downloads attachments as base64 (real PDFs now flow into Phase 4 vision). Reading needs Mail.Read; access must be scoped to the single mailbox with Exchange Application RBAC (legacy installs may still use an ApplicationAccessPolicy).

Setup is a checklist: see AZURE_SETUP.md (app registration, Exchange Application RBAC, positive/negative mailbox tests, secrets). Then:

npm run triage:live   # = --live --drafts --files

Hosted multi-tenant storage must use a non-owner Neon role with PostgreSQL RLS. See RLS_SETUP.md; migrations use DATABASE_URL_OWNER, while the app receives only the restricted DATABASE_URL.

One-time client links are emailed separately from their six-digit codes. SMTP setup is documented in LOGIN_EMAIL_SETUP.md.

What's done vs. what's next

Built: Phase 0 (brain), 1 (triage), 2 (Graph inbox + attachment download + threading + draft write-back), 3 (drafts), 4 (attachments). Threading groups messages by conversationId, summarizes the whole thread, and collapses repeated forwards into one entry. Drafts can be written back to Outlook's Drafts folder (--write-drafts, never sent). Remaining: sending (Mail.Send, deliberately skipped), Phase 5 (Monday), Phase 6 (Azure provider). See PROJECT_PLAN.md.

About

Executive inbox copilot: IMAP triage, AI summaries, draft replies and attachment classification. TypeScript, Next.js, Anthropic SDK

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages