agent-notes 2.25.0__tar.gz → 2.26.0__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.
Files changed (270) hide show
  1. {agent_notes-2.25.0 → agent_notes-2.26.0}/PKG-INFO +1 -1
  2. agent_notes-2.26.0/agent_notes/VERSION +1 -0
  3. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/notes.py +4 -2
  4. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/cost_reporting.md +1 -1
  5. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/hard_limits.md +1 -1
  6. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/pricing.yaml +3 -3
  7. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/scripts/_claude_backend.py +10 -62
  8. agent_notes-2.26.0/agent_notes/scripts/_formatting.py +120 -0
  9. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/scripts/_opencode_backend.py +10 -68
  10. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/PKG-INFO +1 -1
  11. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/SOURCES.txt +2 -0
  12. agent_notes-2.26.0/tests/functional/memory/test_memory_add_local_backend.py +55 -0
  13. agent_notes-2.26.0/tests/unit/scripts/test_opencode_backend_pricing.py +100 -0
  14. agent_notes-2.25.0/agent_notes/VERSION +0 -1
  15. agent_notes-2.25.0/agent_notes/scripts/_formatting.py +0 -49
  16. {agent_notes-2.25.0 → agent_notes-2.26.0}/LICENSE +0 -0
  17. {agent_notes-2.25.0 → agent_notes-2.26.0}/README.md +0 -0
  18. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/__init__.py +0 -0
  19. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/__main__.py +0 -0
  20. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/cli.py +0 -0
  21. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/__init__.py +0 -0
  22. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/_install_helpers.py +0 -0
  23. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/build.py +0 -0
  24. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/config.py +0 -0
  25. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/doctor.py +0 -0
  26. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/hook.py +0 -0
  27. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/info.py +0 -0
  28. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/install.py +0 -0
  29. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/list.py +0 -0
  30. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/__init__.py +0 -0
  31. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/_common.py +0 -0
  32. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/migrate.py +0 -0
  33. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/reset.py +0 -0
  34. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/transfer.py +0 -0
  35. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/vault.py +0 -0
  36. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/memory/wiki.py +0 -0
  37. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/regenerate.py +0 -0
  38. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/set_role.py +0 -0
  39. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/uninstall.py +0 -0
  40. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/validate.py +0 -0
  41. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/wizard/__init__.py +0 -0
  42. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/wizard/_common.py +0 -0
  43. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/wizard/cost_report.py +0 -0
  44. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/wizard/execute.py +0 -0
  45. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/commands/wizard/orchestrator.py +0 -0
  46. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/config.py +0 -0
  47. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/constants.py +0 -0
  48. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/agents.yaml +0 -0
  49. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/analyst.md +0 -0
  50. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/api-reviewer.md +0 -0
  51. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/architect.md +0 -0
  52. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/coder.md +0 -0
  53. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/database-specialist.md +0 -0
  54. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/debugger.md +0 -0
  55. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/devil.md +0 -0
  56. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/devops.md +0 -0
  57. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/explorer.md +0 -0
  58. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/integrations.md +0 -0
  59. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/lead.md +0 -0
  60. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/performance-profiler.md +0 -0
  61. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/refactorer.md +0 -0
  62. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/reviewer.md +0 -0
  63. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/security-auditor.md +0 -0
  64. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/execution.md +0 -0
  65. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/guardrails.md +0 -0
  66. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/phase0.md +0 -0
  67. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/pipelines.md +0 -0
  68. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/review.md +0 -0
  69. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/verification.md +0 -0
  70. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/shared/wiki_compile.md +0 -0
  71. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/system-auditor.md +0 -0
  72. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/tech-writer.md +0 -0
  73. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/test-runner.md +0 -0
  74. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/test-writer.md +0 -0
  75. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/agents/wiki-compiler.md +0 -0
  76. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/cli/claude.yaml +0 -0
  77. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/cli/copilot.yaml +0 -0
  78. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/cli/opencode.yaml +0 -0
  79. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/commands/brainstorm.md +0 -0
  80. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/commands/debug.md +0 -0
  81. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/commands/review.md +0 -0
  82. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/global-claude.md +0 -0
  83. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/global-copilot.md +0 -0
  84. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/global-opencode.md +0 -0
  85. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/hooks/session-context.md.tpl +0 -0
  86. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-haiku-4-5.yaml +0 -0
  87. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-opus-4-1.yaml +0 -0
  88. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-opus-4-5.yaml +0 -0
  89. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-opus-4-6.yaml +0 -0
  90. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-opus-4-7.yaml +0 -0
  91. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-opus-4-8.yaml +0 -0
  92. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-sonnet-4-5.yaml +0 -0
  93. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-sonnet-4-6.yaml +0 -0
  94. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/models/claude-sonnet-4.yaml +0 -0
  95. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/plugin/claude.yaml +0 -0
  96. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/plugin/opencode-index.js.template +0 -0
  97. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/plugin/opencode.yaml +0 -0
  98. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/roles/orchestrator.yaml +0 -0
  99. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/roles/reasoner.yaml +0 -0
  100. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/roles/scout.yaml +0 -0
  101. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/roles/worker.yaml +0 -0
  102. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/rules/code-quality.md +0 -0
  103. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/rules/safety.md +0 -0
  104. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/brainstorming/SKILL.md +0 -0
  105. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/caveman/SKILL.md +0 -0
  106. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/code-review/SKILL.md +0 -0
  107. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/debugging-protocol/SKILL.md +0 -0
  108. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/docker/SKILL.md +0 -0
  109. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/docker/compose.md +0 -0
  110. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/docker/dockerfile.md +0 -0
  111. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/git/SKILL.md +0 -0
  112. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/grill-me/SKILL.md +0 -0
  113. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/grill-with-docs/SKILL.md +0 -0
  114. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/handoff/SKILL.md +0 -0
  115. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/improve-codebase-architecture/SKILL.md +0 -0
  116. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/ingest/SKILL.md +0 -0
  117. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/migrate-memory/SKILL.md +0 -0
  118. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/obsidian-memory/SKILL.md +0 -0
  119. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/prototype/LOGIC.md +0 -0
  120. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/prototype/SKILL.md +0 -0
  121. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/prototype/UI.md +0 -0
  122. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/SKILL.md +0 -0
  123. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/controllers.md +0 -0
  124. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/frontend.md +0 -0
  125. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/infra.md +0 -0
  126. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/models.md +0 -0
  127. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/testing.md +0 -0
  128. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/rails/views.md +0 -0
  129. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/refactoring-protocol/SKILL.md +0 -0
  130. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/setup-project-context/SKILL.md +0 -0
  131. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/tdd/SKILL.md +0 -0
  132. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/to-issues/SKILL.md +0 -0
  133. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/to-prd/SKILL.md +0 -0
  134. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/write-a-skill/SKILL.md +0 -0
  135. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/skills/zoom-out/SKILL.md +0 -0
  136. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/__init__.py +0 -0
  137. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  138. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/__init__.py +0 -0
  139. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  140. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  141. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  142. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/claude.py +0 -0
  143. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/data/templates/frontmatter/opencode.py +0 -0
  144. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/doctor_checks.py +0 -0
  145. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/__init__.py +0 -0
  146. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/agent.py +0 -0
  147. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/cli_backend.py +0 -0
  148. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/diagnostics.py +0 -0
  149. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/diff.py +0 -0
  150. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/model.py +0 -0
  151. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/role.py +0 -0
  152. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/rule.py +0 -0
  153. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/skill.py +0 -0
  154. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/domain/state.py +0 -0
  155. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/__init__.py +0 -0
  156. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/_base.py +0 -0
  157. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/agent_registry.py +0 -0
  158. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/cli_registry.py +0 -0
  159. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/model_registry.py +0 -0
  160. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/role_registry.py +0 -0
  161. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/rule_registry.py +0 -0
  162. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/registries/skill_registry.py +0 -0
  163. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/scripts/__init__.py +0 -0
  164. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/scripts/_pricing.py +0 -0
  165. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/scripts/cost_report.py +0 -0
  166. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/__init__.py +0 -0
  167. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/_memory_utils.py +0 -0
  168. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/counts.py +0 -0
  169. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/credentials.py +0 -0
  170. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/diagnostics/__init__.py +0 -0
  171. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/diagnostics/_checks.py +0 -0
  172. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/diagnostics/_display.py +0 -0
  173. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/diagnostics/_fix.py +0 -0
  174. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/diff.py +0 -0
  175. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/fs.py +0 -0
  176. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/install_state_builder.py +0 -0
  177. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/installer.py +0 -0
  178. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/local_backend.py +0 -0
  179. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/memory_router.py +0 -0
  180. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/migrations/__init__.py +0 -0
  181. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/obsidian_backend.py +0 -0
  182. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/rendering.py +0 -0
  183. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/session_context.py +0 -0
  184. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/settings_writer.py +0 -0
  185. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/state_store.py +0 -0
  186. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/ui.py +0 -0
  187. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/user_config.py +0 -0
  188. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/validation.py +0 -0
  189. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/__init__.py +0 -0
  190. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/_wiki_utils.py +0 -0
  191. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/wiki_index.py +0 -0
  192. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/wiki_ingest.py +0 -0
  193. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/wiki_lint.py +0 -0
  194. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/wiki_query.py +0 -0
  195. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki/wiki_storage.py +0 -0
  196. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes/services/wiki_backend.py +0 -0
  197. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/dependency_links.txt +0 -0
  198. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/entry_points.txt +0 -0
  199. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/requires.txt +0 -0
  200. {agent_notes-2.25.0 → agent_notes-2.26.0}/agent_notes.egg-info/top_level.txt +0 -0
  201. {agent_notes-2.25.0 → agent_notes-2.26.0}/pyproject.toml +0 -0
  202. {agent_notes-2.25.0 → agent_notes-2.26.0}/setup.cfg +0 -0
  203. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/conftest.py +0 -0
  204. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/__init__.py +0 -0
  205. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/__init__.py +0 -0
  206. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_config_command.py +0 -0
  207. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_doctor_command.py +0 -0
  208. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_info_command.py +0 -0
  209. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_install_command.py +0 -0
  210. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_list_command.py +0 -0
  211. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_regenerate_command.py +0 -0
  212. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_uninstall_command.py +0 -0
  213. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/commands/test_validate_command.py +0 -0
  214. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/memory/__init__.py +0 -0
  215. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/memory/test_memory_command.py +0 -0
  216. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/scripts/__init__.py +0 -0
  217. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/functional/scripts/test_release_script.py +0 -0
  218. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/__init__.py +0 -0
  219. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/build_output/__init__.py +0 -0
  220. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/build_output/test_build_output.py +0 -0
  221. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/install/__init__.py +0 -0
  222. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/install/test_install_methods.py +0 -0
  223. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/plugin_builders/__init__.py +0 -0
  224. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/integration/plugin_builders/test_plugin_builders.py +0 -0
  225. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/plugins/__init__.py +0 -0
  226. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/plugins/claude/__init__.py +0 -0
  227. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/plugins/claude/test_agents.py +0 -0
  228. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/plugins/test_skills.py +0 -0
  229. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/__init__.py +0 -0
  230. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/__init__.py +0 -0
  231. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_cost_report_subcommand.py +0 -0
  232. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_count_agents.py +0 -0
  233. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_info.py +0 -0
  234. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_memory_add_description.py +0 -0
  235. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_memory_imports.py +0 -0
  236. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_memory_migrate.py +0 -0
  237. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_wizard_imports.py +0 -0
  238. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_wizard_orchestrator_skip.py +0 -0
  239. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_wizard_preflight.py +0 -0
  240. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/test_wizard_steps.py +0 -0
  241. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/commands/wizard/test_cost_report_step.py +0 -0
  242. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/registries/__init__.py +0 -0
  243. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/registries/test_registries.py +0 -0
  244. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/scripts/__init__.py +0 -0
  245. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/scripts/test_cost_report.py +0 -0
  246. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/scripts/test_cost_report_scoping.py +0 -0
  247. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/scripts/test_formatting_tty.py +0 -0
  248. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/scripts/test_time_aggregation.py +0 -0
  249. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/__init__.py +0 -0
  250. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_build_functions.py +0 -0
  251. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_credential_filter.py +0 -0
  252. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_credentials.py +0 -0
  253. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_fs.py +0 -0
  254. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_installer_hooks.py +0 -0
  255. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_installer_plan.py +0 -0
  256. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_local_backend.py +0 -0
  257. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_memory_backend.py +0 -0
  258. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_memory_backend_io.py +0 -0
  259. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_memory_router.py +0 -0
  260. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_rendering_includes.py +0 -0
  261. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_session_context.py +0 -0
  262. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_settings_writer.py +0 -0
  263. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_skill_filtering.py +0 -0
  264. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_state_store.py +0 -0
  265. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_user_config_cost_report.py +0 -0
  266. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_validation.py +0 -0
  267. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_wiki_backend.py +0 -0
  268. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/services/test_wiki_imports.py +0 -0
  269. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/test_import_health.py +0 -0
  270. {agent_notes-2.25.0 → agent_notes-2.26.0}/tests/unit/test_memory_dir_for_backend.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-notes
3
- Version: 2.25.0
3
+ Version: 2.26.0
4
4
  Summary: AI agent configuration manager for Claude Code, OpenCode, and Copilot
5
5
  Author-email: Eugene Naumov <min.verkligheten@gmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ 2.26.0
@@ -45,8 +45,10 @@ def do_add(title: str, body: str, note_type: str = "context", agent: str = "", p
45
45
  )
46
46
  print(f"{Color.GREEN}Note saved: {note_path}{Color.NC}")
47
47
  else:
48
- print("The `add` subcommand is for obsidian or wiki storage.")
49
- print("For local storage, write files directly to the agent subdirectory.")
48
+ import sys
49
+ print("The `add` subcommand is for obsidian or wiki storage.", file=sys.stderr)
50
+ print("For local storage, write files directly to the agent subdirectory.", file=sys.stderr)
51
+ sys.exit(1)
50
52
 
51
53
 
52
54
  def do_list() -> None:
@@ -4,7 +4,7 @@ At the END of every response, run `agent-notes cost-report` and include the outp
4
4
 
5
5
  **Session cost** (cumulative for the entire conversation):
6
6
 
7
- Render every column the `agent-notes cost-report` CLI emits — `agent(model)`, `in/out/cache`, `time`, `actual`, `vs Claude Opus 4.7` — in that order. Do not split, drop, or rename columns. Preserve the data verbatim.
7
+ Render every column the `agent-notes cost-report` CLI emits — `agent(model)`, `in/out/cache`, `time`, `actual`, `vs Claude Opus 4.8` — in that order. Do not split, drop, or rename columns. Preserve the data verbatim.
8
8
 
9
9
  **On failure or skip — never fabricate.** If `agent-notes cost-report` returns non-zero, errors, or you skip running it, do NOT render a placeholder table or invent rows like `(cost report unavailable — agent-notes cost-report not run)`. Instead, print one plain line under the heading:
10
10
 
@@ -13,7 +13,7 @@ You MUST NOT directly:
13
13
  - Run installs, builds, migrations, or destructive commands — dispatch `devops`
14
14
  - Use `bash` for anything beyond the read-only verification list above
15
15
 
16
- If you feel the urge to "just quickly check a file" — STOP. Dispatch `explorer`. Every file read by the lead is a budget leak (Opus tokens are 30× Haiku).
16
+ If you feel the urge to "just quickly check a file" — STOP. Dispatch `explorer`. Every file read by the lead is a budget leak (Opus tokens are 5× Haiku).
17
17
 
18
18
  Exception: trivial requests (factual questions, conversational replies, single-line answers) may be handled inline with no tools.
19
19
 
@@ -1,5 +1,5 @@
1
1
  baseline:
2
- label: Claude Opus 4.7
2
+ label: Claude Opus 4.8
3
3
  price:
4
4
  in: 5.00
5
5
  out: 25.00
@@ -16,8 +16,8 @@ providers:
16
16
  match: "*sonnet*"
17
17
  price: {in: 3.00, out: 15.00, cache: 0.30}
18
18
  updated_at: "2026-04"
19
- - name: Claude Opus 4.7 / 4.6 / 4.5
20
- match: ["*opus-4.7*", "*opus-4.6*", "*opus-4.5*"]
19
+ - name: Claude Opus 4.8 / 4.7 / 4.6 / 4.5
20
+ match: ["*opus-4.8*", "*opus-4.7*", "*opus-4.6*", "*opus-4.5*"]
21
21
  price: {in: 5.00, out: 25.00, cache: 0.50}
22
22
  updated_at: "2026-04"
23
23
  - name: Claude Opus (legacy)
@@ -6,8 +6,7 @@ from pathlib import Path
6
6
 
7
7
  from . import _pricing
8
8
  from ._formatting import (
9
- BOLD, DIM, GREEN, YELLOW, NC,
10
- tier_color, fmt_tokens, fmt_cost, fmt_time,
9
+ fmt_time, render_cost_table,
11
10
  )
12
11
  from ..services.state_store import state_file as _state_file, load_state as _load_state
13
12
 
@@ -279,66 +278,15 @@ def run(since: float | None = None, session_id: str | None = None) -> int:
279
278
  ms = agent_time_ms.get(label, 0)
280
279
  return fmt_time(ms / 1000) if ms > 0 else "n/a"
281
280
 
282
- agent_col_w = max(len(f"{a}({m})") for a, m, *_ in costs) + 2
283
- tok_col_w = max(
284
- max(len(fmt_tokens(i, o, c)) for _, _, i, o, c, *_ in costs),
285
- len(fmt_tokens(
286
- sum(i for _, _, i, *_ in costs),
287
- sum(o for _, _, _, o, *_ in costs),
288
- sum(c for _, _, _, _, c, *_ in costs),
289
- ))
290
- ) + 2
291
- all_time_strs = [_time_str_for(a) for a, *_ in costs]
292
- time_col_w = max(len(s) for s in all_time_strs) + 2
293
- W = (agent_col_w, tok_col_w, time_col_w, 12, 12)
294
-
295
- bl_label = _pricing.baseline_label()
296
- header = (
297
- f"{'agent(model)':<{W[0]}}"
298
- f" {'in/out/cache':<{W[1]}}"
299
- f" {'time':<{W[2]}}"
300
- f" {'actual':<{W[3]}}"
301
- f" {f'vs {bl_label}':<{W[4]}}"
281
+ total_time_ms = sum(
282
+ lead_time_ms if agent == "lead" else agent_time_ms.get(agent, 0)
283
+ for agent, *_ in costs
302
284
  )
303
- print(BOLD + header + NC)
304
- print(DIM + "-" * len(header) + NC)
305
-
306
- total_inp = total_outp = total_cache = 0
307
- total_actual = total_vs = 0.0
308
- total_time_ms = 0
309
-
310
- for agent, model, inp, outp, cache, actual, vs in costs:
311
- label = f"{agent}({model})"
312
- col = tier_color(model)
313
- t_str = _time_str_for(agent)
314
- print(
315
- col + f"{label:<{W[0]}}" + NC
316
- + f" {fmt_tokens(inp, outp, cache):<{W[1]}}"
317
- + f" {t_str:<{W[2]}}"
318
- + f" {fmt_cost(actual):<{W[3]}}"
319
- + f" {fmt_cost(vs):<{W[4]}}"
320
- )
321
- total_inp += inp
322
- total_outp += outp
323
- total_cache += cache
324
- total_actual += actual
325
- total_vs += vs
326
- if agent == "lead":
327
- total_time_ms += lead_time_ms
328
- else:
329
- total_time_ms += agent_time_ms.get(agent, 0)
330
-
331
- saved_pct = round((1 - total_actual / total_vs) * 100) if total_vs else 0
332
- total_label = f"TOTAL (saved {saved_pct}%)"
333
285
  total_time_str = fmt_time(total_time_ms / 1000) if total_time_ms > 0 else "n/a"
334
- col = GREEN if total_actual <= 5 else YELLOW
335
- print(
336
- col + BOLD
337
- + f"{total_label:<{W[0]}}"
338
- + f" {fmt_tokens(total_inp, total_outp, total_cache):<{W[1]}}"
339
- + f" {total_time_str:<{W[2]}}"
340
- + f" {fmt_cost(total_actual):<{W[3]}}"
341
- + f" {fmt_cost(total_vs):<{W[4]}}"
342
- + NC
343
- )
286
+
287
+ rows = [
288
+ (f"{agent}({model})", model, inp, outp, cache, _time_str_for(agent), actual, vs)
289
+ for agent, model, inp, outp, cache, actual, vs in costs
290
+ ]
291
+ render_cost_table(rows, total_time_str, _pricing.baseline_label())
344
292
  return 0
@@ -0,0 +1,120 @@
1
+ """ANSI color constants and shared formatting helpers."""
2
+ import os
3
+ import sys
4
+
5
+ _USE_COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
6
+
7
+ BOLD = "\033[1m" if _USE_COLOR else ""
8
+ DIM = "\033[2m" if _USE_COLOR else ""
9
+ YELLOW = "\033[0;33m" if _USE_COLOR else ""
10
+ GREEN = "\033[0;32m" if _USE_COLOR else ""
11
+ CYAN = "\033[0;36m" if _USE_COLOR else ""
12
+ NC = "\033[0m" if _USE_COLOR else ""
13
+
14
+
15
+ def tier_color(model_id: str) -> str:
16
+ if not _USE_COLOR:
17
+ return ""
18
+ if "opus" in model_id:
19
+ return YELLOW
20
+ if "sonnet" in model_id:
21
+ return CYAN
22
+ return DIM
23
+
24
+
25
+ def fmt_num(n: int) -> str:
26
+ if n >= 1_000_000:
27
+ return f"{n / 1_000_000:.2f}m"
28
+ if n >= 1_000:
29
+ return f"{n / 1_000:.2f}k"
30
+ return str(n)
31
+
32
+
33
+ def fmt_tokens(inp, outp, cache) -> str:
34
+ return f"{fmt_num(inp)}/{fmt_num(outp)}/{fmt_num(cache)}"
35
+
36
+
37
+ def fmt_time(sec: float) -> str:
38
+ s = int(round(sec))
39
+ if s < 60:
40
+ return f"{s}s"
41
+ m, s = divmod(s, 60)
42
+ if m < 60:
43
+ return f"{m}m {s}s" if s else f"{m}m"
44
+ h, m = divmod(m, 60)
45
+ return f"{h}h {m}m" if m else f"{h}h"
46
+
47
+
48
+ def fmt_cost(c: float) -> str:
49
+ return f"${c:.4f}"
50
+
51
+
52
+ def render_cost_table(
53
+ rows: list[tuple],
54
+ total_time_str: str,
55
+ baseline_label: str,
56
+ ) -> None:
57
+ """Print a cost table to stdout.
58
+
59
+ Each row must be a tuple of:
60
+ (label: str, model: str, inp: int, outp: int, cache: int,
61
+ time_str: str, actual: float, vs: float)
62
+
63
+ total_time_str — pre-formatted total time (backends compute it differently)
64
+ baseline_label — label for the rightmost "vs" column header
65
+ """
66
+ agent_col_w = max(len(label) for label, *_ in rows) + 2
67
+ tok_col_w = max(
68
+ max(len(fmt_tokens(i, o, c)) for _, _, i, o, c, *_ in rows),
69
+ len(fmt_tokens(
70
+ sum(i for _, _, i, *_ in rows),
71
+ sum(o for _, _, _, o, *_ in rows),
72
+ sum(c for _, _, _, _, c, *_ in rows),
73
+ ))
74
+ ) + 2
75
+ time_col_w = max(
76
+ max(len(t) for _, _, _, _, _, t, *_ in rows),
77
+ len(total_time_str)
78
+ ) + 2
79
+ W = (agent_col_w, tok_col_w, time_col_w, 12, 12)
80
+
81
+ header = (
82
+ f"{'agent(model)':<{W[0]}}"
83
+ f" {'in/out/cache':<{W[1]}}"
84
+ f" {'time':<{W[2]}}"
85
+ f" {'actual':<{W[3]}}"
86
+ f" {f'vs {baseline_label}':<{W[4]}}"
87
+ )
88
+ print(BOLD + header + NC)
89
+ print(DIM + "-" * len(header) + NC)
90
+
91
+ total_inp = total_outp = total_cache = 0
92
+ total_actual = total_vs = 0.0
93
+
94
+ for label, model, inp, outp, cache, time_str, actual, vs in rows:
95
+ col = tier_color(model)
96
+ print(
97
+ col + f"{label:<{W[0]}}" + NC
98
+ + f" {fmt_tokens(inp, outp, cache):<{W[1]}}"
99
+ + f" {time_str:<{W[2]}}"
100
+ + f" {fmt_cost(actual):<{W[3]}}"
101
+ + f" {fmt_cost(vs):<{W[4]}}"
102
+ )
103
+ total_inp += inp
104
+ total_outp += outp
105
+ total_cache += cache
106
+ total_actual += actual
107
+ total_vs += vs
108
+
109
+ saved_pct = round((1 - total_actual / total_vs) * 100) if total_vs else 0
110
+ total_label = f"TOTAL (saved {saved_pct}%)"
111
+ col = GREEN if total_actual <= 5 else YELLOW
112
+ print(
113
+ col + BOLD
114
+ + f"{total_label:<{W[0]}}"
115
+ + f" {fmt_tokens(total_inp, total_outp, total_cache):<{W[1]}}"
116
+ + f" {total_time_str:<{W[2]}}"
117
+ + f" {fmt_cost(total_actual):<{W[3]}}"
118
+ + f" {fmt_cost(total_vs):<{W[4]}}"
119
+ + NC
120
+ )
@@ -3,10 +3,7 @@ import sqlite3
3
3
  from pathlib import Path
4
4
 
5
5
  from . import _pricing
6
- from ._formatting import (
7
- BOLD, DIM, GREEN, YELLOW, NC,
8
- tier_color, fmt_tokens, fmt_time, fmt_cost,
9
- )
6
+ from ._formatting import fmt_time, render_cost_table
10
7
 
11
8
  DB = Path.home() / ".local/share/opencode/opencode.db"
12
9
 
@@ -63,7 +60,7 @@ def run() -> int:
63
60
  return 0
64
61
 
65
62
  records = [
66
- (agent, model or "unknown", inp or 0, outp or 0, cache or 0, sec or 0)
63
+ (agent, _pricing.normalize_model(model or "unknown"), inp or 0, outp or 0, cache or 0, sec or 0)
67
64
  for agent, model, inp, outp, cache, sec in rows
68
65
  ]
69
66
 
@@ -74,68 +71,13 @@ def run() -> int:
74
71
  for agent, model, inp, outp, cache, sec in records
75
72
  ]
76
73
 
77
- _total_inp = sum(i for _, _, i, *_ in costs)
78
- _total_outp = sum(o for _, _, _, o, *_ in costs)
79
- _total_cache = sum(c for _, _, _, _, c, *_ in costs)
80
- _max_sec = max(s for _, _, _, _, _, s, *_ in costs)
81
- _total_sec = sum(s for _, _, _, _, _, s, *_ in costs)
82
- _total_time = f"{fmt_time(_max_sec)} / {fmt_time(_total_sec)} seq"
83
-
84
- agent_col_w = max(len(f"{a}({m})") for a, m, *_ in costs) + 2
85
- tok_col_w = max(
86
- max(len(fmt_tokens(i, o, c)) for _, _, i, o, c, *_ in costs),
87
- len(fmt_tokens(_total_inp, _total_outp, _total_cache))
88
- ) + 2
89
- time_col_w = max(
90
- max(len(fmt_time(s)) for _, _, _, _, _, s, *_ in costs),
91
- len(_total_time)
92
- ) + 2
93
- W = (agent_col_w, tok_col_w, time_col_w, 12, 12)
94
-
95
- bl_label = _pricing.baseline_label()
96
- header = (
97
- f"{'agent(model)':<{W[0]}}"
98
- f" {'in/out/cache':<{W[1]}}"
99
- f" {'time':<{W[2]}}"
100
- f" {'actual':<{W[3]}}"
101
- f" {f'vs {bl_label}':<{W[4]}}"
102
- )
103
- print(BOLD + header + NC)
104
- print(DIM + "-" * len(header) + NC)
74
+ max_sec = max(s for _, _, _, _, _, s, *_ in costs)
75
+ total_sec = sum(s for _, _, _, _, _, s, *_ in costs)
76
+ total_time_str = f"{fmt_time(max_sec)} / {fmt_time(total_sec)} seq"
105
77
 
106
- total_inp = total_outp = total_cache = 0
107
- total_actual = total_vs = max_sec = total_sec = 0.0
108
-
109
- for agent, model, inp, outp, cache, sec, actual, vs in costs:
110
- label = f"{agent}({model})"
111
- time_str = fmt_time(sec)
112
- col = tier_color(model)
113
- print(
114
- col + f"{label:<{W[0]}}" + NC
115
- + f" {fmt_tokens(inp, outp, cache):<{W[1]}}"
116
- + f" {time_str:<{W[2]}}"
117
- + f" {fmt_cost(actual):<{W[3]}}"
118
- + f" {fmt_cost(vs):<{W[4]}}"
119
- )
120
- total_inp += inp
121
- total_outp += outp
122
- total_cache += cache
123
- total_actual += actual
124
- total_vs += vs
125
- max_sec = max(max_sec, sec)
126
- total_sec += sec
127
-
128
- saved_pct = round((1 - total_actual / total_vs) * 100) if total_vs else 0
129
- total_label = f"TOTAL (saved {saved_pct}%)"
130
- total_time = _total_time
131
- col = GREEN if total_actual <= 5 else YELLOW
132
- print(
133
- col + BOLD
134
- + f"{total_label:<{W[0]}}"
135
- + f" {fmt_tokens(total_inp, total_outp, total_cache):<{W[1]}}"
136
- + f" {total_time:<{W[2]}}"
137
- + f" {fmt_cost(total_actual):<{W[3]}}"
138
- + f" {fmt_cost(total_vs):<{W[4]}}"
139
- + NC
140
- )
78
+ rows = [
79
+ (f"{agent}({model})", model, inp, outp, cache, fmt_time(sec), actual, vs)
80
+ for agent, model, inp, outp, cache, sec, actual, vs in costs
81
+ ]
82
+ render_cost_table(rows, total_time_str, _pricing.baseline_label())
141
83
  return 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-notes
3
- Version: 2.25.0
3
+ Version: 2.26.0
4
4
  Summary: AI agent configuration manager for Claude Code, OpenCode, and Copilot
5
5
  Author-email: Eugene Naumov <min.verkligheten@gmail.com>
6
6
  License-Expression: MIT
@@ -206,6 +206,7 @@ tests/functional/commands/test_regenerate_command.py
206
206
  tests/functional/commands/test_uninstall_command.py
207
207
  tests/functional/commands/test_validate_command.py
208
208
  tests/functional/memory/__init__.py
209
+ tests/functional/memory/test_memory_add_local_backend.py
209
210
  tests/functional/memory/test_memory_command.py
210
211
  tests/functional/scripts/__init__.py
211
212
  tests/functional/scripts/test_release_script.py
@@ -241,6 +242,7 @@ tests/unit/scripts/__init__.py
241
242
  tests/unit/scripts/test_cost_report.py
242
243
  tests/unit/scripts/test_cost_report_scoping.py
243
244
  tests/unit/scripts/test_formatting_tty.py
245
+ tests/unit/scripts/test_opencode_backend_pricing.py
244
246
  tests/unit/scripts/test_time_aggregation.py
245
247
  tests/unit/services/__init__.py
246
248
  tests/unit/services/test_build_functions.py
@@ -0,0 +1,55 @@
1
+ """Regression test: memory add on local backend exits non-zero and prints to stderr.
2
+
3
+ Previously the command printed guidance to stdout and returned silently (exit 0),
4
+ causing callers to believe the note was saved when nothing was written.
5
+ """
6
+ import pytest
7
+
8
+
9
+ class TestMemoryAddLocalBackendExitsNonZero:
10
+ def test_local_backend_add_exits_nonzero(self, tmp_path, monkeypatch, capsys):
11
+ """memory add on local backend must exit non-zero — not silently succeed."""
12
+ mem_dir = tmp_path / "memory"
13
+
14
+ import agent_notes.commands.memory as mem_mod
15
+ monkeypatch.setattr(mem_mod._common, "_load_memory_config", lambda: ("local", mem_dir))
16
+
17
+ from agent_notes.commands.memory import memory
18
+ with pytest.raises(SystemExit) as exc_info:
19
+ memory(action="add", name="My Note", extra=["Body text", "context", "lead"])
20
+
21
+ assert exc_info.value.code != 0, (
22
+ "memory add on local backend must exit non-zero so callers detect the failure"
23
+ )
24
+
25
+ def test_local_backend_add_prints_guidance_to_stderr(self, tmp_path, monkeypatch, capsys):
26
+ """Guidance message must go to stderr, not stdout, so pipelines can separate it."""
27
+ mem_dir = tmp_path / "memory"
28
+
29
+ import agent_notes.commands.memory as mem_mod
30
+ monkeypatch.setattr(mem_mod._common, "_load_memory_config", lambda: ("local", mem_dir))
31
+
32
+ from agent_notes.commands.memory import memory
33
+ with pytest.raises(SystemExit):
34
+ memory(action="add", name="My Note", extra=["Body text", "context", "lead"])
35
+
36
+ captured = capsys.readouterr()
37
+ assert captured.out == "", f"Expected no stdout output, got: {captured.out!r}"
38
+ assert "local storage" in captured.err.lower() or "directly" in captured.err.lower(), (
39
+ f"Expected guidance in stderr, got: {captured.err!r}"
40
+ )
41
+
42
+ def test_local_backend_add_writes_nothing(self, tmp_path, monkeypatch):
43
+ """No file should be created in MEMORY_DIR when local backend is used."""
44
+ mem_dir = tmp_path / "memory"
45
+ mem_dir.mkdir()
46
+
47
+ import agent_notes.commands.memory as mem_mod
48
+ monkeypatch.setattr(mem_mod._common, "_load_memory_config", lambda: ("local", mem_dir))
49
+
50
+ from agent_notes.commands.memory import memory
51
+ with pytest.raises(SystemExit):
52
+ memory(action="add", name="My Note", extra=["Body text", "context", "lead"])
53
+
54
+ written = list(mem_dir.rglob("*"))
55
+ assert written == [], f"Expected no files written, found: {written}"
@@ -0,0 +1,100 @@
1
+ """Regression test: OpenCode backend normalizes dash-form model IDs before pricing lookup.
2
+
3
+ Without normalization, `claude-opus-4-8` fails the `*opus-4.8*` fnmatch glob and
4
+ falls through to the legacy `*opus*` catch-all ($15/M in instead of $5/M in).
5
+ """
6
+ import sqlite3
7
+ import pytest
8
+ from pathlib import Path
9
+ from unittest.mock import patch
10
+
11
+ from agent_notes.scripts import _pricing
12
+
13
+
14
+ # ── helpers ───────────────────────────────────────────────────────────────────
15
+
16
+ def _make_opencode_db(tmp_path: Path, model: str, inp: int = 1_000_000, outp: int = 1_000_000) -> Path:
17
+ """Build a minimal OpenCode SQLite DB with one session and one assistant message."""
18
+ db_path = tmp_path / "opencode.db"
19
+ conn = sqlite3.connect(db_path)
20
+ conn.executescript("""
21
+ CREATE TABLE session (
22
+ id TEXT PRIMARY KEY,
23
+ parent_id TEXT,
24
+ time_created INTEGER
25
+ );
26
+ CREATE TABLE message (
27
+ id TEXT PRIMARY KEY,
28
+ session_id TEXT,
29
+ data TEXT
30
+ );
31
+ """)
32
+
33
+ import json, time
34
+ now_ms = int(time.time() * 1000)
35
+ session_id = "sess-001"
36
+ conn.execute(
37
+ "INSERT INTO session VALUES (?, NULL, ?)",
38
+ (session_id, now_ms),
39
+ )
40
+ # assistant message with the raw dash-form model ID
41
+ msg_data = json.dumps({
42
+ "role": "assistant",
43
+ "modelID": model,
44
+ "tokens": {"input": inp, "output": outp, "cache": {"read": 0}},
45
+ "time": {"created": now_ms - 1000, "completed": now_ms},
46
+ "agent": "lead",
47
+ })
48
+ conn.execute(
49
+ "INSERT INTO message VALUES (?, ?, ?)",
50
+ ("msg-001", session_id, msg_data),
51
+ )
52
+ conn.commit()
53
+ conn.close()
54
+ return db_path
55
+
56
+
57
+ # ── tests ─────────────────────────────────────────────────────────────────────
58
+
59
+ class TestOpencodeBackendNormalizesModel:
60
+ def test_opus_dash_form_uses_opus_rate_not_legacy(self, tmp_path):
61
+ """claude-opus-4-8 (dash form) must resolve to the $5/M Opus rate, not $15/M legacy."""
62
+ opus_price = _pricing.get_price("claude-opus-4.8")
63
+ legacy_price = _pricing.get_price("claude-opus-4.1")
64
+
65
+ # Sanity: confirm we have distinct pricing tiers to differentiate
66
+ assert opus_price["in"] < legacy_price["in"], (
67
+ "Test precondition failed: expected Opus 4.8 cheaper than legacy Opus"
68
+ )
69
+
70
+ # Normalize as the backend should
71
+ normalized = _pricing.normalize_model("claude-opus-4-8")
72
+ assert normalized == "claude-opus-4.8"
73
+
74
+ actual_price = _pricing.get_price(normalized)
75
+ assert actual_price["in"] == opus_price["in"], (
76
+ f"Normalized 'claude-opus-4-8' priced at {actual_price['in']} instead of {opus_price['in']}"
77
+ )
78
+
79
+ def test_opencode_run_uses_normalized_model(self, tmp_path, capsys):
80
+ """Integration: _opencode_backend.run() prices opus-4-8 at the Opus rate end-to-end."""
81
+ from agent_notes.scripts import _opencode_backend
82
+
83
+ db = _make_opencode_db(tmp_path, model="claude-opus-4-8", inp=1_000_000, outp=0)
84
+
85
+ with patch.object(_opencode_backend, "DB", db):
86
+ rc = _opencode_backend.run()
87
+
88
+ assert rc == 0
89
+ out = capsys.readouterr().out
90
+
91
+ # At 1M input tokens:
92
+ # Opus rate ($5/M) → $5.00
93
+ # Legacy rate ($15/M) → $15.00
94
+ # The output must contain the Opus cost, not the legacy cost.
95
+ assert "$5.00" in out or "5.00" in out, (
96
+ f"Expected ~$5.00 (Opus rate) in output, got:\n{out}"
97
+ )
98
+ assert "$15.00" not in out and "15.00" not in out.replace("$5.00", ""), (
99
+ f"Legacy rate ($15.00) appeared in output — normalization not applied:\n{out}"
100
+ )
@@ -1 +0,0 @@
1
- 2.25.0
@@ -1,49 +0,0 @@
1
- """ANSI color constants and shared formatting helpers."""
2
- import os
3
- import sys
4
-
5
- _USE_COLOR = sys.stdout.isatty() and not os.environ.get("NO_COLOR")
6
-
7
- BOLD = "\033[1m" if _USE_COLOR else ""
8
- DIM = "\033[2m" if _USE_COLOR else ""
9
- YELLOW = "\033[0;33m" if _USE_COLOR else ""
10
- GREEN = "\033[0;32m" if _USE_COLOR else ""
11
- CYAN = "\033[0;36m" if _USE_COLOR else ""
12
- NC = "\033[0m" if _USE_COLOR else ""
13
-
14
-
15
- def tier_color(model_id: str) -> str:
16
- if not _USE_COLOR:
17
- return ""
18
- if "opus" in model_id:
19
- return YELLOW
20
- if "sonnet" in model_id:
21
- return CYAN
22
- return DIM
23
-
24
-
25
- def fmt_num(n: int) -> str:
26
- if n >= 1_000_000:
27
- return f"{n / 1_000_000:.2f}m"
28
- if n >= 1_000:
29
- return f"{n / 1_000:.2f}k"
30
- return str(n)
31
-
32
-
33
- def fmt_tokens(inp, outp, cache) -> str:
34
- return f"{fmt_num(inp)}/{fmt_num(outp)}/{fmt_num(cache)}"
35
-
36
-
37
- def fmt_time(sec: float) -> str:
38
- s = int(round(sec))
39
- if s < 60:
40
- return f"{s}s"
41
- m, s = divmod(s, 60)
42
- if m < 60:
43
- return f"{m}m {s}s" if s else f"{m}m"
44
- h, m = divmod(m, 60)
45
- return f"{h}h {m}m" if m else f"{h}h"
46
-
47
-
48
- def fmt_cost(c: float) -> str:
49
- return f"${c:.4f}"
File without changes
File without changes