Capture free-form thoughts and turn them into:
- Structured markdown notes in your Obsidian vault
- Extracted tasks
- Optional Google Calendar events
This project is designed as a clean, modular Python service with explicit boundaries between orchestration, LLM reasoning, and tool integrations.
The goal is to reduce friction between thinking and execution:
- You type one unstructured capture.
- The system classifies intent.
- It structures the content.
- It writes a clean note.
- It optionally creates calendar events.
The same pipeline can be run from CLI today and extended to API/webhooks later.
- Groq LLM integration (OpenAI-compatible chat endpoint)
- Task-specific model routing (fast default classifier + stronger default structurer)
- LangGraph runtime when available
- Deterministic inline fallback runner when
langgraphis not installed - Obsidian vault markdown writer with safe path checks
- Optional Google Calendar event creation with OAuth refresh-token flow
- One-time helper script to generate and persist
GOOGLE_REFRESH_TOKEN - Heuristic fallbacks when LLM/network errors occur
.envloading built into config (no extra dotenv dependency)- Timezone-aware scheduling for relative time phrases (today/tomorrow/next week)
- End-to-end CLI capture flow (
main.py) - Groq-backed classification + structuring with JSON/fallback handling
- Obsidian note writing with safe path validation
- Google Calendar integration using OAuth refresh-token flow
- Automatic access-token refresh and retry on token expiration (
401) - Local timezone handling for relative schedule phrases
- One-time refresh-token generation helper script
main.py: composition root + CLI entrypointservices/: application use-case orchestrationgraph/: workflow state machine, nodes, routingllm/: prompt loading, reasoning, and Groq adapterinterfaces/: ports/protocols for dependency inversiontools/: concrete adapters (Obsidian, Google Calendar)schemas/: typed, validated dataclass contractsutils/: file helper primitives
High-level flow:
CLI input
-> CapturePipelineService
-> WorkflowGraphBuilder
-> ClassifyNode
-> StructureNode
-> WriteNoteNode
-> (optional) CalendarNode
-> PipelineResult
idea2tasks-ai-assistant/
├── main.py
├── config.py
├── pyproject.toml
├── .env.example
├── prompts/
│ ├── classifier.txt
│ └── formatter.txt
├── llm/
│ ├── groq_client.py
│ ├── prompt_loader.py
│ └── reasoner.py
├── graph/
│ ├── builder.py
│ ├── state.py
│ └── nodes/
│ ├── classify.py
│ ├── structure.py
│ ├── write_note.py
│ └── calendar.py
├── services/
│ └── capture_pipeline.py
├── interfaces/
│ ├── llm.py
│ ├── note_sink.py
│ └── calendar.py
├── tools/
│ ├── obsidian_writer.py
│ ├── google_calendar_auth.py
│ └── google_calendar.py
├── scripts/
│ └── generate_refresh_token.py
├── schemas/
│ └── capture.py
├── utils/
│ └── file_helpers.py
└── tests/
└── test_pipeline.py
- Input is collected via CLI (
--text, stdin, or prompt). CaptureRequestvalidates raw capture text.- Workflow state is initialized.
ClassifyNodepredicts one of:note_only,note_and_tasks,note_tasks_calendar,unknown.StructureNodeproducesStructuredCapture.WriteNoteNodewrites markdown to Obsidian using filename formatYYYY-MM-DD_HHMMSS_<slug>.md.CalendarNoderuns only if events exist.PipelineResultreturnsnote_path,tasks_count, andcreated_event_ids.
- Provider: Groq (
llm/groq_client.py) - Classifier model default:
llama-3.1-8b-instant - Structurer model default:
llama-3.3-70b-versatile
Fallback behavior:
- If Groq request fails or returns invalid JSON, classification falls back to deterministic heuristics.
- Structuring falls back to a minimal safe structured output.
This guarantees pipeline continuity even during partial LLM outages.
git clone <your-repo-url>
cd idea2tasks-ai-assistantuv syncAlternative:
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"uv run idea2tasks --text "quick capture"cp .env.example .envMinimum required .env:
GROQ_API_KEY=your_groq_api_key_here
OBSIDIAN_VAULT_ROOT=/absolute/path/to/your/obsidian/vaultImportant:
OBSIDIAN_VAULT_ROOTshould point to the vault root, not.obsidian.- Correct:
/home/user/Documents/Obsidian Vault - Incorrect:
/home/user/Documents/Obsidian Vault/.obsidian
Required:
GROQ_API_KEYOBSIDIAN_VAULT_ROOT
Optional:
GROQ_BASE_URL(default:https://api.groq.com/openai/v1)GROQ_MODEL(default:llama-3.3-70b-versatile)GROQ_MODEL_CLASSIFIER(default:llama-3.1-8b-instant)GROQ_MODEL_STRUCTURER(default:GROQ_MODEL)LLM_TEMPERATURE(default:0.2, valid range0.0-2.0)PROMPTS_DIR(default:./prompts)OBSIDIAN_NOTES_SUBDIR(default:Inbox)CALENDAR_ENABLED(true/false, default:false)GOOGLE_CALENDAR_ID(required if calendar enabled)GOOGLE_CLIENT_ID(required if calendar enabled)GOOGLE_CLIENT_SECRET(required if calendar enabled)GOOGLE_REFRESH_TOKEN(required if calendar enabled)
Run with inline text:
python main.py --text "Plan sprint review tomorrow at 10am and follow up with design team"Run with stdin:
echo "Need to prepare proposal by Friday and schedule a client call next week" | python main.pyIf installed as script:
idea2tasks --text "Draft product brief and create follow-up tasks"If using uv:
uv run idea2tasks --text "Draft product brief and create follow-up tasks"Example output:
Note path: Inbox/2026-02-20_214501_draft-product-brief.md
Tasks extracted: 2
Calendar events created: none
Set:
CALENDAR_ENABLED=true
GOOGLE_CALENDAR_ID=primary
GOOGLE_CLIENT_ID=<google_oauth_client_id>
GOOGLE_CLIENT_SECRET=<google_oauth_client_secret>
GOOGLE_REFRESH_TOKEN=<google_oauth_refresh_token>Generate GOOGLE_REFRESH_TOKEN automatically (one-time):
python scripts/generate_refresh_token.pyThe script:
- uses Installed App OAuth flow in your browser
- requests
https://www.googleapis.com/auth/calendar - forces
access_type=offlineandprompt=consent - validates calendar API access
- saves
GOOGLE_REFRESH_TOKENinto.env
Behavior:
- Access tokens are refreshed automatically from
GOOGLE_REFRESH_TOKEN. - No manual access-token rotation in
.envis required. - Legacy
GOOGLE_CALENDAR_ACCESS_TOKENis no longer used. - If calendar is enabled and structured events exist, events are created via Google Calendar API.
- If disabled, calendar node is a no-op.
Run tests:
python -m pytest -qCurrent tests cover:
- Note-only flow
- Calendar-disabled behavior with event-like captures
Before pushing to GitHub:
- Confirm
.envis not tracked (git statusshould not show.env) - Keep
.env.exampleplaceholder-only (no real keys/tokens) - Run
python -m compileall main.py config.py graph llm tools services schemas interfaces utils scripts teststo catch syntax errors - Run
python -m pytest -q(if pytest installed) - Verify
README.mdreflects current env variables and auth flow - Verify no secrets appear in staged diff (
git diff --staged) - Ensure generated artifacts are ignored (
.venv,__pycache__,*.egg-info)
Missing required environment variable: GROQ_API_KEY
- Ensure
.envexists at repo root. - Ensure line format is valid:
GROQ_API_KEY=... - Restart shell/session if you rely on exported env vars.
Groq API request failed: 403 ... 1010
- Cloudflare/network policy block.
- Disable VPN/proxy/ad blockers.
- Try another network.
- Regenerate API key and verify account permissions.
Error 403: access_denied during Google OAuth
- In Google Cloud, your OAuth app is likely in
Testingmode. - Add your Google account under Google Auth Platform -> Audience -> Test users.
- If app type is
Internal, use an account in that Workspace org. - Retry
python scripts/generate_refresh_token.pyafter updating audience/test users.
Permission denied writing notes
- Check
OBSIDIAN_VAULT_ROOT. - Do not point it at
.obsidian. - Verify write permission to vault directory.
Calendar created at wrong local time
- The app now uses local timezone from your system clock for capture timestamps.
- For unambiguous scheduling, include timezone in text, e.g.
10:00 AM IST.
pytest: command not found
- Install dev deps:
uv syncorpip install -e ".[dev]"
uv sync package discovery issues
- This repo uses explicit setuptools package discovery in
pyproject.toml. - Re-run
uv syncafter pulling latestpyproject.toml.
- Never commit
.env. .env.examplemust contain placeholders only.- Treat
GROQ_API_KEYand calendar tokens as secrets. - Rotate keys immediately if accidentally exposed.
- If a key/token was ever committed, revoke and replace it before publishing.
- Code style is intentionally explicit and modular.
- Protocol interfaces in
interfaces/make swapping providers straightforward. - Graph logic stays isolated from tool implementations.
- You can replace Groq by implementing
interfaces.llm.LLMClient.
Common extension points:
- Add new graph nodes for additional actions.
- Add new providers (Notion, Todoist, etc.) in
tools/. - Replace CLI entry with FastAPI while reusing service layer.
- Add richer prompt templates under
prompts/.
This is a working implementation with robust fallbacks and clear architecture. It is suitable for personal use and as a base for open-source collaboration.
No license file is currently included in this repository.
If you plan to publish it as open source, add a LICENSE file (for example MIT or Apache-2.0).