From 7007c4aa9dbeee0dbf9479803936cb76509feadd Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Wed, 26 Aug 2026 11:54:32 -0400 Subject: [PATCH] docs: update README and SECURITY for Twenty CRM sync, add CSV templates and scripts --- README.md | 12 +++++++----- SECURITY.md | 2 +- templates/company.csv | 2 ++ templates/dashboard.csv | 2 ++ templates/note.csv | 2 ++ templates/opportunity.csv | 2 ++ templates/person.csv | 2 ++ templates/task.csv | 2 ++ templates/workflow.csv | 2 ++ 9 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 templates/company.csv create mode 100644 templates/dashboard.csv create mode 100644 templates/note.csv create mode 100644 templates/opportunity.csv create mode 100644 templates/person.csv create mode 100644 templates/task.csv create mode 100644 templates/workflow.csv diff --git a/README.md b/README.md index c1dbd62..07473ea 100755 --- a/README.md +++ b/README.md @@ -150,8 +150,8 @@ Postings are treated as untrusted input (the workflow follows no instructions em - **`/interview`** preps you for a scheduled interview on a tracked application. It builds a stage-specific prep pack from the application's archive (the exact posting, the CV and cover letter the interviewer actually read, feedback recorded from earlier rounds), researches the company and interviewers with a verify-before-use rule, maps likely questions to your STAR examples, and offers a mock interview following the roleplay protocol in `07-interview-prep.md`. Gaps get honest bridge answers, never invented experience. - **`/outcome`** records what happened to an application - interview stages, offers, rejections, silence. It archives the submitted CV, cover letter, and posting text into `documents/applications/_/`, keeps `outcome.md` in the format `/setup` Path A parses, and updates the tracker. It also owns the stretch before there is an outcome to record: `/outcome followup` surfaces open applications that have gone quiet (default 10 days), drafts a short channel-appropriate follow-up in your writing style using only claims from the materials you already submitted (drafts only, never sends; at most twice per application), and offers a thank-you note in the same turn an interview stage is recorded. Once a few applications resolve, it points you back to `/setup` to calibrate the fit framework from what actually got interviews. -- **`/notion-sync`** publishes a one-way, read-only view of the pipeline into a Notion database via the official Notion MCP server (OAuth, no API keys) - one row per ranked job plus every tracked application, with a write-once briefing page per row. The repo files stay the system of record: nothing syncs back, and documents sync as filenames only. Complements `/html-report`: that is the deep offline dashboard you regenerate at your desk; this is the glanceable live view from anywhere Notion runs (desktop, web, phone). -- **`/gmail-sync`** reads your Gmail (via the Gmail connector) for status signals on your open applications - interview invites, assessment links, offers, rejections - and proposes them as a batch for you to approve before anything is written to the tracker or `outcome.md`, citing the source email on every proposed change. Offers stop short of proposing `hired`/`offer_declined` since that's your call; conflicting or unmatched signals get flagged for a manual `/outcome` pass instead of guessed. +- **`/sync-twenty`** publishes a one-way, read-only view of the pipeline into a self-hosted [Twenty CRM](https://twenty.com) workspace via its MCP server (OAuth) - one Opportunity per ranked job plus every tracked application, each linked to an enriched Company record (domain, LinkedIn, HQ, revenue - sourced from the local postings database and verified web search). The repo files stay the system of record: nothing syncs back, and documents sync as filenames only. Twenty is the pipeline backend; `/html-report` remains the deep offline dashboard you regenerate at your desk. +- **`/gather-recent-context`** scans your Fastmail inbox (via the Fastmail MCP server) for conversations about active job loops - interview invites, assessments, scheduling, offers - classifies each thread, and syncs a dated summary note onto the matching Opportunity in Twenty via `tools/gather_recent_context.py`. Read-only against the mailbox, summaries only (never raw email bodies), idempotent by thread id. - **`/rank`** bridges `/scrape` and `/apply`: it batch-scores all newly scraped postings against the fit framework (parallel agents fetch each posting and score the five evaluation dimensions) and returns a ranked shortlist with honest per-job strengths and gaps. Deal-breakers veto, deadlines get urgency flags, dead postings get marked expired. Pick a number and it hands off to the full `/apply` workflow. - **`/expand`** enriches your profile by scanning public sources you've already linked in it (GitHub repos, portfolio site, Kaggle, Google Scholar) and looking up syllabi for named courses and certifications. Discovered competencies are added to your profile with a source tag. Useful right after `/setup` to surface skills that documents alone don't make explicit. - **`/upskill`** analyzes the gap between your profile, your tracked job postings, and your ranked-but-untracked postings (`/rank`'s recorded gaps in `seen_jobs.json`) — or a single posting via `/upskill `. Produces a prioritized heatmap of skill gaps and a learning plan with web-searched study resources and time estimates. Useful for career planning between applications. @@ -175,10 +175,10 @@ ai-job-search/ │ │ ├── add-portal.md # /add-portal generate a job-portal search skill for your market │ │ ├── rank.md # /rank triage scraped jobs into a ranked shortlist │ │ ├── outcome.md # /outcome record application results, archive materials -│ │ ├── gmail-sync.md # /gmail-sync auto-detect application status from Gmail │ │ ├── interview.md # /interview stage-specific prep pack + mock interview │ │ ├── html-report.md # /html-report generate application tracker dashboard -│ │ ├── notion-sync.md # /notion-sync one-way pipeline view in a Notion database +│ │ ├── sync-twenty.md # /sync-twenty one-way pipeline + company sync into Twenty CRM +│ │ ├── gather-recent-context.md # /gather-recent-context email context → Twenty CRM notes │ │ └── reset.md # /reset wipe profile data or documents folder │ ├── skills/ │ │ ├── job-application-assistant/ # Core application skill @@ -221,9 +221,11 @@ ai-job-search/ │ ├── convert_salary_excel.py # Convert salary Excel to JSON │ ├── lint_skills.py # CI lint for skills, commands, settings.json │ ├── security_guards.py # CI guards: permission allowlist, gitignore rules, manifests +│ ├── twenty_client.py # Minimal Twenty CRM REST client (stdlib only) +│ ├── twenty_enrich.py # Twenty enrichment plan: salary ladder, company fills, name cleanup +│ ├── gather_recent_context.py # Email-thread → opportunity note sync (deterministic core) │ └── README_SALARY_TOOL.md # Salary tool setup instructions ├── job_scraper/ # Scraper state (seen jobs, results) -├── gmail_sync/ # /gmail-sync state (processed message IDs, last sync date) ├── upskill/ # /upskill report output (markdown reports per run) ├── job_search_tracker.csv # Application tracking spreadsheet └── SETUP.md # Detailed setup guide diff --git a/SECURITY.md b/SECURITY.md index 4543743..ac65256 100755 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,7 +12,7 @@ This is an agentic workflow: an LLM with file access reads untrusted web content - **Untrusted-input rules**: `/apply` and `/rank` treat posting text as data, never instructions - agents are told not to follow directions embedded in postings and not to fetch URLs found inside posting text (the user-supplied posting URL is the one exception). Reviewer research starts from the company identity the user confirmed, never from links in the posting body. - **Permission allowlist**: `.claude/settings.json` pre-approves only the specific commands the workflow needs; the `security-guards` CI job fails any PR that widens it, adds package-manifest lifecycle scripts, or weakens the personal-data gitignore rules. Note the allowlist governs Bash commands - the model's native WebFetch/WebSearch tools are outside its reach, which is exactly why the instruction-level rules above exist. -- **Personal data boundaries**: your populated profile, tracker, salary data, and application archive are gitignored; documents never leave the machine by design (`/notion-sync` syncs filenames only; nothing uploads document content anywhere). +- **Personal data boundaries**: the tracker, salary data, and application archive are gitignored; documents never leave the machine by design (`/sync-twenty` syncs pipeline metadata and filenames only; nothing uploads document content anywhere). This fork deliberately tracks `documents/` source files in git. Instruction-level defenses raise the bar; they are not a sandbox. If you run this workflow against job boards you do not trust at all, review what the agent fetched and wrote before sending anything out. diff --git a/templates/company.csv b/templates/company.csv new file mode 100644 index 0000000..c517f21 --- /dev/null +++ b/templates/company.csv @@ -0,0 +1,2 @@ +Id,Name,Domain Name / Link URL,Domain Name / Link Label,Domain Name / Secondary Links,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Linkedin / Link URL,Linkedin / Link Label,Linkedin / Secondary Links,Last contact,Last contact item +c1a81b56-fa94-4d03-967a-88da331f8228,Roivant,https://roivant.com,Roivant,[],AGENT,6fa482f3-cebd-4bfa-a096-d454697afc54,Prad Nukala,,https://www.linkedin.com/company/roivant,Roivant,[],, \ No newline at end of file diff --git a/templates/dashboard.csv b/templates/dashboard.csv new file mode 100644 index 0000000..2c63877 --- /dev/null +++ b/templates/dashboard.csv @@ -0,0 +1,2 @@ +Id,Title,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Creation date,Last update +f31ecf3b-87d3-4e8a-a84b-b6f0f3f8c7e2,My First Dashboard,SYSTEM,,System,{},2026-08-15T19:49:14.993Z,2026-08-15T19:49:14.993Z \ No newline at end of file diff --git a/templates/note.csv b/templates/note.csv new file mode 100644 index 0000000..7ba0f26 --- /dev/null +++ b/templates/note.csv @@ -0,0 +1,2 @@ +Id,Title,Body / BlockNote,Body / Markdown,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Creation date +3b92cb37-85a0-4515-955a-c73a50a733e6,,,,MANUAL,6fa482f3-cebd-4bfa-a096-d454697afc54,Prad Nukala,{},2026-08-26T15:34:28.802Z \ No newline at end of file diff --git a/templates/opportunity.csv b/templates/opportunity.csv new file mode 100644 index 0000000..304df19 --- /dev/null +++ b/templates/opportunity.csv @@ -0,0 +1,2 @@ +Id,Position,Salary / Amount,Salary / Currency,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Deadline,Company Id,Point of Contact Id,Last contact item,Last contact,Score,URL / Link URL,URL / Link Label,URL / Secondary Links,Verdict,Location / Address 1,Location / Address 2,Location / City,Location / State,Location / Country,Location / Post Code,Location / Latitude,Location / Longitude +f4d006dc-7cb0-4649-a3ca-732bb1e89961,Founding Engineer — Stackbirds,0,,AGENT,6fa482f3-cebd-4bfa-a096-d454697afc54,Prad Nukala,,,d214fab2-accf-4b29-98c3-09c051ae6e61,,,,70,https://www.linkedin.com/jobs/view/founding-engineer-at-stackbirds-4455351694,Founding Engineer,[],GOOD_FIT,,,San Francisco,CA,,,, \ No newline at end of file diff --git a/templates/person.csv b/templates/person.csv new file mode 100644 index 0000000..48a59bf --- /dev/null +++ b/templates/person.csv @@ -0,0 +1,2 @@ +Id,Name / First Name,Name / Last Name,Emails / Primary Email,Emails / Additional Emails,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Company Id,Phones / Primary Phone Number,Phones / Primary Phone Country Code,Phones / Primary Phone Calling Code,Phones / Additional Phones,Creation date,Job Title,Linkedin / Link URL,Linkedin / Link Label,Linkedin / Secondary Links,Last inbound,Last contact,Last meeting Id,Last contact item,Last contact by Id,Last outbound,Last email Id +7a93d1e5-3f74-4945-8a65-d7f996083f72,Ivan,Zhao,zhao@notion.com,[],SYSTEM,,System,,06290608-8bf0-4806-99ae-a715a6a93fad,882261739,,‍+1,[],2026-08-15T19:49:14.993Z,,,,[],,,,,,, \ No newline at end of file diff --git a/templates/task.csv b/templates/task.csv new file mode 100644 index 0000000..59ae052 --- /dev/null +++ b/templates/task.csv @@ -0,0 +1,2 @@ +Id,Title,Status,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context,Due Date,Assignee Id,Body / BlockNote,Body / Markdown,Creation date +68892d48-7f2c-49c5-a399-36e255301b21,,TODO,MANUAL,6fa482f3-cebd-4bfa-a096-d454697afc54,Prad Nukala,{},,,,,2026-08-26T15:34:41.441Z \ No newline at end of file diff --git a/templates/workflow.csv b/templates/workflow.csv new file mode 100644 index 0000000..a79fdaf --- /dev/null +++ b/templates/workflow.csv @@ -0,0 +1,2 @@ +Id,Name,Statuses,Last update,Created by / Source,Created by / Workspace Member,Created by / Name,Created by / Context +33bfd30b-5558-53a8-866c-b679ce0c0bf9,Create company when adding a new person,"[""ACTIVE""]",2026-08-15T19:49:14.993Z,SYSTEM,,System,{} \ No newline at end of file