interlatch 0.29.2__tar.gz → 0.29.3__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 (531) hide show
  1. {interlatch-0.29.2 → interlatch-0.29.3}/CHANGELOG.md +13 -0
  2. {interlatch-0.29.2 → interlatch-0.29.3}/PKG-INFO +1 -1
  3. interlatch-0.29.3/changelog.d/analysis-usage-footer.md +4 -0
  4. {interlatch-0.29.2 → interlatch-0.29.3}/docs/configuration.md +1 -0
  5. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/configuration.md +1 -0
  6. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/queue.py +24 -0
  7. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/application/workflows.py +10 -5
  8. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/config.py +4 -0
  9. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/i18n/ja.py +3 -0
  10. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/status.py +50 -0
  11. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/routes.py +1 -0
  12. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/app.css +28 -0
  13. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/app.js +88 -0
  14. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/index.html +2 -0
  15. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/ja.js +30 -0
  16. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_queue.py +60 -0
  17. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_route_matrix.py +1 -0
  18. interlatch-0.29.2/changelog.d/hub-glossary-members.md +0 -4
  19. interlatch-0.29.2/changelog.d/hub-project-page.md +0 -6
  20. {interlatch-0.29.2 → interlatch-0.29.3}/.github/dependabot.yml +0 -0
  21. {interlatch-0.29.2 → interlatch-0.29.3}/.github/windows-ja-qa/driver.ps1 +0 -0
  22. {interlatch-0.29.2 → interlatch-0.29.3}/.github/windows-ja-qa/ja_check.py +0 -0
  23. {interlatch-0.29.2 → interlatch-0.29.3}/.github/windows-ja-qa/report.py +0 -0
  24. {interlatch-0.29.2 → interlatch-0.29.3}/.github/windows-ja-qa/shots.py +0 -0
  25. {interlatch-0.29.2 → interlatch-0.29.3}/.github/windows-ja-qa/user.ps1 +0 -0
  26. {interlatch-0.29.2 → interlatch-0.29.3}/.github/workflows/changelog.yml +0 -0
  27. {interlatch-0.29.2 → interlatch-0.29.3}/.github/workflows/ci.yml +0 -0
  28. {interlatch-0.29.2 → interlatch-0.29.3}/.github/workflows/docs.yml +0 -0
  29. {interlatch-0.29.2 → interlatch-0.29.3}/.github/workflows/release.yml +0 -0
  30. {interlatch-0.29.2 → interlatch-0.29.3}/.github/workflows/windows-ja-qa.yml +0 -0
  31. {interlatch-0.29.2 → interlatch-0.29.3}/.gitignore +0 -0
  32. {interlatch-0.29.2 → interlatch-0.29.3}/.pre-commit-config.yaml +0 -0
  33. {interlatch-0.29.2 → interlatch-0.29.3}/.python-version +0 -0
  34. {interlatch-0.29.2 → interlatch-0.29.3}/CONTRIBUTING.md +0 -0
  35. {interlatch-0.29.2 → interlatch-0.29.3}/LICENSE +0 -0
  36. {interlatch-0.29.2 → interlatch-0.29.3}/README.ja.md +0 -0
  37. {interlatch-0.29.2 → interlatch-0.29.3}/README.md +0 -0
  38. {interlatch-0.29.2 → interlatch-0.29.3}/ROADMAP.ja.md +0 -0
  39. {interlatch-0.29.2 → interlatch-0.29.3}/ROADMAP.md +0 -0
  40. {interlatch-0.29.2 → interlatch-0.29.3}/SECURITY.md +0 -0
  41. {interlatch-0.29.2 → interlatch-0.29.3}/careers/README.md +0 -0
  42. {interlatch-0.29.2 → interlatch-0.29.3}/careers/boards.json +0 -0
  43. {interlatch-0.29.2 → interlatch-0.29.3}/careers/pipeline.py +0 -0
  44. {interlatch-0.29.2 → interlatch-0.29.3}/changelog.d/README.md +0 -0
  45. {interlatch-0.29.2 → interlatch-0.29.3}/dev.sh +0 -0
  46. {interlatch-0.29.2 → interlatch-0.29.3}/docker/.dockerignore +0 -0
  47. {interlatch-0.29.2 → interlatch-0.29.3}/docker/.env.example +0 -0
  48. {interlatch-0.29.2 → interlatch-0.29.3}/docker/Caddyfile +0 -0
  49. {interlatch-0.29.2 → interlatch-0.29.3}/docker/Dockerfile +0 -0
  50. {interlatch-0.29.2 → interlatch-0.29.3}/docker/compose.yaml +0 -0
  51. {interlatch-0.29.2 → interlatch-0.29.3}/docker/wheels/.gitignore +0 -0
  52. {interlatch-0.29.2 → interlatch-0.29.3}/docs/README.md +0 -0
  53. {interlatch-0.29.2 → interlatch-0.29.3}/docs/_site/hooks.py +0 -0
  54. {interlatch-0.29.2 → interlatch-0.29.3}/docs/analysis.md +0 -0
  55. {interlatch-0.29.2 → interlatch-0.29.3}/docs/cli.md +0 -0
  56. {interlatch-0.29.2 → interlatch-0.29.3}/docs/dashboard.md +0 -0
  57. {interlatch-0.29.2 → interlatch-0.29.3}/docs/demo/make_demo.py +0 -0
  58. {interlatch-0.29.2 → interlatch-0.29.3}/docs/demo/record_demo.py +0 -0
  59. {interlatch-0.29.2 → interlatch-0.29.3}/docs/design/innerlatch.md +0 -0
  60. {interlatch-0.29.2 → interlatch-0.29.3}/docs/design/ja/innerlatch.md +0 -0
  61. {interlatch-0.29.2 → interlatch-0.29.3}/docs/design/ja/learning.md +0 -0
  62. {interlatch-0.29.2 → interlatch-0.29.3}/docs/design/learning.md +0 -0
  63. {interlatch-0.29.2 → interlatch-0.29.3}/docs/development.md +0 -0
  64. {interlatch-0.29.2 → interlatch-0.29.3}/docs/devices.md +0 -0
  65. {interlatch-0.29.2 → interlatch-0.29.3}/docs/diagrams/analysis.excalidraw.svg +0 -0
  66. {interlatch-0.29.2 → interlatch-0.29.3}/docs/diagrams/architecture.excalidraw.svg +0 -0
  67. {interlatch-0.29.2 → interlatch-0.29.3}/docs/docker.md +0 -0
  68. {interlatch-0.29.2 → interlatch-0.29.3}/docs/faq.md +0 -0
  69. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/demo.gif +0 -0
  70. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/group-connections.png +0 -0
  71. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/hero.png +0 -0
  72. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/home.png +0 -0
  73. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/job-goal.png +0 -0
  74. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/map.png +0 -0
  75. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/palette.png +0 -0
  76. {interlatch-0.29.2 → interlatch-0.29.3}/docs/images/session.png +0 -0
  77. {interlatch-0.29.2 → interlatch-0.29.3}/docs/install.md +0 -0
  78. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/README.md +0 -0
  79. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/analysis.md +0 -0
  80. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/cli.md +0 -0
  81. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/dashboard.md +0 -0
  82. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/development.md +0 -0
  83. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/devices.md +0 -0
  84. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/docker.md +0 -0
  85. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/faq.md +0 -0
  86. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/install.md +0 -0
  87. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/join-a-hub.md +0 -0
  88. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/mcp.md +0 -0
  89. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/moving-from-chronicle.md +0 -0
  90. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/party.md +0 -0
  91. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/postgres.md +0 -0
  92. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/privacy.md +0 -0
  93. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/profiles.md +0 -0
  94. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/sources.md +0 -0
  95. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/suggestions.md +0 -0
  96. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/troubleshooting.md +0 -0
  97. {interlatch-0.29.2 → interlatch-0.29.3}/docs/ja/vscode.md +0 -0
  98. {interlatch-0.29.2 → interlatch-0.29.3}/docs/join-a-hub.md +0 -0
  99. {interlatch-0.29.2 → interlatch-0.29.3}/docs/mcp.md +0 -0
  100. {interlatch-0.29.2 → interlatch-0.29.3}/docs/moving-from-chronicle.md +0 -0
  101. {interlatch-0.29.2 → interlatch-0.29.3}/docs/party.md +0 -0
  102. {interlatch-0.29.2 → interlatch-0.29.3}/docs/postgres.md +0 -0
  103. {interlatch-0.29.2 → interlatch-0.29.3}/docs/privacy.md +0 -0
  104. {interlatch-0.29.2 → interlatch-0.29.3}/docs/profiles.md +0 -0
  105. {interlatch-0.29.2 → interlatch-0.29.3}/docs/sources.md +0 -0
  106. {interlatch-0.29.2 → interlatch-0.29.3}/docs/suggestions.md +0 -0
  107. {interlatch-0.29.2 → interlatch-0.29.3}/docs/troubleshooting.md +0 -0
  108. {interlatch-0.29.2 → interlatch-0.29.3}/docs/vscode.md +0 -0
  109. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/.firebaserc +0 -0
  110. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/.gitignore +0 -0
  111. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/README.md +0 -0
  112. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/firebase.json +0 -0
  113. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/firestore.indexes.json +0 -0
  114. {interlatch-0.29.2 → interlatch-0.29.3}/leaderboard/firestore.rules +0 -0
  115. {interlatch-0.29.2 → interlatch-0.29.3}/mkdocs.yml +0 -0
  116. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/README.md +0 -0
  117. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/art.json +0 -0
  118. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/filing.html +0 -0
  119. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/loops.json +0 -0
  120. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/loops.py +0 -0
  121. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/members.js +0 -0
  122. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/render.html +0 -0
  123. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/render.py +0 -0
  124. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/scenes-more.js +0 -0
  125. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/icons3d/scenes.html +0 -0
  126. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/Chronicle.icns +0 -0
  127. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/Interlatch.spec +0 -0
  128. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/build.sh +0 -0
  129. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/entitlements.plist +0 -0
  130. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/icon-3d.webp +0 -0
  131. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/icon-blueprint-prompt.md +0 -0
  132. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/icon-blueprint.webp +0 -0
  133. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/icon.png +0 -0
  134. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/interlatch_app.py +0 -0
  135. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/macos/make_icon.py +0 -0
  136. {interlatch-0.29.2 → interlatch-0.29.3}/packaging/release.py +0 -0
  137. {interlatch-0.29.2 → interlatch-0.29.3}/pyproject.toml +0 -0
  138. {interlatch-0.29.2 → interlatch-0.29.3}/src/chronicle/__init__.py +0 -0
  139. {interlatch-0.29.2 → interlatch-0.29.3}/src/chronicle/__main__.py +0 -0
  140. {interlatch-0.29.2 → interlatch-0.29.3}/src/chronicle/windows.py +0 -0
  141. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/__init__.py +0 -0
  142. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/__main__.py +0 -0
  143. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/__init__.py +0 -0
  144. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/digest.py +0 -0
  145. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/language.py +0 -0
  146. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/__init__.py +0 -0
  147. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/api.py +0 -0
  148. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/base.py +0 -0
  149. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/bob.py +0 -0
  150. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/claude.py +0 -0
  151. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/codex.py +0 -0
  152. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/factory.py +0 -0
  153. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/off.py +0 -0
  154. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/opencode.py +0 -0
  155. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/process.py +0 -0
  156. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/replies.py +0 -0
  157. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/runners/settings.py +0 -0
  158. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/screen.py +0 -0
  159. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/session.py +0 -0
  160. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/analysis/worker.py +0 -0
  161. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/application/__init__.py +0 -0
  162. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/application/profiles.py +0 -0
  163. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/application/records.py +0 -0
  164. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/application/search.py +0 -0
  165. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/artifacts/__init__.py +0 -0
  166. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/artifacts/files.py +0 -0
  167. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/artifacts/queries.py +0 -0
  168. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/cli.py +0 -0
  169. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/__init__.py +0 -0
  170. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/agents.py +0 -0
  171. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/artifact_records.py +0 -0
  172. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/budget.py +0 -0
  173. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/diagrams.py +0 -0
  174. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/i18n/__init__.py +0 -0
  175. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/machine.py +0 -0
  176. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/paths.py +0 -0
  177. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/pricing.py +0 -0
  178. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/process.py +0 -0
  179. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/profiles.py +0 -0
  180. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/redact.py +0 -0
  181. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/sessions.py +0 -0
  182. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/core/util.py +0 -0
  183. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/__init__.py +0 -0
  184. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/analyses.py +0 -0
  185. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/archive.py +0 -0
  186. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/chat_import.py +0 -0
  187. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/deletion.py +0 -0
  188. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/history.py +0 -0
  189. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/memory.py +0 -0
  190. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/persistence.py +0 -0
  191. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/report.py +0 -0
  192. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/ingestion/service.py +0 -0
  193. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/__init__.py +0 -0
  194. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/__init__.py +0 -0
  195. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/__init__.py +0 -0
  196. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/archive.py +0 -0
  197. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/hubs.py +0 -0
  198. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/knowledge.py +0 -0
  199. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/operations.py +0 -0
  200. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/recording.py +0 -0
  201. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/setup.py +0 -0
  202. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/commands/sharing.py +0 -0
  203. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/common.py +0 -0
  204. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/main.py +0 -0
  205. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/cli/parser.py +0 -0
  206. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/exports/__init__.py +0 -0
  207. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/exports/diagrams.py +0 -0
  208. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/exports/markdown.py +0 -0
  209. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/exports/sessions.py +0 -0
  210. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/exports/vault.py +0 -0
  211. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/mcp.py +0 -0
  212. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/__init__.py +0 -0
  213. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/__init__.py +0 -0
  214. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/artifacts.py +0 -0
  215. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/common.py +0 -0
  216. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/devices.py +0 -0
  217. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/hub_admin.py +0 -0
  218. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/hubs.py +0 -0
  219. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/knowledge.py +0 -0
  220. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/leaderboard.py +0 -0
  221. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/party.py +0 -0
  222. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/projects.py +0 -0
  223. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/sessions.py +0 -0
  224. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/settings.py +0 -0
  225. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/sources.py +0 -0
  226. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/storage.py +0 -0
  227. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/api/team.py +0 -0
  228. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/app.py +0 -0
  229. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/assets.py +0 -0
  230. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/context.py +0 -0
  231. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/handler.py +0 -0
  232. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/jobs.py +0 -0
  233. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/interfaces/web/server.py +0 -0
  234. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/__init__.py +0 -0
  235. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/arena.py +0 -0
  236. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/bag.py +0 -0
  237. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/cases.py +0 -0
  238. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/formation.py +0 -0
  239. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/freshness.py +0 -0
  240. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/friction.py +0 -0
  241. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/gates.py +0 -0
  242. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/glossary.py +0 -0
  243. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/group_graph.py +0 -0
  244. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/hero.py +0 -0
  245. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/instructions.py +0 -0
  246. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/interview.py +0 -0
  247. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/jobprep.py +0 -0
  248. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/jobs.py +0 -0
  249. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/ladder.py +0 -0
  250. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/learning.py +0 -0
  251. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/market.json +0 -0
  252. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/party.py +0 -0
  253. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/reviews.py +0 -0
  254. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/roadmap.json +0 -0
  255. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/study.py +0 -0
  256. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/suggest.py +0 -0
  257. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/synthesize.py +0 -0
  258. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/knowledge/weekly.py +0 -0
  259. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/__init__.py +0 -0
  260. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/groups.py +0 -0
  261. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/hubgroups.py +0 -0
  262. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/labels.py +0 -0
  263. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/owners.py +0 -0
  264. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/progress.py +0 -0
  265. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/resume.py +0 -0
  266. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/routing.py +0 -0
  267. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/scope.py +0 -0
  268. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/systems.py +0 -0
  269. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/projects/team.py +0 -0
  270. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/__init__.py +0 -0
  271. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/connectors.py +0 -0
  272. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/container.py +0 -0
  273. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/feedback.py +0 -0
  274. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/hooks.py +0 -0
  275. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/install.py +0 -0
  276. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/migrate.py +0 -0
  277. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/notify.py +0 -0
  278. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/platforms/__init__.py +0 -0
  279. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/platforms/desktop.py +0 -0
  280. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/platforms/menubar.py +0 -0
  281. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/platforms/windows.py +0 -0
  282. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/statusline.py +0 -0
  283. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/tailnet.py +0 -0
  284. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/runtime/update.py +0 -0
  285. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/__init__.py +0 -0
  286. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/access.py +0 -0
  287. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/arena.py +0 -0
  288. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/client.py +0 -0
  289. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/identity.py +0 -0
  290. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/leaderboard.py +0 -0
  291. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/market.py +0 -0
  292. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/membership.py +0 -0
  293. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/news.py +0 -0
  294. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/people.py +0 -0
  295. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/projects.py +0 -0
  296. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/protocol.py +0 -0
  297. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/pull.py +0 -0
  298. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/push.py +0 -0
  299. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sharing/receiver.py +0 -0
  300. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/__init__.py +0 -0
  301. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/antigravity.py +0 -0
  302. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/bob.py +0 -0
  303. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/builder.py +0 -0
  304. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/chatgpt.py +0 -0
  305. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/chats.py +0 -0
  306. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/claude/__init__.py +0 -0
  307. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/claude/export.py +0 -0
  308. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/claude/files.py +0 -0
  309. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/claude/parser.py +0 -0
  310. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/codex/__init__.py +0 -0
  311. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/codex/cloud.py +0 -0
  312. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/codex/parser.py +0 -0
  313. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/copilot.py +0 -0
  314. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/discovery.py +0 -0
  315. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/opencode.py +0 -0
  316. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/sources/prompts.py +0 -0
  317. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/__init__.py +0 -0
  318. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/fts.py +0 -0
  319. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/kv.py +0 -0
  320. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/migrations.py +0 -0
  321. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/mirror.py +0 -0
  322. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/postgres.py +0 -0
  323. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/schema.py +0 -0
  324. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/sqlite.py +0 -0
  325. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/storage/team.py +0 -0
  326. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/AGENT-ICONS.txt +0 -0
  327. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/PLEX-OFL.txt +0 -0
  328. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/adventure.js +0 -0
  329. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-antigravity.svg +0 -0
  330. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-bob.svg +0 -0
  331. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-chatgpt.svg +0 -0
  332. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-claude-ai.svg +0 -0
  333. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-claude.svg +0 -0
  334. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-codex.svg +0 -0
  335. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-copilot.svg +0 -0
  336. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/agent-opencode.svg +0 -0
  337. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-book.webp +0 -0
  338. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-camera.webp +0 -0
  339. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-chip.webp +0 -0
  340. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-clock.webp +0 -0
  341. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-cloud.webp +0 -0
  342. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-coins.webp +0 -0
  343. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-container.webp +0 -0
  344. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-dashboard.webp +0 -0
  345. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-dex.webp +0 -0
  346. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-document.webp +0 -0
  347. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-flag.webp +0 -0
  348. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-folder.webp +0 -0
  349. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-gateway.webp +0 -0
  350. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-gears.webp +0 -0
  351. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-glitch.webp +0 -0
  352. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-globe.webp +0 -0
  353. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-laptop.webp +0 -0
  354. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-log.webp +0 -0
  355. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-lumi.webp +0 -0
  356. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-magnifier.webp +0 -0
  357. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-pip.webp +0 -0
  358. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-plug.webp +0 -0
  359. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-rack.webp +0 -0
  360. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/art-tink.webp +0 -0
  361. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/battle.js +0 -0
  362. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/boot.js +0 -0
  363. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/favicon.png +0 -0
  364. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/field.js +0 -0
  365. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/i18n.js +0 -0
  366. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/icon-180.png +0 -0
  367. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/icon-192.png +0 -0
  368. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/icon-512.png +0 -0
  369. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/icon.png +0 -0
  370. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/innerlatch.css +0 -0
  371. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/innerlatch.js +0 -0
  372. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/learning.js +0 -0
  373. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/manifest.webmanifest +0 -0
  374. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/panel.css +0 -0
  375. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/panel.html +0 -0
  376. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/panel.js +0 -0
  377. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-mono-400.woff2 +0 -0
  378. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-mono-500.woff2 +0 -0
  379. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-mono-600.woff2 +0 -0
  380. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-sans-400.woff2 +0 -0
  381. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-sans-500.woff2 +0 -0
  382. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-sans-600.woff2 +0 -0
  383. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/plex-sans-700.woff2 +0 -0
  384. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/rough.js +0 -0
  385. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/scene.js +0 -0
  386. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/signin.js +0 -0
  387. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/web/sprites.js +0 -0
  388. {interlatch-0.29.2 → interlatch-0.29.3}/src/interlatch/windows.py +0 -0
  389. {interlatch-0.29.2 → interlatch-0.29.3}/tests/antigravity_fixture.py +0 -0
  390. {interlatch-0.29.2 → interlatch-0.29.3}/tests/codex_fixture.py +0 -0
  391. {interlatch-0.29.2 → interlatch-0.29.3}/tests/conftest.py +0 -0
  392. {interlatch-0.29.2 → interlatch-0.29.3}/tests/copilot_fixture.py +0 -0
  393. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.21.3/chronicle.db +0 -0
  394. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.21.3/expected.json +0 -0
  395. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.27.8/chronicle.db +0 -0
  396. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.27.8/expected.json +0 -0
  397. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.5.0/chronicle.db +0 -0
  398. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/0.5.0/expected.json +0 -0
  399. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/README.md +0 -0
  400. {interlatch-0.29.2 → interlatch-0.29.3}/tests/fixtures/db/make_fixture.py +0 -0
  401. {interlatch-0.29.2 → interlatch-0.29.3}/tests/friction_fixture.py +0 -0
  402. {interlatch-0.29.2 → interlatch-0.29.3}/tests/golden.py +0 -0
  403. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/cli.json +0 -0
  404. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/diagram.json +0 -0
  405. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/antigravity.json +0 -0
  406. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/bob.json +0 -0
  407. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/claude_code.json +0 -0
  408. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/codex.json +0 -0
  409. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/copilot.json +0 -0
  410. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/parsers/opencode.json +0 -0
  411. {interlatch-0.29.2 → interlatch-0.29.3}/tests/goldens/systems.json +0 -0
  412. {interlatch-0.29.2 → interlatch-0.29.3}/tests/hubs_fixture.py +0 -0
  413. {interlatch-0.29.2 → interlatch-0.29.3}/tests/opencode_fixture.py +0 -0
  414. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_adventure_web.py +0 -0
  415. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_analysis.py +0 -0
  416. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_antigravity.py +0 -0
  417. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_architecture.py +0 -0
  418. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_arena.py +0 -0
  419. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_artifacts.py +0 -0
  420. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_bag.py +0 -0
  421. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_battle_web.py +0 -0
  422. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_bob_analysis.py +0 -0
  423. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_boot_web.py +0 -0
  424. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_careers.py +0 -0
  425. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_case_files.py +0 -0
  426. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_cases.py +0 -0
  427. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_chat_import.py +0 -0
  428. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_cli_pins.py +0 -0
  429. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_codex.py +0 -0
  430. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_codex_analysis.py +0 -0
  431. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_codex_cloud.py +0 -0
  432. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_container.py +0 -0
  433. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_copilot_bob.py +0 -0
  434. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_db_compat.py +0 -0
  435. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_dedicated_hub.py +0 -0
  436. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_dedupe.py +0 -0
  437. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_desktop.py +0 -0
  438. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_diagram.py +0 -0
  439. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_doctor_agents.py +0 -0
  440. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_ee_boundary.py +0 -0
  441. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_feedback.py +0 -0
  442. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_field_web.py +0 -0
  443. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_fix_effect.py +0 -0
  444. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_formation.py +0 -0
  445. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_friction.py +0 -0
  446. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_game_mode.py +0 -0
  447. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_gates.py +0 -0
  448. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_glossary.py +0 -0
  449. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_group_graph.py +0 -0
  450. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_group_sharing.py +0 -0
  451. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_guidance.py +0 -0
  452. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hero.py +0 -0
  453. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_home_agents.py +0 -0
  454. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub.py +0 -0
  455. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_accept.py +0 -0
  456. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_cookie.py +0 -0
  457. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_groups.py +0 -0
  458. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_inventory_scope.py +0 -0
  459. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_leave_join.py +0 -0
  460. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_news.py +0 -0
  461. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_payload_privacy.py +0 -0
  462. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_projects.py +0 -0
  463. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_projects_listed.py +0 -0
  464. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_purge.py +0 -0
  465. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_send_problems.py +0 -0
  466. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_hub_sharing_race.py +0 -0
  467. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_i18n_web.py +0 -0
  468. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_ingest.py +0 -0
  469. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_instructions.py +0 -0
  470. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_integration.py +0 -0
  471. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_interview.py +0 -0
  472. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_jobprep.py +0 -0
  473. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_jobs.py +0 -0
  474. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_knowledge_freshness.py +0 -0
  475. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_ladder.py +0 -0
  476. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_language.py +0 -0
  477. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_leaderboard.py +0 -0
  478. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_learning_glance.py +0 -0
  479. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_learning_interests.py +0 -0
  480. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_learning_web.py +0 -0
  481. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_leave_project.py +0 -0
  482. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_legacy_package.py +0 -0
  483. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_map_goldens.py +0 -0
  484. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_market.py +0 -0
  485. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_mcp_resume.py +0 -0
  486. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_menubar.py +0 -0
  487. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_migrate.py +0 -0
  488. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_mirror.py +0 -0
  489. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_notify.py +0 -0
  490. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_opencode.py +0 -0
  491. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_opencode_analysis.py +0 -0
  492. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_parser.py +0 -0
  493. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_parser_goldens.py +0 -0
  494. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_party.py +0 -0
  495. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_party_map.py +0 -0
  496. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_people.py +0 -0
  497. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_people_hub.py +0 -0
  498. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_people_web.py +0 -0
  499. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_profile_hubs.py +0 -0
  500. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_profile_scope.py +0 -0
  501. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_profiles.py +0 -0
  502. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_project_groups.py +0 -0
  503. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_project_progress.py +0 -0
  504. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_project_team.py +0 -0
  505. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_providers.py +0 -0
  506. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_regressions.py +0 -0
  507. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_release.py +0 -0
  508. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_roadmap_catalog.py +0 -0
  509. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_screen.py +0 -0
  510. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_security.py +0 -0
  511. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_session_context_budget.py +0 -0
  512. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_setup.py +0 -0
  513. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_statusline.py +0 -0
  514. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_study.py +0 -0
  515. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_suggest.py +0 -0
  516. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_suggest_cli.py +0 -0
  517. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_systems.py +0 -0
  518. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_team.py +0 -0
  519. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_team_home.py +0 -0
  520. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_update.py +0 -0
  521. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_weekly_reanalysis.py +0 -0
  522. {interlatch-0.29.2 → interlatch-0.29.3}/tests/test_windows.py +0 -0
  523. {interlatch-0.29.2 → interlatch-0.29.3}/tests/windows_smoke.py +0 -0
  524. {interlatch-0.29.2 → interlatch-0.29.3}/uv.lock +0 -0
  525. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/.vscodeignore +0 -0
  526. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/LICENSE +0 -0
  527. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/README.md +0 -0
  528. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/extension.js +0 -0
  529. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/media/icon.png +0 -0
  530. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/media/interlatch.svg +0 -0
  531. {interlatch-0.29.2 → interlatch-0.29.3}/vscode-extension/package.json +0 -0
@@ -3,6 +3,19 @@
3
3
  <!-- Don't add lines here: a pull request puts its line in a new file in changelog.d/ (see changelog.d/README.md),
4
4
  and each release moves the files it took into a dated section below. -->
5
5
 
6
+ ## 0.29.2 (2026-10-29)
7
+
8
+ - **A project's page on a team hub:** the team's knowledge leads. Each knowledge base entry and lesson names whose
9
+ sessions taught it, lessons two people found separately sit side by side, **Since your last visit** lists new and
10
+ replaced lessons, **Start here** greets someone new to the project, **People** shows who has worked on what (never
11
+ a count per person), and **Who's working on it** draws each person's sessions over eight weeks. Tokens and cost
12
+ move to an admin-only Usage tab, and the Systems map is no longer offered where it would be empty or refused
13
+ ([On a team hub](docs/dashboard.md#on-a-team-hub)).
14
+ - **A project's glossary and mindmap on a team hub:** a project's page there has a **Glossary & map** tab: its terms
15
+ as a list or a mindmap. Teammates limited to some projects now see it too, with the terms their projects use and
16
+ what their projects say of them, never what another project wrote
17
+ ([On a team hub](docs/dashboard.md#on-a-team-hub)).
18
+
6
19
  ## 0.29.1 (2026-10-25)
7
20
 
8
21
  - **The interview, your job goal's final boss:** three questions a day, written from the job description and from
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: interlatch
3
- Version: 0.29.2
3
+ Version: 0.29.3
4
4
  Summary: Shared memory for your coding agents: records every session (Claude Code, Codex, Copilot, Bob, Antigravity, OpenCode) locally and keeps what they learned, each lesson linked to the session it came from.
5
5
  Project-URL: Homepage, https://interlatch.com
6
6
  Project-URL: Documentation, https://interlatch.com/docs/
@@ -0,0 +1,4 @@
1
+ - **Analysis usage on every page:** the status bar shows your Claude plan's session (5hr) and weekly (7 day) usage, as
2
+ Claude Code names them, and what analysis spent today. Click it to see what's left and when each resets, this month's
3
+ and all-time spend, and to set the analysis limits: sessions per run, the budget per call, and a new cap that pauses
4
+ automatic analysis at a percent of your plan limit ([Configuration](docs/configuration.md)).
@@ -35,6 +35,7 @@ the file. Changes apply on the next sync or worker run. `INTERLATCH_HOME` reloca
35
35
  | `idle_minutes` | `20` | a session must have ended or been idle this long before it is analyzed |
36
36
  | `min_prompts` | `1` | sessions with fewer human prompts are skipped |
37
37
  | `max_per_run` / `concurrency` | `6` / `2` | analyses per 15-minute run, and parallel analysis processes |
38
+ | `plan_limit_pct` | `0` | pause automatic analysis while your Claude plan's session (5hr) or weekly (7 day) limit is at or above this percent (Claude Code as the analyzer, with the status-line collector); `0` never pauses. Set it, and the limits above, from the usage chip in the dashboard's status bar |
38
39
  | `backfill` | `true` | also analyze sessions recorded before install (newest first) |
39
40
  | `chunk_chars` | `150000` | characters of condensed transcript per call; longer sessions are map-reduced |
40
41
  | `timeout_seconds` | `900` | wall-clock limit per call |
@@ -35,6 +35,7 @@
35
35
  | `idle_minutes` | `20` | 分析されるには、セッションが終了しているか、この時間アイドルである必要があります |
36
36
  | `min_prompts` | `1` | 人間のプロンプトがこれより少ないセッションはスキップされます |
37
37
  | `max_per_run` / `concurrency` | `6` / `2` | 15 分ごとの実行 1 回あたりの分析数と、並列に動かす分析プロセスの数 |
38
+ | `plan_limit_pct` | `0` | Claude プランのセッション(5 時間)または週間(7 日)の上限がこの割合以上のあいだ、自動分析を止めます(分析エンジンが Claude Code で、ステータスラインの収集があるとき)。`0` は止めません。これと上の上限は、ダッシュボードのステータスバーの使用量から設定できます |
38
39
  | `backfill` | `true` | インストール前に記録されたセッションも分析する(新しい順) |
39
40
  | `chunk_chars` | `150000` | 1 回の呼び出しに渡す、まとめたトランスクリプトの文字数。これより長いセッションは map-reduce で処理します |
40
41
  | `timeout_seconds` | `900` | 呼び出し 1 回あたりの実時間の上限 |
@@ -95,12 +95,36 @@ def queue_block(conn, cfg: Config, runner: Runner | None = None) -> str | None:
95
95
  return tr("analysis is off (record only): pick an analyzer in Status › Analysis to analyze sessions")
96
96
  if not cfg.analysis.auto:
97
97
  return tr("automatic analysis is off (analysis.auto); analyze it from its page or with `interlatch analyze`")
98
+ over = plan_over_limit(conn, cfg)
99
+ if over:
100
+ return tr("your Claude plan's {window} limit is at {pct}%, at or above the {cap}% you set; it resumes after {when}",
101
+ window=tr(WINDOW_LABEL.get(over["window"], over["window"])), pct=round(over["used_pct"]),
102
+ cap=cfg.analysis.plan_limit_pct, when=local_str(over["resets_at"], "%m-%d %H:%M"))
98
103
  runner = runner or make_runner(cfg)
99
104
  if not runner.available():
100
105
  return tr("{reason}, so nothing can be analyzed", reason=runner.unavailable_reason())
101
106
  return None
102
107
 
103
108
 
109
+ WINDOW_LABEL = {"five_hour": "session (5hr)", "seven_day": "weekly (7 day)", "spend_limit": "spend"} # Claude Code's names
110
+
111
+
112
+ def plan_over_limit(conn, cfg: Config) -> dict | None:
113
+ """The Claude plan window (statusline.py) at or above analysis.plan_limit_pct, while Claude Code analyzes: its
114
+ name, used_pct and resets_at. None when the cap is off, another analyzer runs, or no window is that full."""
115
+ cap = cfg.analysis.plan_limit_pct
116
+ if not cap or cap <= 0 or cfg.analysis.backend != "claude":
117
+ return None
118
+ from ..runtime.statusline import plan_usage
119
+
120
+ windows = (plan_usage(conn) or {}).get("limits") or {}
121
+ over = [(name, w) for name, w in windows.items() if name in WINDOW_LABEL and (w.get("used_pct") or 0) >= cap]
122
+ if not over:
123
+ return None
124
+ name, w = max(over, key=lambda x: x[1]["resets_at"]) # the one that frees up last
125
+ return {"window": name, "used_pct": w["used_pct"], "resets_at": w["resets_at"]}
126
+
127
+
104
128
  def count_pending(conn, cfg: Config) -> dict:
105
129
  """The analysis queue: `ready` now, `queued` (everything that will be analyzed by itself, ready included),
106
130
  `held` (needs a change first), each with a count per reason, and `block` when the whole queue is stopped."""
@@ -9,7 +9,7 @@ import sqlite3
9
9
  from collections.abc import Callable
10
10
  from datetime import timedelta
11
11
 
12
- from ..analysis.queue import PAUSE_KEY, pending_sessions
12
+ from ..analysis.queue import PAUSE_KEY, pending_sessions, plan_over_limit
13
13
  from ..analysis.runners.base import LLMError, Runner, UsageLimitError
14
14
  from ..analysis.runners.factory import make_runner
15
15
  from ..analysis.worker import WorkReport, analyze_many
@@ -92,7 +92,12 @@ def _run_locked(cfg: Config, conn, report: WorkReport, *, session_ids, max_analy
92
92
  conn.commit()
93
93
  runner = make_runner(cfg)
94
94
  paused = kv_get(conn, PAUSE_KEY)
95
- if analyze and (cfg.analysis.auto or session_ids or force):
95
+ # at the plan limit the user set (analysis.plan_limit_pct): runs on their own wait; sessions asked for still run
96
+ capped = None if session_ids or force else plan_over_limit(conn, cfg)
97
+ if capped:
98
+ report.note = (f"Claude plan's {capped['window']} limit at {capped['used_pct']:.0f}% "
99
+ f"(analysis.plan_limit_pct = {cfg.analysis.plan_limit_pct}); analysis skipped")
100
+ if analyze and not capped and (cfg.analysis.auto or session_ids or force):
96
101
  if paused and paused > to_iso(utcnow()) and not session_ids:
97
102
  report.paused_until = paused
98
103
  elif not runner.available():
@@ -102,9 +107,9 @@ def _run_locked(cfg: Config, conn, report: WorkReport, *, session_ids, max_analy
102
107
  analyze_many(cfg, ids, runner, report, model=model, progress=progress)
103
108
  auto = cfg.synthesis.auto or force
104
109
  # the projects and groups whose weekly re-analysis is due: someone turned each on, so they run without [synthesis] auto
105
- due = (_weekly_due(cfg, conn) if synthesize and not session_ids and runner.available() and not report.paused_until
106
- and not (paused and paused > to_iso(utcnow())) else [])
107
- if synthesize and (auto or due) and runner.available() and not report.paused_until:
110
+ due = (_weekly_due(cfg, conn) if synthesize and not session_ids and not capped and runner.available()
111
+ and not report.paused_until and not (paused and paused > to_iso(utcnow())) else [])
112
+ if synthesize and not capped and (auto or due) and runner.available() and not report.paused_until:
108
113
  _synthesize(cfg, conn, runner, report, force=force, auto=auto, also=_weekly_projects(cfg, conn, due),
109
114
  progress=progress)
110
115
  _glossaries(cfg, conn, runner, report, progress=progress)
@@ -62,6 +62,9 @@ idle_minutes = 20
62
62
  min_prompts = 1
63
63
  # Max session analyses per worker run (the background job runs every 15 minutes).
64
64
  max_per_run = 6
65
+ # Pause automatic analysis while your Claude plan's 5-hour or 7-day limit is at or above this percent (backend
66
+ # "claude", needs `interlatch install --statusline`). 0 = never pause for it.
67
+ plan_limit_pct = 0
65
68
  # Parallel analysis processes.
66
69
  concurrency = 2
67
70
  # Also analyze sessions recorded before Interlatch was installed (newest first, within max_per_run).
@@ -275,6 +278,7 @@ class AnalysisConfig:
275
278
  idle_minutes: int = 20
276
279
  min_prompts: int = 1
277
280
  max_per_run: int = 6
281
+ plan_limit_pct: int = 0 # pause automatic runs at this % of a Claude plan limit; 0 = off
278
282
  concurrency: int = 2
279
283
  backfill: bool = True
280
284
  chunk_chars: int = 150_000
@@ -156,6 +156,9 @@ JA: dict[str, str] = {
156
156
  "準備完了:バックグラウンドの処理が次の実行(15 分ごと)で分析します",
157
157
  "ready to re-analyze: the session continued after it was analyzed": "再分析の準備完了:分析のあとにセッションが続きました",
158
158
  "analysis is paused until {when} (usage limit); it resumes by itself": "使用量の上限のため、分析は {when} まで一時停止しています。自動で再開します",
159
+ "your Claude plan's {window} limit is at {pct}%, at or above the {cap}% you set; it resumes after {when}":
160
+ "Claude プランの{window}の上限が {pct}% で、設定した {cap}% 以上です。{when} 以降に再開します",
161
+ "session (5hr)": "セッション(5 時間)", "weekly (7 day)": "週間(7 日)", "spend": "利用額",
159
162
  "automatic analysis is off (analysis.auto); analyze it from its page or with `interlatch analyze`":
160
163
  "自動分析がオフです(analysis.auto)。セッションのページか `interlatch analyze` で分析してください",
161
164
  "analysis is off (record only): pick an analyzer in Status › Analysis to analyze sessions":
@@ -178,9 +178,59 @@ def status_small(ctx: Context, limited: bool = False) -> dict:
178
178
  "hub_url": ctx.cfg.hub_url or None, # set on a computer that sends its sessions to a hub
179
179
  "hub_urls": [c.hub_url for c in sending], # and the other hubs it sends to (docs/profiles.md)
180
180
  "hub_problem": _hub_problem(sending), # its pushes don't get through: the status bar warns
181
+ "usage": analysis_usage(ctx), # the status bar's usage chip and the limits it sets
181
182
  }
182
183
 
183
184
 
185
+ def analysis_usage(ctx: Context) -> dict:
186
+ """What analysis has used (API-equivalent USD today, this month, all time; the Claude plan's windows from the status
187
+ line) and the limits set for it (analysis.max_per_run, max_budget_usd, plan_limit_pct)."""
188
+ from ....analysis.queue import plan_over_limit, queue_block
189
+ from ....runtime.install import statusline_installed
190
+ from ....runtime.statusline import plan_usage
191
+
192
+ a = ctx.cfg.analysis
193
+ day = datetime.now().astimezone().replace(hour=0, minute=0, second=0, microsecond=0)
194
+ today, month = to_iso(day), to_iso(day.replace(day=1))
195
+ row = ctx.conn.execute(
196
+ "SELECT COALESCE(SUM(CASE WHEN started_at >= ? THEN cost_usd END), 0), "
197
+ "COALESCE(SUM(CASE WHEN started_at >= ? THEN cost_usd END), 0), COALESCE(SUM(cost_usd), 0), "
198
+ "SUM(CASE WHEN started_at >= ? THEN 1 ELSE 0 END) FROM analyses", (today, month, today)).fetchone()
199
+ over = plan_over_limit(ctx.conn, ctx.cfg)
200
+ return {"spent": {"today": row[0], "month": row[1], "total": row[2]}, "calls_today": row[3] or 0,
201
+ "plan": plan_usage(ctx.conn), "plan_applies": a.backend == "claude", "over": over,
202
+ "collector": statusline_installed(ctx.cfg), # without it no plan readings ever arrive
203
+ "block": queue_block(ctx.conn, ctx.cfg) if over else None, # why the queue stopped, in the queue's words
204
+ "limits": {"max_per_run": a.max_per_run, "max_budget_usd": a.max_budget_usd, "plan_limit_pct": a.plan_limit_pct}}
205
+
206
+
207
+ # what the status bar's usage panel may set, with the bounds a value must be in
208
+ LIMITS = {"max_per_run": (int, 1, 100), "max_budget_usd": (float, 0.1, 100.0), "plan_limit_pct": (int, 0, 100)}
209
+
210
+
211
+ def action_limits(ctx: Context, body: dict) -> dict:
212
+ """Save the analysis limits (LIMITS) the status bar's usage panel sets; returns the usage with them."""
213
+ from ....core.config import load_config, set_config_value
214
+
215
+ refused = []
216
+ for key, (kind, lo, hi) in LIMITS.items():
217
+ if key not in body:
218
+ continue
219
+ try:
220
+ v = kind(body[key])
221
+ except (TypeError, ValueError):
222
+ v = None
223
+ if v is None or isinstance(body[key], bool) or not lo <= v <= hi:
224
+ refused.append(key)
225
+ continue
226
+ set_config_value(ctx.cfg, "analysis", key, f"{v:.2f}" if kind is float else str(v))
227
+ ctx.cfg = load_config(ctx.cfg.home)
228
+ out = analysis_usage(ctx)
229
+ if refused:
230
+ out["error"] = tr("not saved: {keys}", keys=", ".join(refused))
231
+ return out
232
+
233
+
184
234
  def _sending_to(ctx: Context) -> list:
185
235
  """The configs that send to a hub: Personal's (when it joined one), then each other hub this computer joined."""
186
236
  from ....application.profiles import joined
@@ -478,6 +478,7 @@ POST: list[Route] = [
478
478
  Route("/api/analysis/backend", ANYONE,
479
479
  lambda h, ctx, m, b: h._json(api.settings.action_backend(ctx, str(b.get("backend") or "")))),
480
480
  Route("/api/analysis/agent", ANYONE, lambda h, ctx, m, b: h._json(api.settings.action_agent(ctx, _body(b)))),
481
+ Route("/api/analysis/limits", ANYONE, lambda h, ctx, m, b: h._json(api.status.action_limits(ctx, _body(b)))),
481
482
  # an API key, and where transcripts are sent
482
483
  Route("/api/analysis/provider", OWNER, lambda h, ctx, m, b: h._json(api.settings.action_provider(ctx, _body(b)))),
483
484
  Route("/api/analysis/provider/test", OWNER, lambda h, ctx, m, b: h._json(
@@ -1727,6 +1727,34 @@ table.data tr.picked:hover td { background: color-mix(in srgb, var(--accent) 11%
1727
1727
  #activity .act-mark.no { color: var(--critical-ink); }
1728
1728
  #activity .act-foot { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding-top: 10px; border-top: 1px solid var(--grid); font-size: 12px; }
1729
1729
  :root[data-motion="off"] #activity, :root[data-motion="off"] #activity .act-bar i { animation: none; transition: none; } :root[data-motion="off"] #activity .act-bar.indeterminate i { left: 0; width: 100%; opacity: 0.4; }
1730
+ /* Analysis usage: what the status bar's usage chip opens: the plan's windows, analysis spend, and the limits */
1731
+ #usage-panel { position: fixed; left: 12px; bottom: 38px; z-index: 62; width: min(380px, calc(100vw - 24px)); max-height: min(75vh, 600px); overflow-y: auto;
1732
+ padding: 12px 14px; border-radius: 6px; background: var(--glass-strong); color: var(--ink); font-size: 13px;
1733
+ box-shadow: var(--glass-shadow), 0 10px 32px rgba(0, 0, 0, 0.16), inset 0 0 0 0.5px var(--glass-rim); animation: upd-in 0.18s ease-out; }
1734
+ #usage-panel .act-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; font-size: 14px; }
1735
+ #usage-panel .un-close { border: 0; background: transparent; color: var(--muted); font-size: 17px; line-height: 1; padding: 2px 5px; border-radius: 6px; cursor: pointer; }
1736
+ #usage-panel .un-close:hover { background: var(--hover); color: var(--ink); }
1737
+ #usage-panel .subhead { margin: 12px 0 6px; }
1738
+ #usage-panel .warn-line { margin: 6px 0 0; font-size: 12.5px; }
1739
+ #usage-panel .us-wins { display: grid; gap: 10px; }
1740
+ #usage-panel .us-top { display: flex; justify-content: space-between; gap: 10px; font-variant-numeric: tabular-nums; }
1741
+ #usage-panel .us-bar { position: relative; height: 6px; margin-top: 6px; border-radius: 3px; background: var(--surface-3); }
1742
+ #usage-panel .us-bar i { position: absolute; inset: 0 auto 0 0; border-radius: 3px; background: var(--accent); }
1743
+ #usage-panel .us-bar.over i { background: var(--warning); }
1744
+ #usage-panel .us-bar u { position: absolute; top: -3px; bottom: -3px; width: 2px; margin-left: -1px; border-radius: 1px; background: var(--ink-2); }
1745
+ #usage-panel .us-note { font-size: 12px; }
1746
+ #usage-panel .us-win .us-note { margin-top: 4px; }
1747
+ #usage-panel .us-facts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
1748
+ #usage-panel .us-facts .fact { display: grid; gap: 1px; }
1749
+ #usage-panel .us-facts b { font-size: 15px; font-variant-numeric: tabular-nums; }
1750
+ #usage-panel .us-facts span { font-size: 11.5px; color: var(--muted); }
1751
+ #usage-panel .us-form { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; align-items: start; }
1752
+ #usage-panel .us-form label { display: grid; gap: 3px; font-size: 12px; color: var(--ink-2); }
1753
+ #usage-panel .us-form label:nth-child(3) { grid-column: 1 / -1; }
1754
+ #usage-panel .us-form small { font-size: 11px; }
1755
+ #usage-panel .us-form .input { width: 100%; }
1756
+ #usage-panel .us-save { grid-column: 1 / -1; display: flex; justify-content: flex-end; }
1757
+ :root[data-motion="off"] #usage-panel { animation: none; }
1730
1758
 
1731
1759
  /* Export: a small menu of formats under (or, in the selection bar, above) its button */
1732
1760
  details.menu { position: relative; }
@@ -9117,6 +9117,7 @@ async function pollStatus() {
9117
9117
  : st.last_sync ? t("Synced {ago}", { ago: ago(st.last_sync) }) : t("Not synced yet");
9118
9118
  $("#status-version").textContent = st.version ? `Interlatch ${st.version}` : "";
9119
9119
  showHubProblem(st.hub_problem);
9120
+ showUsage(st);
9120
9121
  showUpdate(st.update, st.version);
9121
9122
  pollUnseen();
9122
9123
  pollNews();
@@ -9185,6 +9186,7 @@ function toggleActivity(force) {
9185
9186
  const open = force ?? box.hidden;
9186
9187
  box.hidden = !open;
9187
9188
  $("#status-pill").setAttribute("aria-expanded", String(open));
9189
+ if (open && !$("#usage-panel").hidden) toggleUsage(false);
9188
9190
  if (open) { if (lastStatus) drawActivity(lastStatus); pollStatus(); }
9189
9191
  }
9190
9192
  function drawActivity(st) {
@@ -9241,6 +9243,86 @@ function showHubProblem(pb) {
9241
9243
  chip.dataset.page = `#/hub?name=${encodeURIComponent(pb.profile || "personal")}`;
9242
9244
  chip.title = `${hubProblemTitle(pb)} · ${t("since {time}", { time: fmtDT(pb.since) })}`;
9243
9245
  }
9246
+ // Analysis usage on every page: a chip in the status bar (the Claude plan's windows, what analysis spent today) that
9247
+ // opens a panel with the detail and the limits analysis runs under (analysis_usage, action_limits)
9248
+ // named as Claude Code's own Account & usage panel names them
9249
+ const PLAN_SHORT = { five_hour: t("Session"), seven_day: t("Weekly"), spend_limit: t("spend") };
9250
+ const PLAN_NAME = { five_hour: t("Session (5hr)"), seven_day: t("Weekly (7 day)"), spend_limit: t("Spend limit") };
9251
+ const PLAN_CAPS = [0, 50, 60, 70, 80, 90, 95];
9252
+ function showUsage(st) {
9253
+ const chip = $("#status-usage"), u = st.usage;
9254
+ chip.hidden = !u || st.limited;
9255
+ if (chip.hidden) return;
9256
+ const cap = u.plan_applies ? u.limits.plan_limit_pct : 0;
9257
+ const wins = Object.entries(u.plan?.limits || {}).map(([k, v]) => `${PLAN_SHORT[k] || k} ${Math.round(v.used_pct)}%`);
9258
+ chip.className = u.over ? "warn" : "";
9259
+ if (!wins.length) wins.push(u.collector ? t("plan usage: waiting for Claude Code") : t("plan usage: not set up"));
9260
+ chip.replaceChildren(h("span", { class: "dot" }), [...wins, cap && u.plan?.limits && Object.keys(u.plan.limits).length ? t("pauses at {pct}%", { pct: cap }) : null,
9261
+ t("{cost} today", { cost: fmtCost(u.spent.today) })].filter(Boolean).join(" · "));
9262
+ chip.title = u.block || t("Analysis usage and limits: click to see and change them");
9263
+ }
9264
+ function toggleUsage(force) {
9265
+ const box = $("#usage-panel");
9266
+ const open = force ?? box.hidden;
9267
+ box.hidden = !open;
9268
+ $("#status-usage").setAttribute("aria-expanded", String(open));
9269
+ if (open) { toggleActivity(false); if (lastStatus?.usage) drawUsage(lastStatus.usage); }
9270
+ }
9271
+ // drawn when it opens and after a save, not on every poll, so a value being typed stays
9272
+ function drawUsage(u) {
9273
+ const box = $("#usage-panel"), admin = canAdmin(), lim = u.limits;
9274
+ const cap = u.plan_applies ? lim.plan_limit_pct : 0;
9275
+ const win = ([k, v]) => h("div", { class: "us-win" },
9276
+ h("div", { class: "us-top" }, h("b", null, PLAN_NAME[k] || k), h("span", null, `${Math.round(v.used_pct)}%`)),
9277
+ h("div", { class: `us-bar${cap && v.used_pct >= cap ? " over" : ""}`, role: "meter", "aria-label": PLAN_NAME[k] || k,
9278
+ "aria-valuemin": "0", "aria-valuemax": "100", "aria-valuenow": String(Math.round(v.used_pct)) },
9279
+ h("i", { style: { width: `${Math.min(100, Math.max(1, v.used_pct))}%` } }),
9280
+ cap ? h("u", { style: { left: `${cap}%` }, title: t("Analysis pauses at {pct}%", { pct: cap }) }) : null),
9281
+ h("div", { class: "muted us-note", title: fmtDT(v.resets_at) }, t("{pct}% left · resets {when}",
9282
+ { pct: Math.max(0, 100 - Math.round(v.used_pct)), when: untilText(v.resets_at) })));
9283
+ const wins = Object.entries(u.plan?.limits || {});
9284
+ const plan = wins.length ? [...wins.map(win), h("div", { class: "muted us-note" }, [t("as of {ago}", { ago: ago(u.plan.as_of) }),
9285
+ !u.plan_applies ? t("Analysis isn't using Claude Code now, so these don't limit it") : null].filter(Boolean).join(" · "))]
9286
+ : h("div", { class: "muted us-note" }, u.collector
9287
+ ? t("Recorded from Claude Code's status line: the numbers come with your next Claude Code session in a terminal and the sync after it (Pro and Max plans).")
9288
+ : tx("Not set up: run {command} so Claude Code's status line records them (Pro and Max plans). Until then the pause at plan limit can't act.",
9289
+ { command: h("code", null, "interlatch install --statusline") }));
9290
+ const fact = (v, label) => h("div", { class: "fact" }, h("b", null, fmtCost(v)), h("span", null, label));
9291
+ const num = (name, label, attrs, hint) => h("label", null, h("span", null, label),
9292
+ h("input", { class: "input", type: "number", name, value: lim[name], disabled: !admin, required: true, ...attrs }), hint ? h("small", { class: "muted" }, hint) : null);
9293
+ const caps = PLAN_CAPS.includes(lim.plan_limit_pct) ? PLAN_CAPS : [...PLAN_CAPS, lim.plan_limit_pct].sort((a, b) => a - b);
9294
+ const form = h("form", { class: "us-form", onsubmit: async (e) => {
9295
+ e.preventDefault();
9296
+ const f = e.currentTarget, btn = f.querySelector("button[type=submit]");
9297
+ btn.disabled = true;
9298
+ const r = await post("/api/analysis/limits", { max_per_run: +f.max_per_run.value, max_budget_usd: +f.max_budget_usd.value,
9299
+ plan_limit_pct: +f.plan_limit_pct.value }).catch((err) => ({ error: err.message }));
9300
+ btn.disabled = false;
9301
+ if (r.error) { toast(r.error, 6000); return; }
9302
+ toast(t("Analysis limits saved."));
9303
+ lastStatus = { ...lastStatus, usage: r };
9304
+ drawUsage(r);
9305
+ pollStatus(); // the chip and the queue's reason follow the new limits
9306
+ } },
9307
+ num("max_per_run", t("Sessions per run"), { min: 1, max: 100, step: 1 }, t("every 15 minutes")),
9308
+ num("max_budget_usd", t("Budget per call (USD)"), { min: 0.1, max: 100, step: 0.1 }, t("API-equivalent; Claude Code and Bob")),
9309
+ h("label", null, h("span", null, t("Pause at plan limit")),
9310
+ h("select", { class: "input", name: "plan_limit_pct", disabled: !admin },
9311
+ caps.map((c) => h("option", { value: c, selected: c === lim.plan_limit_pct }, c ? `${c}%` : t("Never")))),
9312
+ h("small", { class: "muted" }, t("Session or weekly, while Claude Code analyzes"))),
9313
+ h("div", { class: "us-save" }, h("button", { class: "btn small primary", type: "submit", disabled: !admin }, t("Save"))));
9314
+ box.replaceChildren(...[
9315
+ h("div", { class: "act-head" }, h("b", null, t("Analysis usage")),
9316
+ h("button", { class: "un-close", type: "button", "aria-label": t("Close"), onclick: () => toggleUsage(false) }, "×")),
9317
+ u.block ? h("div", { class: "warn-line" }, icon("pause"), " ", LANG === "en" ? `${u.block[0].toUpperCase()}${u.block.slice(1)}.` : `${u.block}。`) : null,
9318
+ h("div", { class: "subhead" }, t("Claude plan")), h("div", { class: "us-wins" }, plan),
9319
+ h("div", { class: "subhead" }, t("Spent on analysis")),
9320
+ h("div", { class: "us-facts", title: t("API-equivalent: what the tokens would cost at list prices") },
9321
+ fact(u.spent.today, t("today")), fact(u.spent.month, t("this month")), fact(u.spent.total, t("all time"))),
9322
+ h("div", { class: "subhead" }, t("Limits")), form,
9323
+ admin ? null : h("div", { class: "muted us-note" }, t("Only an admin can change these.")),
9324
+ ].filter(Boolean));
9325
+ }
9244
9326
  // an update on offer: a chip in the status bar, a dot on Settings, and once per update a notification card
9245
9327
  function showUpdate(u, version) {
9246
9328
  const upd = $("#status-update");
@@ -9419,6 +9501,12 @@ document.addEventListener("mousedown", (e) => { if (!$("#activity").hidden && !e
9419
9501
  document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !$("#activity").hidden) toggleActivity(false); });
9420
9502
  $("#status-update").addEventListener("click", () => go("#/status?focus=updates"));
9421
9503
  $("#status-hub-problem").addEventListener("click", (e) => go(e.currentTarget.dataset.page || "#/devices"));
9504
+ $("#status-usage").addEventListener("click", () => toggleUsage());
9505
+ $("#status-usage").setAttribute("aria-haspopup", "dialog");
9506
+ $("#status-usage").setAttribute("aria-expanded", "false");
9507
+ $("#usage-panel").setAttribute("aria-label", t("Analysis usage"));
9508
+ document.addEventListener("mousedown", (e) => { if (!$("#usage-panel").hidden && !e.target.closest("#usage-panel, #status-usage")) toggleUsage(false); });
9509
+ document.addEventListener("keydown", (e) => { if (e.key === "Escape" && !$("#usage-panel").hidden) toggleUsage(false); });
9422
9510
  $("#status-queue").addEventListener("click", openQueueDialog);
9423
9511
  $("#status-queue").setAttribute("aria-haspopup", "dialog");
9424
9512
  $("#status-queue").setAttribute("aria-expanded", "false");
@@ -68,6 +68,7 @@
68
68
  <span id="status-hub" hidden></span>
69
69
  <button type="button" id="status-pill" title="Background work"></button>
70
70
  <button type="button" id="status-queue" hidden></button>
71
+ <button type="button" id="status-usage" hidden></button>
71
72
  </div>
72
73
  <div class="st-right">
73
74
  <span id="status-sync"></span>
@@ -85,6 +86,7 @@
85
86
  <div id="toast" role="status" aria-live="polite" hidden></div>
86
87
  <div id="update-note" role="status" aria-live="polite" hidden></div>
87
88
  <div id="activity" role="dialog" aria-label="Activity" hidden></div>
89
+ <div id="usage-panel" role="dialog" aria-label="Analysis usage" hidden></div>
88
90
  <script src="rough.js"></script>
89
91
  <script src="ja.js"></script>
90
92
  <script src="i18n.js"></script>
@@ -73,6 +73,36 @@ window.INTERLATCH_JA = {
73
73
  "{limit} limit {pct}% (resets {when})": "{limit}の上限 {pct}%({when} にリセット)",
74
74
  "as of {ago}": "取得:{ago}",
75
75
  "{limit} limit used": "{limit}の上限の使用量",
76
+ // Analysis usage: the status bar's chip and panel
77
+ "Weekly": "週間",
78
+ "Session (5hr)": "セッション(5 時間)",
79
+ "Weekly (7 day)": "週間(7 日)",
80
+ "Spend limit": "利用額の上限",
81
+ "{pct}% left · resets {when}": "残り {pct}% · {when}にリセット",
82
+ "{cost} today": "今日 {cost}",
83
+ "pauses at {pct}%": "{pct}% で停止",
84
+ "Analysis usage and limits: click to see and change them": "分析の使用量と上限:クリックで確認・変更",
85
+ "Analysis pauses at {pct}%": "{pct}% で分析を一時停止",
86
+ "Analysis isn't using Claude Code now, so these don't limit it": "いまの分析は Claude Code を使っていないため、これらの上限はかかりません",
87
+ "plan usage: waiting for Claude Code": "プランの使用量:Claude Code を待っています",
88
+ "plan usage: not set up": "プランの使用量:未設定",
89
+ "Recorded from Claude Code's status line: the numbers come with your next Claude Code session in a terminal and the sync after it (Pro and Max plans).": "Claude Code のステータスラインから記録します。次にターミナルで Claude Code を使い、そのあと同期すると表示されます(Pro と Max プラン)。",
90
+ "Not set up: run {command} so Claude Code's status line records them (Pro and Max plans). Until then the pause at plan limit can't act.": "未設定です。{command} を実行すると、Claude Code のステータスラインが記録します(Pro と Max プラン)。それまでは「プランの上限で一時停止」は働きません。",
91
+ "Analysis limits saved.": "分析の上限を保存しました。",
92
+ "Sessions per run": "1 回の実行のセッション数",
93
+ "every 15 minutes": "15 分ごと",
94
+ "Budget per call (USD)": "1 回の呼び出しの予算(USD)",
95
+ "API-equivalent; Claude Code and Bob": "API 換算。Claude Code と Bob",
96
+ "Pause at plan limit": "プランの上限で一時停止",
97
+ "Never": "しない",
98
+ "Session or weekly, while Claude Code analyzes": "セッションまたは週間。Claude Code で分析しているとき",
99
+ "Analysis usage": "分析の使用量",
100
+ "Claude plan": "Claude プラン",
101
+ "Spent on analysis": "分析に使った額",
102
+ "today": "今日",
103
+ "this month": "今月",
104
+ "Limits": "上限",
105
+ "Only an admin can change these.": "変更できるのは管理者だけです。",
76
106
  "How far your plan limits moved while this session ran (includes anything else using the same account then)": "このセッションの実行中にプランの上限がどれだけ進んだか(同じ時間に同じアカウントを使ったものも含みます)",
77
107
  "ready now": "すぐに分析",
78
108
  "still active": "まだ作業中",
@@ -187,3 +187,63 @@ def test_a_spoke_that_shares_knowledge_analyzes_here(synced):
187
187
  assert st["hub_url"] and st["analysis"]["on_hub"] is on_hub
188
188
  assert api.status.analysis_queue(app.context())["on_hub"] is on_hub
189
189
  assert api.status.status_small(App(cfg).context())["analysis"]["on_hub"] is False
190
+
191
+
192
+ def _plan(conn, five_hour, seven_day=10.0):
193
+ import json
194
+
195
+ later = to_iso(utcnow() + timedelta(hours=2))
196
+ kv_set(conn, "plan_usage_latest", json.dumps({"as_of": to_iso(utcnow()), "limits": {
197
+ "five_hour": {"used_pct": five_hour, "resets_at": later},
198
+ "seven_day": {"used_pct": seven_day, "resets_at": to_iso(utcnow() + timedelta(days=3))}}}))
199
+ conn.commit()
200
+
201
+
202
+ def test_the_plan_limit_the_user_set_stops_automatic_runs(synced):
203
+ """analysis.plan_limit_pct: at or above it, the queue says why and a background run analyzes nothing; a session
204
+ asked for still runs, and the cap means nothing while another analyzer runs or when it is 0."""
205
+ from interlatch.analysis.queue import plan_over_limit
206
+ from interlatch.application.workflows import run_worker
207
+
208
+ conn, cfg = synced["conn"], synced["cfg"]
209
+ conn.execute("UPDATE sessions SET analysis_status = 'pending', analysis_attempts = 0, ended_flag = 1")
210
+ _plan(conn, five_hour=85.0)
211
+ capped = replace(cfg, analysis=replace(cfg.analysis, plan_limit_pct=80))
212
+ assert plan_over_limit(conn, capped)["window"] == "five_hour"
213
+ assert "session (5hr) limit is at 85%, at or above the 80% you set" in count_pending(conn, capped)["block"]
214
+ assert count_pending(conn, replace(capped, analysis=replace(capped.analysis, plan_limit_pct=90)))["block"] is None
215
+ assert count_pending(conn, cfg)["block"] is None # 0: off
216
+ assert plan_over_limit(conn, replace(capped, analysis=replace(capped.analysis, backend="codex"))) is None
217
+
218
+ report = run_worker(capped, synthesize=False)
219
+ assert not report.analyzed and "plan_limit_pct = 80" in report.note
220
+ assert conn.execute("SELECT analysis_status FROM sessions WHERE id = ?", (SID,)).fetchone()[0] == "pending"
221
+ report = run_worker(capped, session_ids=[SID], synthesize=False)
222
+ assert report.analyzed
223
+ assert conn.execute("SELECT analysis_status FROM sessions WHERE id = ?", (SID,)).fetchone()[0] == "done"
224
+
225
+
226
+ def test_the_status_bar_shows_usage_and_saves_the_limits(synced):
227
+ from interlatch.core.config import load_config
228
+ from interlatch.interfaces.web.app import App
229
+
230
+ conn, cfg = synced["conn"], synced["cfg"]
231
+ _plan(conn, five_hour=42.0)
232
+ conn.execute("INSERT INTO analyses(kind, target, started_at, cost_usd) VALUES ('session', 'x', ?, 0.5), "
233
+ "('session', 'y', '2020-01-01T00:00:00Z', 2.0)", (to_iso(utcnow()),))
234
+ conn.commit()
235
+ ctx = App(cfg).context()
236
+ u = api.status.status_small(ctx)["usage"]
237
+ assert u["spent"]["today"] == 0.5 and u["spent"]["total"] >= 2.5 and u["calls_today"] >= 1
238
+ assert u["plan"]["limits"]["five_hour"]["used_pct"] == 42.0 and u["over"] is None and u["collector"] is False
239
+ assert "usage" not in api.status.status_small(ctx, limited=True) # someone the hub limits to projects
240
+
241
+ r = api.status.action_limits(ctx, {"max_per_run": 3, "max_budget_usd": 1.5, "plan_limit_pct": 40})
242
+ assert r["limits"] == {"max_per_run": 3, "max_budget_usd": 1.5, "plan_limit_pct": 40} and "error" not in r
243
+ assert r["over"]["window"] == "five_hour" and "at or above the 40% you set" in r["block"]
244
+ saved = load_config(cfg.home).analysis
245
+ assert (saved.max_per_run, saved.max_budget_usd, saved.plan_limit_pct) == (3, 1.5, 40)
246
+
247
+ r = api.status.action_limits(ctx, {"max_per_run": 0, "max_budget_usd": "lots", "plan_limit_pct": True})
248
+ assert r["error"] == "not saved: max_per_run, max_budget_usd, plan_limit_pct"
249
+ assert r["limits"] == {"max_per_run": 3, "max_budget_usd": 1.5, "plan_limit_pct": 40}
@@ -216,6 +216,7 @@ ROUTES: dict[tuple[str, str], Access] = {
216
216
  ("POST", "/api/update"): POST_ADMIN,
217
217
  ("POST", "/api/analysis/backend"): POST_ADMIN,
218
218
  ("POST", "/api/analysis/agent"): POST_ADMIN,
219
+ ("POST", "/api/analysis/limits"): POST_ADMIN,
219
220
  ("POST", "/api/analysis/provider"): POST_ADMIN,
220
221
  ("POST", "/api/analysis/provider/test"): POST_ADMIN,
221
222
  ("POST", "/api/analysis/provider/models"): POST_ADMIN,
@@ -1,4 +0,0 @@
1
- - **A project's glossary and mindmap on a team hub:** a project's page there has a **Glossary & map** tab: its terms
2
- as a list or a mindmap. Teammates limited to some projects now see it too, with the terms their projects use and
3
- what their projects say of them, never what another project wrote
4
- ([On a team hub](docs/dashboard.md#on-a-team-hub)).
@@ -1,6 +0,0 @@
1
- - **A project's page on a team hub:** the team's knowledge leads. Each knowledge base entry and lesson names whose
2
- sessions taught it, lessons two people found separately sit side by side, **Since your last visit** lists new and
3
- replaced lessons, **Start here** greets someone new to the project, **People** shows who has worked on what (never
4
- a count per person), and **Who's working on it** draws each person's sessions over eight weeks. Tokens and cost
5
- move to an admin-only Usage tab, and the Systems map is no longer offered where it would be empty or refused
6
- ([On a team hub](docs/dashboard.md#on-a-team-hub)).
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes