#sqlite #metrics #asana #sync #data-warehouse

bin+lib asanadw

Sync Asana data to a local SQLite data warehouse

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

MIT license

330KB
7.5K SLoC

asanadw

Crates.io docs.rs CI Coverage License

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

  1. After a full sync, asanadw stores an events sync token for each project.
  2. On the next sync, it asks the Events API "what changed since this token?"
  3. Only the changed tasks are fetched individually.
  4. 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

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