3 releases
| new 0.1.2 | Feb 12, 2026 |
|---|---|
| 0.1.1 | Feb 12, 2026 |
| 0.1.0 | Feb 9, 2026 |
#490 in Database interfaces
330KB
7.5K
SLoC
asanadw
Sync Asana data to a local SQLite data warehouse for offline queries, metrics, and LLM-powered summaries.
Quick start
cargo install asanadw
export ASANA_TOKEN="your-asana-personal-access-token"
asanadw monitor add-favorites
asanadw sync all
Monitoring
Before syncing, you tell asanadw which entities to track. Monitored entities are synced when you run sync all.
# Add all your favorited projects and portfolios
asanadw monitor add-favorites
# Add individual entities
asanadw monitor add project 1234567890
asanadw monitor add user user@example.com
asanadw monitor add team 1234567890
asanadw monitor add portfolio 1234567890
# Asana URLs work too
asanadw monitor add project https://app.asana.com/0/1234567890/list
# List and remove
asanadw monitor list
asanadw monitor remove project:1234567890
Syncing
Sync pulls data from the Asana API into the local database.
# Sync all monitored entities
asanadw sync all
# Sync a single entity
asanadw sync project 1234567890
asanadw sync user user@example.com
asanadw sync team 1234567890
asanadw sync portfolio 1234567890
What each entity type syncs:
- project -- tasks, comments, custom fields, sections
- user -- tasks assigned to the user
- team -- team members and team projects
- portfolio -- contained projects (and their tasks)
Filtering by date
asanadw sync all --days 90 # last 90 days
asanadw sync all --since 2024-01-01 # since a specific date
asanadw sync all --full # force full sync (ignore incremental tokens)
Incremental sync
After the first full sync of a project, subsequent syncs use the Asana Events API to fetch only what changed. This is significantly faster for large projects.
How it works
- After a full sync, asanadw stores an events sync token for each project.
- On the next sync, it asks the Events API "what changed since this token?"
- Only the changed tasks are fetched individually.
- If more than 50 tasks changed, asanadw falls back to a full bulk fetch (faster than 50+ individual GETs).
Token expiry
Asana event sync tokens expire after 24 hours. When a token expires:
- The next sync does a one-time full sync for that project
- A fresh token is stored automatically
- Subsequent syncs resume the fast incremental path
Forcing a full sync
asanadw sync all --full
asanadw sync project 1234567890 --full
Scheduling syncs
To stay on the fast incremental path, run sync all at least once every 24 hours. Running every 15-30 minutes is recommended for near-real-time data.
cron (every 15 minutes):
*/15 * * * * ASANA_TOKEN="your-token" /path/to/asanadw sync all >> /tmp/asanadw-sync.log 2>&1
launchd (macOS, every 15 minutes):
Save as ~/Library/LaunchAgents/com.asanadw.sync.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.asanadw.sync</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/asanadw</string>
<string>sync</string>
<string>all</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>ASANA_TOKEN</key>
<string>your-token</string>
</dict>
<key>StartInterval</key>
<integer>900</integer>
<key>StandardOutPath</key>
<string>/tmp/asanadw-sync.log</string>
<key>StandardErrorPath</key>
<string>/tmp/asanadw-sync.log</string>
</dict>
</plist>
Load with:
launchctl load ~/Library/LaunchAgents/com.asanadw.sync.plist
Querying
Query synced tasks with filters.
asanadw query --mine --incomplete
asanadw query --project 1234567890 --overdue
asanadw query --assignee user@example.com --completed
asanadw query --team 1234567890 --due-before 2025-03-01
asanadw query --portfolio 1234567890 --created-after 2025-01-01
Filters
| Flag | Description |
|---|---|
--project <GID> |
Filter by project |
--portfolio <GID> |
Filter by portfolio |
--team <GID> |
Filter by team |
--assignee <GID or email> |
Filter by assignee |
--mine |
Tasks assigned to you |
--completed |
Completed tasks only |
--incomplete |
Incomplete tasks only |
--overdue |
Overdue tasks only |
--created-after <YYYY-MM-DD> |
Created after date |
--created-before <YYYY-MM-DD> |
Created before date |
--due-after <YYYY-MM-DD> |
Due after date |
--due-before <YYYY-MM-DD> |
Due before date |
--limit <N> |
Max results (default: 100) |
Output formats
asanadw query --mine # default table
asanadw query --mine --json # JSON
asanadw query --mine --csv # CSV
asanadw query --mine --count # count only
Search
Full-text search across tasks, comments, projects, and custom fields.
asanadw search "launch plan"
asanadw search "bug" --type task --mine
asanadw search "feedback" --project 1234567890
asanadw search "design review" --type comment --json
| Flag | Description |
|---|---|
--type <TYPE> |
Filter by type: task, comment, project, custom_field |
--assignee <GID or email> |
Filter by assignee |
--mine |
Tasks assigned to you |
--project <GID> |
Filter by project |
--limit <N> |
Max results (default: 20) |
--json |
JSON output |
Metrics
Compute task metrics for a user, project, portfolio, or team over a time period.
asanadw metrics me
asanadw metrics me --period rolling-30d
asanadw metrics user user@example.com --period 2024-Q1
asanadw metrics project 1234567890 --period ytd
asanadw metrics portfolio 1234567890 --period 2024-M06
asanadw metrics team 1234567890 --period qtd --json
Period formats
| Period | Description |
|---|---|
qtd |
Quarter to date (default) |
ytd |
Year to date |
rolling-30d |
Rolling 30 days |
2024-Q1 |
Specific quarter |
2024-M03 |
Specific month |
Summaries
Generate LLM-powered narrative summaries. Requires an LLM provider to be configured (see Configuration).
asanadw summarize me
asanadw summarize me --period rolling-30d
asanadw summarize task 1234567890
asanadw summarize user user@example.com --period 2024-Q1
asanadw summarize project 1234567890 --period ytd
asanadw summarize portfolio 1234567890 --json
asanadw summarize team 1234567890 --force # bypass summary cache
| Flag | Description |
|---|---|
--period <PERIOD> |
Time period (same formats as metrics, default: qtd) |
--force |
Bypass cached summary and regenerate |
--json |
JSON output |
Configuration
asanadw config list
asanadw config get llm_provider
asanadw config set llm_provider bedrock
asanadw config set llm_model claude-sonnet-4-5
| Key | Description |
|---|---|
workspace_gid |
Asana workspace GID (auto-detected on first sync) |
llm_provider |
bedrock (default) or anthropic |
llm_model |
Model name (e.g. claude-sonnet-4-5, claude-haiku-4-5) |
Database
Data is stored in a SQLite database at ~/.asanadw/asanadw.db. Override with --db:
asanadw --db /path/to/custom.db sync all
The database uses WAL mode and can be queried directly with any SQLite client:
sqlite3 ~/.asanadw/asanadw.db "SELECT name FROM fact_tasks WHERE is_completed = 0 LIMIT 10"
Schema
The database follows a star schema:
- dim_ tables (dimensions):
dim_users,dim_teams,dim_projects,dim_portfolios,dim_sections,dim_date,dim_period,dim_custom_fields,dim_enum_options - fact_ tables (facts):
fact_tasks,fact_comments,fact_status_updates,fact_task_custom_fields,fact_task_summaries,fact_user_period_summaries,fact_project_period_summaries,fact_portfolio_period_summaries,fact_team_period_summaries - bridge_ tables (many-to-many):
bridge_task_projects,bridge_portfolio_projects,bridge_task_tags,bridge_task_dependencies,bridge_task_followers,bridge_team_members,bridge_task_multi_enum_values
Full-text search is powered by FTS5 virtual tables (tasks_fts, comments_fts, projects_fts, custom_fields_fts).
Environment variables
| Variable | Required | Description |
|---|---|---|
ASANA_TOKEN |
Yes | Asana personal access token |
ANTHROPIC_API_KEY |
For summarize with anthropic provider |
Anthropic API key |
AWS_* |
For summarize with bedrock provider (default) |
Standard AWS credentials (e.g. AWS_PROFILE, AWS_REGION) |
Development
make ci # Run all checks (fmt, lint, build, docs, test)
make test # Run tests
make coverage-html # Coverage report in browser
make fmt # Format code
make lint # Run clippy
License
MIT
Dependencies
~49–71MB
~1M SLoC