argus-code 0.2.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {argus_code-0.2.0 → argus_code-0.3.1}/PKG-INFO +68 -3
- {argus_code-0.2.0 → argus_code-0.3.1}/README.md +67 -2
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/layouts/Default.astro +3 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/index.astro +12 -14
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/session.astro +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/settings.astro +11 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/scripts/charts.ts +14 -13
- argus_code-0.3.1/dashboard-dist/_astro/charts.CAJCDcsn.js +1 -0
- argus_code-0.3.1/dashboard-dist/_astro/index.astro_astro_type_script_index_0_lang.Dtzf0Pdc.js +24 -0
- argus_code-0.2.0/dashboard-dist/_astro/session.astro_astro_type_script_index_0_lang.Dj_bfrIa.js → argus_code-0.3.1/dashboard-dist/_astro/session.astro_astro_type_script_index_0_lang.C2_GW8Bb.js +86 -86
- argus_code-0.3.1/dashboard-dist/_astro/settings.astro_astro_type_script_index_0_lang.C--f3VLy.js +24 -0
- argus_code-0.2.0/dashboard-dist/_astro/trends.astro_astro_type_script_index_0_lang.BLLeGRNa.js → argus_code-0.3.1/dashboard-dist/_astro/trends.astro_astro_type_script_index_0_lang.BZ0GmC-o.js +1 -1
- argus_code-0.3.1/dashboard-dist/index.html +2 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/models/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/prompts/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/session/index.html +2 -2
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/sessions/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/settings/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/tools/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/trends/index.html +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/pyproject.toml +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/__init__.py +1 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/cli.py +153 -42
- argus_code-0.3.1/python/argus/core/runtime.py +119 -0
- argus_code-0.3.1/python/argus/daemon/logging.py +103 -0
- argus_code-0.3.1/python/argus/daemon/pidfile.py +79 -0
- argus_code-0.3.1/python/argus/daemon/process.py +107 -0
- argus_code-0.3.1/python/argus/daemon/service.py +70 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/server/api.py +31 -2
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/server/app.py +2 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/store/db.py +25 -2
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/store/repository.py +82 -25
- argus_code-0.3.1/tests/core/test_runtime.py +178 -0
- argus_code-0.3.1/tests/daemon/test_logs.py +70 -0
- argus_code-0.3.1/tests/daemon/test_pidfile.py +39 -0
- argus_code-0.3.1/tests/daemon/test_process.py +85 -0
- argus_code-0.3.1/tests/daemon/test_service.py +59 -0
- argus_code-0.3.1/tests/daemon/test_yield.py +26 -0
- argus_code-0.3.1/tests/scaffold/__init__.py +0 -0
- argus_code-0.3.1/tests/schema/__init__.py +0 -0
- argus_code-0.3.1/tests/server/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/server/test_api.py +58 -1
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/server/test_api_search.py +41 -0
- argus_code-0.3.1/tests/store/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/store/test_db.py +27 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/store/test_repository.py +69 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/uv.lock +983 -983
- argus_code-0.2.0/dashboard-dist/_astro/charts.BIevw6Es.js +0 -1
- argus_code-0.2.0/dashboard-dist/_astro/index.astro_astro_type_script_index_0_lang.CgwSARdD.js +0 -24
- argus_code-0.2.0/dashboard-dist/_astro/settings.astro_astro_type_script_index_0_lang.d_a-uvdi.js +0 -24
- argus_code-0.2.0/dashboard-dist/index.html +0 -2
- {argus_code-0.2.0 → argus_code-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/.github/pull_request_template.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/.gitignore +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/.npmrc +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/ARCHITECTURE.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/CONTRIBUTING.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/LICENSE +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/NOTICE +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/PRD.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/SECURITY.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/TESTING.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/.astro/content-assets.mjs +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/.astro/content-modules.mjs +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/.astro/content.d.ts +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/.astro/types.d.ts +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/astro.config.mjs +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/package-lock.json +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/package.json +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/public/styles/global.css +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/models.astro +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/prompts.astro +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/sessions/index.astro +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/tools.astro +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/pages/trends.astro +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/scripts/alerts.ts +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/scripts/api.ts +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/scripts/format.ts +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/src/styles/global.css +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard/tsconfig.json +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/format.DxC1NGYT.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/index.astro_astro_type_script_index_0_lang.W18SJsr7.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/installCanvasRenderer.D_tC6TXz.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/models.astro_astro_type_script_index_0_lang.BHTHXYHC.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/prompts.astro_astro_type_script_index_0_lang.DfNgiDv9.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/_astro/tools.astro_astro_type_script_index_0_lang.Dzzau3Yt.js +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/dashboard-dist/styles/global.css +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/pricing/2026-05-02.json +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/base.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/adapter.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/discover.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/extract_tool_calls.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/extract_transcript.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/extract_turns.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/history_jsonl.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/ingest_file.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/model.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/claude_code/schemas.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/adapters/registry.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/aggregate.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/first_run.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/pipeline.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/rollup_subagents.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/scheduler.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/search_backfill.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/collector/watcher.py +0 -0
- {argus_code-0.2.0/python/argus/pricing → argus_code-0.3.1/python/argus/core}/__init__.py +0 -0
- {argus_code-0.2.0/python/argus/scaffold → argus_code-0.3.1/python/argus/daemon}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/detectors/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/detectors/base.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/detectors/registry.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/detectors/tool_error_rate_spike.py +0 -0
- {argus_code-0.2.0/python/argus/schema → argus_code-0.3.1/python/argus/pricing}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/pricing/compute.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/pricing/load.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/pricing/refresh.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/pricing/types.py +0 -0
- {argus_code-0.2.0/python/argus/server → argus_code-0.3.1/python/argus/scaffold}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/scaffold/scaffolder.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/scaffold/snapshot.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/scaffold/storage.py +0 -0
- {argus_code-0.2.0/python/argus/store → argus_code-0.3.1/python/argus/schema}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/schema/types.py +0 -0
- {argus_code-0.2.0/python/argus/store/migrations → argus_code-0.3.1/python/argus/server}/__init__.py +0 -0
- {argus_code-0.2.0/tests → argus_code-0.3.1/python/argus/store}/__init__.py +0 -0
- {argus_code-0.2.0/tests/adapters → argus_code-0.3.1/python/argus/store/migrations}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/python/argus/store/migrations/inline.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/agents/code-reviewer.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/agents/security-auditor.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/commands/commit.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/commands/deploy.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/commands/fix-issue.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/commands/pr.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/commands/review.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/rules/api-conventions.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/rules/code-style.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/rules/testing.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/settings.json +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/.claude/skills/example/SKILL.md +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/templates/default/CLAUDE.md +0 -0
- {argus_code-0.2.0/tests/adapters/claude_code → argus_code-0.3.1/tests}/__init__.py +0 -0
- {argus_code-0.2.0/tests/collector → argus_code-0.3.1/tests/adapters}/__init__.py +0 -0
- {argus_code-0.2.0/tests/detectors → argus_code-0.3.1/tests/adapters/claude_code}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_adapter.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_discover.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_extract_tool_calls.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_extract_transcript.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_extract_turns.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_history_jsonl.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_ingest_file.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_integration.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_model.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/claude_code/test_schemas.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/adapters/test_registry.py +0 -0
- {argus_code-0.2.0/tests/pricing → argus_code-0.3.1/tests/collector}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_aggregate.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_first_run.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_pipeline.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_rollup_subagents.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_scheduler.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/collector/test_watcher.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/conftest.py +0 -0
- {argus_code-0.2.0/tests/scaffold → argus_code-0.3.1/tests/core}/__init__.py +0 -0
- {argus_code-0.2.0/tests/schema → argus_code-0.3.1/tests/daemon}/__init__.py +0 -0
- {argus_code-0.2.0/tests/server → argus_code-0.3.1/tests/detectors}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/detectors/test_registry.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/detectors/test_tool_error_rate_spike.py +0 -0
- {argus_code-0.2.0/tests/store → argus_code-0.3.1/tests/pricing}/__init__.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/pricing/test_compute.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/pricing/test_load.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/pricing/test_refresh.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/scaffold/test_bundled_template.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/scaffold/test_cli_claude.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/scaffold/test_scaffolder.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/scaffold/test_snapshot.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/scaffold/test_storage.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/schema/test_types.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/server/test_server.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/server/test_week_of.py +0 -0
- {argus_code-0.2.0 → argus_code-0.3.1}/tests/test_e2e.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: argus-code
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Local-first dashboard for Claude Code cost, token, tool-usage, and full-text search analytics
|
|
5
5
|
Project-URL: Homepage, https://github.com/KrishBhimani/argus-code
|
|
6
6
|
Project-URL: Repository, https://github.com/KrishBhimani/argus-code.git
|
|
@@ -106,11 +106,68 @@ argus # top-level command group
|
|
|
106
106
|
│ ├─ list # list templates (bundled + user)
|
|
107
107
|
│ └─ create <name> [--path <dir>] [--all]
|
|
108
108
|
│ # save a project's .claude/ as a template
|
|
109
|
+
├─ daemon # argusd — background ingestion + detectors
|
|
110
|
+
│ ├─ start # start argusd detached, write PID file
|
|
111
|
+
│ ├─ stop # stop argusd gracefully
|
|
112
|
+
│ ├─ restart # stop then start
|
|
113
|
+
│ ├─ status # running? PID + uptime
|
|
114
|
+
│ └─ logs [-n N] [-f] # tail ~/.argus/argusd.log
|
|
109
115
|
└─ wipe # delete ~/.argus/ entirely
|
|
110
116
|
```
|
|
111
117
|
|
|
112
118
|
Run `--help` at any level for details — `argus --help`, `argus claude --help`,
|
|
113
|
-
`argus claude template --help`.
|
|
119
|
+
`argus claude template --help`, `argus daemon --help`.
|
|
120
|
+
|
|
121
|
+
### `argus daemon` — background daemon (argusd)
|
|
122
|
+
|
|
123
|
+
By default the watcher and the detector scheduler run *inside* `argus start` —
|
|
124
|
+
close the dashboard and ingestion stops. `argusd` moves that work into a
|
|
125
|
+
long-running background process so your data stays fresh and detectors keep
|
|
126
|
+
running whether or not the dashboard is open. It does **not** serve the
|
|
127
|
+
dashboard (port 4242 is still only bound by `argus start`).
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
argus daemon start # run argusd in the background
|
|
131
|
+
argus daemon status # PID + uptime
|
|
132
|
+
argus daemon logs -f # follow the log (survives rotation)
|
|
133
|
+
argus daemon stop
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
> **Autostart at login is coming separately.** For now, start argusd yourself
|
|
137
|
+
> with `argus daemon start` (it survives closing the terminal). OS-native
|
|
138
|
+
> autostart (`argus install` / `argus uninstall`) lands in a follow-up.
|
|
139
|
+
|
|
140
|
+
**Coexistence.** When argusd is running, `argus start` notices its PID file and
|
|
141
|
+
becomes a **read-only dashboard** — it skips its own watcher/scheduler and just
|
|
142
|
+
views the database the daemon keeps fresh (the footer shows *"Powered by
|
|
143
|
+
argusd"*). When argusd is **not** running, `argus start` ingests in-process
|
|
144
|
+
exactly as before, so users who never touch the daemon see no change.
|
|
145
|
+
|
|
146
|
+
> **Known limitation (v1):** if argusd dies while a read-only dashboard is open,
|
|
147
|
+
> the footer keeps showing *"Powered by argusd"* and ingestion pauses until you
|
|
148
|
+
> restart the dashboard (which then resumes ingesting in-process). The dashboard
|
|
149
|
+
> does not re-check daemon liveness on every poll.
|
|
150
|
+
|
|
151
|
+
**Running it long-term.** argusd is designed to stay up. Its idle cost is
|
|
152
|
+
negligible — the watcher is event-driven (sleeps until a `~/.claude` file
|
|
153
|
+
changes) and the detector scheduler wakes once every 10 minutes; expect ~40–70
|
|
154
|
+
MB RAM and effectively no idle CPU. The log is capped at ~4 MB (1 MB × 3
|
|
155
|
+
rotations). A few habits worth knowing:
|
|
156
|
+
|
|
157
|
+
- **Restart after upgrading argus.** A running daemon holds the old code in
|
|
158
|
+
memory — run `argus daemon restart` after `uv sync` / `pip install -U` to pick
|
|
159
|
+
up changes.
|
|
160
|
+
- **No auto-restart yet.** A daemon started with `argus daemon start` that
|
|
161
|
+
crashes stays down until you start it again (the stale PID file is cleaned up
|
|
162
|
+
automatically). OS-supervised restart arrives with autostart in a follow-up.
|
|
163
|
+
- **Windows stop is a hard kill.** `argus daemon stop` terminates the process
|
|
164
|
+
directly on Windows, so no `argusd stopped.` line is written to the log (the
|
|
165
|
+
CLI still prints it). This is safe — there's no critical in-memory state.
|
|
166
|
+
- **Toggling search:** the dashboard's "Enable indexing" button indexes
|
|
167
|
+
immediately; the CLI `argus search enable` only flips the flag and defers the
|
|
168
|
+
backfill to the next `argus start` / `argus daemon restart`.
|
|
169
|
+
|
|
170
|
+
Logs live at `~/.argus/argusd.log` (plain text, rotated at ~1 MB × 3).
|
|
114
171
|
|
|
115
172
|
### `argus claude` — project scaffolding
|
|
116
173
|
|
|
@@ -176,6 +233,12 @@ For vulnerability reports, see [SECURITY.md](./SECURITY.md).
|
|
|
176
233
|
Overview's "What needs attention" card reads these, and critical findings
|
|
177
234
|
raise a browser notification. The v1 detector flags tools whose error rate
|
|
178
235
|
spiked versus their preceding 4-week baseline.
|
|
236
|
+
7. **Shared runtime + daemon.** The watcher, scheduler, and first-pass ingest
|
|
237
|
+
are owned by a single `CoreRuntime` that both `argus start` and `argusd`
|
|
238
|
+
construct. The optional `argusd` daemon (`argus daemon`) runs that runtime in
|
|
239
|
+
its own process; a live daemon flips `argus start`
|
|
240
|
+
into a read-only viewer so the two never double-ingest. See **`argus
|
|
241
|
+
daemon`** above.
|
|
179
242
|
|
|
180
243
|
## Configuration
|
|
181
244
|
|
|
@@ -216,7 +279,7 @@ set, Argus's own database keeps the data even after Claude rotates it out.
|
|
|
216
279
|
git clone https://github.com/KrishBhimani/argus-code.git
|
|
217
280
|
cd argus-code
|
|
218
281
|
uv sync # install deps + create venv
|
|
219
|
-
uv run pytest # ~
|
|
282
|
+
uv run pytest # ~245 tests, ~20s
|
|
220
283
|
uv run argus start # dev — runs directly from source
|
|
221
284
|
```
|
|
222
285
|
|
|
@@ -231,6 +294,8 @@ python/argus/ Python ingest, store, server, CLI
|
|
|
231
294
|
store/ SQLite schema + migrations + repo
|
|
232
295
|
server/ FastAPI app + /api routes
|
|
233
296
|
collector/ watcher + pipeline + first-run + search backfill + alert scheduler
|
|
297
|
+
core/ CoreRuntime — shared watcher+scheduler+ingest lifecycle
|
|
298
|
+
daemon/ argusd: pidfile, foreground service, process control, logging
|
|
234
299
|
detectors/ alert detectors (pure reads) + @register registry
|
|
235
300
|
scaffold/ `argus claude` template storage / init / snapshot
|
|
236
301
|
pricing/ LiteLLM-derived price table + cost compute
|
|
@@ -54,11 +54,68 @@ argus # top-level command group
|
|
|
54
54
|
│ ├─ list # list templates (bundled + user)
|
|
55
55
|
│ └─ create <name> [--path <dir>] [--all]
|
|
56
56
|
│ # save a project's .claude/ as a template
|
|
57
|
+
├─ daemon # argusd — background ingestion + detectors
|
|
58
|
+
│ ├─ start # start argusd detached, write PID file
|
|
59
|
+
│ ├─ stop # stop argusd gracefully
|
|
60
|
+
│ ├─ restart # stop then start
|
|
61
|
+
│ ├─ status # running? PID + uptime
|
|
62
|
+
│ └─ logs [-n N] [-f] # tail ~/.argus/argusd.log
|
|
57
63
|
└─ wipe # delete ~/.argus/ entirely
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
Run `--help` at any level for details — `argus --help`, `argus claude --help`,
|
|
61
|
-
`argus claude template --help`.
|
|
67
|
+
`argus claude template --help`, `argus daemon --help`.
|
|
68
|
+
|
|
69
|
+
### `argus daemon` — background daemon (argusd)
|
|
70
|
+
|
|
71
|
+
By default the watcher and the detector scheduler run *inside* `argus start` —
|
|
72
|
+
close the dashboard and ingestion stops. `argusd` moves that work into a
|
|
73
|
+
long-running background process so your data stays fresh and detectors keep
|
|
74
|
+
running whether or not the dashboard is open. It does **not** serve the
|
|
75
|
+
dashboard (port 4242 is still only bound by `argus start`).
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
argus daemon start # run argusd in the background
|
|
79
|
+
argus daemon status # PID + uptime
|
|
80
|
+
argus daemon logs -f # follow the log (survives rotation)
|
|
81
|
+
argus daemon stop
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> **Autostart at login is coming separately.** For now, start argusd yourself
|
|
85
|
+
> with `argus daemon start` (it survives closing the terminal). OS-native
|
|
86
|
+
> autostart (`argus install` / `argus uninstall`) lands in a follow-up.
|
|
87
|
+
|
|
88
|
+
**Coexistence.** When argusd is running, `argus start` notices its PID file and
|
|
89
|
+
becomes a **read-only dashboard** — it skips its own watcher/scheduler and just
|
|
90
|
+
views the database the daemon keeps fresh (the footer shows *"Powered by
|
|
91
|
+
argusd"*). When argusd is **not** running, `argus start` ingests in-process
|
|
92
|
+
exactly as before, so users who never touch the daemon see no change.
|
|
93
|
+
|
|
94
|
+
> **Known limitation (v1):** if argusd dies while a read-only dashboard is open,
|
|
95
|
+
> the footer keeps showing *"Powered by argusd"* and ingestion pauses until you
|
|
96
|
+
> restart the dashboard (which then resumes ingesting in-process). The dashboard
|
|
97
|
+
> does not re-check daemon liveness on every poll.
|
|
98
|
+
|
|
99
|
+
**Running it long-term.** argusd is designed to stay up. Its idle cost is
|
|
100
|
+
negligible — the watcher is event-driven (sleeps until a `~/.claude` file
|
|
101
|
+
changes) and the detector scheduler wakes once every 10 minutes; expect ~40–70
|
|
102
|
+
MB RAM and effectively no idle CPU. The log is capped at ~4 MB (1 MB × 3
|
|
103
|
+
rotations). A few habits worth knowing:
|
|
104
|
+
|
|
105
|
+
- **Restart after upgrading argus.** A running daemon holds the old code in
|
|
106
|
+
memory — run `argus daemon restart` after `uv sync` / `pip install -U` to pick
|
|
107
|
+
up changes.
|
|
108
|
+
- **No auto-restart yet.** A daemon started with `argus daemon start` that
|
|
109
|
+
crashes stays down until you start it again (the stale PID file is cleaned up
|
|
110
|
+
automatically). OS-supervised restart arrives with autostart in a follow-up.
|
|
111
|
+
- **Windows stop is a hard kill.** `argus daemon stop` terminates the process
|
|
112
|
+
directly on Windows, so no `argusd stopped.` line is written to the log (the
|
|
113
|
+
CLI still prints it). This is safe — there's no critical in-memory state.
|
|
114
|
+
- **Toggling search:** the dashboard's "Enable indexing" button indexes
|
|
115
|
+
immediately; the CLI `argus search enable` only flips the flag and defers the
|
|
116
|
+
backfill to the next `argus start` / `argus daemon restart`.
|
|
117
|
+
|
|
118
|
+
Logs live at `~/.argus/argusd.log` (plain text, rotated at ~1 MB × 3).
|
|
62
119
|
|
|
63
120
|
### `argus claude` — project scaffolding
|
|
64
121
|
|
|
@@ -124,6 +181,12 @@ For vulnerability reports, see [SECURITY.md](./SECURITY.md).
|
|
|
124
181
|
Overview's "What needs attention" card reads these, and critical findings
|
|
125
182
|
raise a browser notification. The v1 detector flags tools whose error rate
|
|
126
183
|
spiked versus their preceding 4-week baseline.
|
|
184
|
+
7. **Shared runtime + daemon.** The watcher, scheduler, and first-pass ingest
|
|
185
|
+
are owned by a single `CoreRuntime` that both `argus start` and `argusd`
|
|
186
|
+
construct. The optional `argusd` daemon (`argus daemon`) runs that runtime in
|
|
187
|
+
its own process; a live daemon flips `argus start`
|
|
188
|
+
into a read-only viewer so the two never double-ingest. See **`argus
|
|
189
|
+
daemon`** above.
|
|
127
190
|
|
|
128
191
|
## Configuration
|
|
129
192
|
|
|
@@ -164,7 +227,7 @@ set, Argus's own database keeps the data even after Claude rotates it out.
|
|
|
164
227
|
git clone https://github.com/KrishBhimani/argus-code.git
|
|
165
228
|
cd argus-code
|
|
166
229
|
uv sync # install deps + create venv
|
|
167
|
-
uv run pytest # ~
|
|
230
|
+
uv run pytest # ~245 tests, ~20s
|
|
168
231
|
uv run argus start # dev — runs directly from source
|
|
169
232
|
```
|
|
170
233
|
|
|
@@ -179,6 +242,8 @@ python/argus/ Python ingest, store, server, CLI
|
|
|
179
242
|
store/ SQLite schema + migrations + repo
|
|
180
243
|
server/ FastAPI app + /api routes
|
|
181
244
|
collector/ watcher + pipeline + first-run + search backfill + alert scheduler
|
|
245
|
+
core/ CoreRuntime — shared watcher+scheduler+ingest lifecycle
|
|
246
|
+
daemon/ argusd: pidfile, foreground service, process control, logging
|
|
182
247
|
detectors/ alert detectors (pure reads) + @register registry
|
|
183
248
|
scaffold/ `argus claude` template storage / init / snapshot
|
|
184
249
|
pricing/ LiteLLM-derived price table + cost compute
|
|
@@ -28,6 +28,7 @@ const path = Astro.url.pathname;
|
|
|
28
28
|
<span class="status-pill" id="ingest-status">Loading…</span>
|
|
29
29
|
<span style="margin: 0 0.8rem;">·</span>
|
|
30
30
|
<span id="pricing-info">Pricing —</span>
|
|
31
|
+
<span id="daemon-status" style="margin-left: 0.8rem; display: none;" title="Ingestion is handled by the argusd background daemon">· Powered by argusd ●</span>
|
|
31
32
|
</footer>
|
|
32
33
|
<script>
|
|
33
34
|
async function refresh() {
|
|
@@ -56,6 +57,8 @@ const path = Astro.url.pathname;
|
|
|
56
57
|
ip.classList.add('busy');
|
|
57
58
|
}
|
|
58
59
|
}
|
|
60
|
+
const ds = document.getElementById('daemon-status');
|
|
61
|
+
if (ds) ds.style.display = s.daemon === true ? 'inline' : 'none';
|
|
59
62
|
} catch (e) { /* server may not be live yet */ }
|
|
60
63
|
}
|
|
61
64
|
refresh();
|
|
@@ -29,7 +29,7 @@ import Default from '../layouts/Default.astro';
|
|
|
29
29
|
</div>
|
|
30
30
|
|
|
31
31
|
<div class="card" style="margin-bottom:1.2rem;">
|
|
32
|
-
<h3>
|
|
32
|
+
<h3>Tokens over time</h3>
|
|
33
33
|
<div id="line" class="chart"></div>
|
|
34
34
|
</div>
|
|
35
35
|
|
|
@@ -39,7 +39,7 @@ import Default from '../layouts/Default.astro';
|
|
|
39
39
|
<div id="heatmap" class="chart"></div>
|
|
40
40
|
</div>
|
|
41
41
|
<div class="card">
|
|
42
|
-
<h3>Top models by
|
|
42
|
+
<h3>Top models by tokens (window)</h3>
|
|
43
43
|
<div id="models" class="chart"></div>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
@@ -52,7 +52,7 @@ import Default from '../layouts/Default.astro';
|
|
|
52
52
|
<script>
|
|
53
53
|
import { api } from '../scripts/api';
|
|
54
54
|
import { usd, tok, num, shortDate, shortPath, escapeHtml } from '../scripts/format';
|
|
55
|
-
import {
|
|
55
|
+
import { lineTokens, calendarHeatmap, modelMix } from '../scripts/charts';
|
|
56
56
|
import { renderAlertCard, startNotificationPoll } from '../scripts/alerts';
|
|
57
57
|
|
|
58
58
|
const $ = (s: string) => document.querySelector(s) as HTMLElement;
|
|
@@ -71,25 +71,23 @@ import Default from '../layouts/Default.astro';
|
|
|
71
71
|
$('.hero-tokens')!.textContent = tok(overview.total_tokens);
|
|
72
72
|
$('#hero-cost')!.textContent = usd(overview.total_cost_usd);
|
|
73
73
|
$('#hero-sessions')!.textContent = num(overview.session_count);
|
|
74
|
-
const
|
|
75
|
-
$('#hero-avg')!.textContent = overview.session_count ? `·
|
|
74
|
+
const avgTok = overview.session_count ? Math.round(overview.total_tokens / overview.session_count) : 0;
|
|
75
|
+
$('#hero-avg')!.textContent = overview.session_count ? `· ${tok(avgTok)} tok/session` : '';
|
|
76
76
|
|
|
77
|
-
const days = Object.entries(overview.
|
|
77
|
+
const days = Object.entries(overview.tokens_by_day).sort().map(([day, value]) => ({ day, value: value as number }));
|
|
78
78
|
lineChart?.dispose();
|
|
79
|
-
if (days.length) lineChart =
|
|
80
|
-
else $('#line').innerHTML = '<p class="empty">No
|
|
79
|
+
if (days.length) lineChart = lineTokens($('#line'), days);
|
|
80
|
+
else $('#line').innerHTML = '<p class="empty">No activity in this window.</p>';
|
|
81
81
|
|
|
82
82
|
const allOv = window === 'all' ? overview : await api.overview('all');
|
|
83
|
-
const allDays = Object.entries(allOv.
|
|
83
|
+
const allDays = Object.entries(allOv.tokens_by_day).sort().map(([day, value]) => ({ day, value: value as number }));
|
|
84
84
|
heatChart?.dispose();
|
|
85
85
|
heatChart = calendarHeatmap($('#heatmap'), allDays, 90);
|
|
86
86
|
|
|
87
|
-
// Models chart: read
|
|
88
|
-
|
|
89
|
-
// a session's whole-lifetime cost onto whichever model was its primary_model.
|
|
90
|
-
const modelsList = Object.entries(overview.cost_by_model ?? {})
|
|
87
|
+
// Models chart: read tokens_by_model from /api/overview (window-bucketed).
|
|
88
|
+
const modelsList = Object.entries(overview.tokens_by_model ?? {})
|
|
91
89
|
.sort((a, b) => (b[1] as number) - (a[1] as number))
|
|
92
|
-
.map(([name,
|
|
90
|
+
.map(([name, value]) => ({ name, value: value as number }));
|
|
93
91
|
modelsChart?.dispose();
|
|
94
92
|
if (modelsList.length) modelsChart = modelMix($('#models'), modelsList);
|
|
95
93
|
else $('#models').innerHTML = '<p class="empty">No data.</p>';
|
|
@@ -107,7 +107,7 @@ import Default from '../layouts/Default.astro';
|
|
|
107
107
|
|
|
108
108
|
root.innerHTML = `
|
|
109
109
|
<div class="grid-cards" style="margin-bottom:1.2rem;">
|
|
110
|
-
<div class="card kpi"><span class="kpi-label">Tokens</span><span class="kpi-value tokens">${tok(totalTokens)}</span><span class="kpi-sub">${num(totalTokens)} total</span></div>
|
|
110
|
+
<div class="card kpi"><span class="kpi-label">Tokens</span><span class="kpi-value tokens">${tok(totalTokens)}</span><span class="kpi-sub">${num(totalTokens)} total${subAgents?.length ? ` · incl. ${subAgents.length} sub-agent${subAgents.length === 1 ? '' : 's'}` : ''}</span></div>
|
|
111
111
|
<div class="card kpi"><span class="kpi-label">Cost <span style="color:var(--text-2);font-weight:400;">(est.)</span></span><span class="kpi-value cost">~${usd(s.total_cost_usd)}</span><span class="kpi-sub">${reportedDelta}</span></div>
|
|
112
112
|
<div class="card kpi"><span class="kpi-label">Turns</span><span class="kpi-value">${num(s.turn_count)}</span><span class="kpi-sub">${turns.length} loaded</span></div>
|
|
113
113
|
<div class="card kpi"><span class="kpi-label">Duration</span><span class="kpi-value">${dur(s.duration_sec)}</span><span class="kpi-sub">started ${fmtLocalDateTime(s.started_at)}</span></div>
|
|
@@ -212,6 +212,17 @@ import Default from '../layouts/Default.astro';
|
|
|
212
212
|
progress.style.display = 'none';
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// When yielded to a running argusd the API is read-only — write
|
|
216
|
+
// controls would 409. Show a note instead of buttons that fail.
|
|
217
|
+
let daemon = false;
|
|
218
|
+
try {
|
|
219
|
+
daemon = (await fetch('/api/ingest/status').then(r => r.json())).daemon === true;
|
|
220
|
+
} catch { /* leave daemon=false */ }
|
|
221
|
+
if (daemon) {
|
|
222
|
+
actions.innerHTML = `<span style="color:var(--text-2);font-size:0.82rem;">Read-only while <code>argusd</code> is running — manage indexing from the CLI (<code>argus search enable</code>) or stop the daemon with <code>argus daemon stop</code>.</span>`;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
215
226
|
// Action buttons reflect current state. Buttons that are
|
|
216
227
|
// pointless in the current state are simply omitted instead of
|
|
217
228
|
// disabled — fewer affordances to mis-click.
|
|
@@ -2,6 +2,7 @@ import * as echarts from 'echarts/core';
|
|
|
2
2
|
import { LineChart, BarChart, HeatmapChart, PieChart } from 'echarts/charts';
|
|
3
3
|
import { GridComponent, TooltipComponent, TitleComponent, LegendComponent, VisualMapComponent, MarkLineComponent } from 'echarts/components';
|
|
4
4
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
5
|
+
import { tok } from './format';
|
|
5
6
|
|
|
6
7
|
echarts.use([LineChart, BarChart, HeatmapChart, PieChart, GridComponent, TooltipComponent, TitleComponent, LegendComponent, VisualMapComponent, MarkLineComponent, CanvasRenderer]);
|
|
7
8
|
|
|
@@ -25,16 +26,16 @@ export function makeChart(el: HTMLElement) {
|
|
|
25
26
|
return echarts.init(el, null, { renderer: 'canvas' });
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function
|
|
29
|
+
export function lineTokens(el: HTMLElement, days: { day: string; value: number }[]) {
|
|
29
30
|
const c = makeChart(el);
|
|
30
31
|
c.setOption({
|
|
31
32
|
...THEME,
|
|
32
|
-
tooltip: { ...THEME.tooltip, trigger: 'axis', valueFormatter: (v: number) =>
|
|
33
|
+
tooltip: { ...THEME.tooltip, trigger: 'axis', valueFormatter: (v: number) => tok(v) + ' tokens' },
|
|
33
34
|
grid: { left: 50, right: 18, top: 18, bottom: 30 },
|
|
34
35
|
xAxis: { type: 'category', data: days.map(d => d.day.slice(5)), ...AXIS },
|
|
35
|
-
yAxis: { type: 'value', axisLabel: { ...AXIS.axisLabel, formatter:
|
|
36
|
+
yAxis: { type: 'value', axisLabel: { ...AXIS.axisLabel, formatter: (v: number) => tok(v) }, splitLine: AXIS.splitLine, axisLine: { show: false } },
|
|
36
37
|
series: [{
|
|
37
|
-
type: 'line', data: days.map(d =>
|
|
38
|
+
type: 'line', data: days.map(d => d.value),
|
|
38
39
|
smooth: true, symbol: 'circle', symbolSize: 5,
|
|
39
40
|
lineStyle: { color: '#f0883e', width: 2 },
|
|
40
41
|
itemStyle: { color: '#f0883e' },
|
|
@@ -65,17 +66,17 @@ export function agentSplit(el: HTMLElement, split: Record<string, { cost: number
|
|
|
65
66
|
return c;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
export function modelMix(el: HTMLElement, models: { name: string;
|
|
69
|
+
export function modelMix(el: HTMLElement, models: { name: string; value: number }[]) {
|
|
69
70
|
const c = makeChart(el);
|
|
70
71
|
const top = models.slice(0, 8);
|
|
71
72
|
c.setOption({
|
|
72
73
|
...THEME,
|
|
73
|
-
tooltip: { ...THEME.tooltip, valueFormatter: (v: number) =>
|
|
74
|
+
tooltip: { ...THEME.tooltip, valueFormatter: (v: number) => tok(v) + ' tokens' },
|
|
74
75
|
grid: { left: 140, right: 30, top: 8, bottom: 24 },
|
|
75
|
-
xAxis: { type: 'value', axisLabel: { ...AXIS.axisLabel, formatter:
|
|
76
|
+
xAxis: { type: 'value', axisLabel: { ...AXIS.axisLabel, formatter: (v: number) => tok(v) }, splitLine: AXIS.splitLine, axisLine: { show: false } },
|
|
76
77
|
yAxis: { type: 'category', data: top.map(m => m.name).reverse(), axisLabel: { ...AXIS.axisLabel, fontSize: 11 }, axisLine: { show: false }, axisTick: { show: false } },
|
|
77
78
|
series: [{
|
|
78
|
-
type: 'bar', data: top.map(m =>
|
|
79
|
+
type: 'bar', data: top.map(m => m.value).reverse(),
|
|
79
80
|
itemStyle: { color: '#f0883e', borderRadius: [0, 4, 4, 0] },
|
|
80
81
|
barWidth: '60%',
|
|
81
82
|
}],
|
|
@@ -83,9 +84,9 @@ export function modelMix(el: HTMLElement, models: { name: string; cost: number }
|
|
|
83
84
|
return c;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
export function calendarHeatmap(el: HTMLElement, days: { day: string;
|
|
87
|
+
export function calendarHeatmap(el: HTMLElement, days: { day: string; value: number }[], lookbackDays = 90) {
|
|
87
88
|
const c = makeChart(el);
|
|
88
|
-
const lookup = Object.fromEntries(days.map(d => [d.day, d.
|
|
89
|
+
const lookup = Object.fromEntries(days.map(d => [d.day, d.value]));
|
|
89
90
|
// Anchor at UTC midnight so lookup keys align with the API's started_at.slice(0,10)
|
|
90
91
|
// bucketing. Stepping by exactly 86400000 ms from a UTC-midnight anchor is robust against
|
|
91
92
|
// local-timezone hour-of-day skew.
|
|
@@ -131,7 +132,7 @@ export function calendarHeatmap(el: HTMLElement, days: { day: string; cost: numb
|
|
|
131
132
|
// ['Mon','Tue','Wed','Thu','Fri','Sat','Sun']: Sun→6, Mon→0, Tue→1, ..., Sat→5
|
|
132
133
|
const rowIdx = (d.getUTCDay() + 6) % 7;
|
|
133
134
|
const weekIdx = Math.round((monOfWeek(dayMs) - firstWeekMonMs) / (7 * 86_400_000));
|
|
134
|
-
cells.push([weekIdx, rowIdx,
|
|
135
|
+
cells.push([weekIdx, rowIdx, v]);
|
|
135
136
|
cellDate.set(`${weekIdx}-${rowIdx}`, key);
|
|
136
137
|
}
|
|
137
138
|
// Total columns spans first calendar week containing oldest day through
|
|
@@ -146,7 +147,7 @@ export function calendarHeatmap(el: HTMLElement, days: { day: string; cost: numb
|
|
|
146
147
|
const dow = p.data[1];
|
|
147
148
|
const v = p.data[2];
|
|
148
149
|
const key = cellDate.get(`${wk}-${dow}`) ?? '';
|
|
149
|
-
return `${key}<br>${v > 0 ?
|
|
150
|
+
return `${key}<br>${v > 0 ? tok(v) + ' tokens' : '<span style="color:#6b7585;">no activity</span>'}`;
|
|
150
151
|
},
|
|
151
152
|
},
|
|
152
153
|
grid: { left: 30, right: 10, top: 8, bottom: 22 },
|
|
@@ -162,7 +163,7 @@ export function calendarHeatmap(el: HTMLElement, days: { day: string; cost: numb
|
|
|
162
163
|
splitArea: { show: false },
|
|
163
164
|
},
|
|
164
165
|
visualMap: {
|
|
165
|
-
min: 0, max: Math.max(
|
|
166
|
+
min: 0, max: Math.max(1, max),
|
|
166
167
|
calculable: true, orient: 'horizontal', left: 'center', bottom: 0,
|
|
167
168
|
inRange: { color: ['#1c222d', '#7a3e1e', '#c2622e', '#f0883e'] },
|
|
168
169
|
textStyle: { color: '#6b7585', fontSize: 10 },
|