thth 2.0.1__tar.gz → 2.3.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 (260) hide show
  1. {thth-2.0.1 → thth-2.3.0}/PKG-INFO +8 -12
  2. {thth-2.0.1 → thth-2.3.0}/README.md +7 -11
  3. {thth-2.0.1 → thth-2.3.0}/mcp/server.py +233 -7
  4. thth-2.3.0/skills/thth/SKILL.md +121 -0
  5. {thth-2.0.1 → thth-2.3.0}/tests/fixtures/callback/worker_responses.json +15 -4
  6. {thth-2.0.1 → thth-2.3.0}/tests/helpers/fake_oauth_server.py +12 -1
  7. thth-2.3.0/tests/helpers/fake_search_adapter.py +78 -0
  8. thth-2.3.0/tests/helpers/fake_threads_writes.py +139 -0
  9. {thth-2.0.1 → thth-2.3.0}/tests/helpers/hold_lock.py +13 -2
  10. {thth-2.0.1 → thth-2.3.0}/tests/helpers/worker_probe.mjs +1 -0
  11. {thth-2.0.1 → thth-2.3.0}/tests/test_adapter_boundary.py +29 -8
  12. thth-2.3.0/tests/test_after_cli.py +205 -0
  13. {thth-2.0.1 → thth-2.3.0}/tests/test_app_env_cli.py +5 -1
  14. {thth-2.0.1 → thth-2.3.0}/tests/test_approve_cmd.py +39 -2
  15. thth-2.3.0/tests/test_auth_identity_required.py +152 -0
  16. {thth-2.0.1 → thth-2.3.0}/tests/test_bluesky_adapter.py +122 -4
  17. {thth-2.0.1 → thth-2.3.0}/tests/test_board_running.py +65 -3
  18. thth-2.3.0/tests/test_cli_help.py +87 -0
  19. thth-2.3.0/tests/test_collect_runs.py +132 -0
  20. thth-2.3.0/tests/test_control_chars_refused.py +210 -0
  21. thth-2.3.0/tests/test_count_for.py +158 -0
  22. {thth-2.0.1 → thth-2.3.0}/tests/test_docs_links.py +26 -0
  23. thth-2.3.0/tests/test_doctor_all_permissions.py +427 -0
  24. thth-2.3.0/tests/test_duplicate_front_matter_keys.py +176 -0
  25. thth-2.3.0/tests/test_engagements.py +208 -0
  26. {thth-2.0.1 → thth-2.3.0}/tests/test_fake_api.py +11 -1
  27. thth-2.3.0/tests/test_inbox_granted_scopes.py +463 -0
  28. thth-2.3.0/tests/test_llm_trial_engage.py +410 -0
  29. {thth-2.0.1 → thth-2.3.0}/tests/test_mastodon_adapter.py +171 -5
  30. thth-2.3.0/tests/test_mastodon_visibility.py +115 -0
  31. thth-2.3.0/tests/test_mcp.py +324 -0
  32. {thth-2.0.1 → thth-2.3.0}/tests/test_mcp_robustness.py +89 -0
  33. {thth-2.0.1 → thth-2.3.0}/tests/test_packaging.py +25 -20
  34. thth-2.3.0/tests/test_runs_read_rows.py +172 -0
  35. thth-2.3.0/tests/test_scopes_source_requested.py +122 -0
  36. thth-2.3.0/tests/test_search_engage_targets.py +279 -0
  37. thth-2.3.0/tests/test_secret_value_redact.py +234 -0
  38. {thth-2.0.1 → thth-2.3.0}/tests/test_selfupdate.py +32 -0
  39. {thth-2.0.1 → thth-2.3.0}/tests/test_send_visibility.py +3 -1
  40. {thth-2.0.1 → thth-2.3.0}/tests/test_site.py +71 -41
  41. thth-2.3.0/tests/test_skill_doc.py +101 -0
  42. thth-2.3.0/tests/test_thread_read.py +333 -0
  43. thth-2.3.0/tests/test_threads_read_permissions.py +630 -0
  44. thth-2.3.0/tests/test_token_scopes_record.py +156 -0
  45. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_review_fixes.py +13 -1
  46. thth-2.3.0/tests/test_v21b_approved_writes.py +662 -0
  47. thth-2.3.0/tests/test_where_cli.py +311 -0
  48. thth-2.3.0/tests/test_who_cli.py +327 -0
  49. thth-2.3.0/thth/VERSION +1 -0
  50. {thth-2.0.1 → thth-2.3.0}/thth/account_report.py +27 -3
  51. {thth-2.0.1 → thth-2.3.0}/thth/accounts.py +7 -2
  52. {thth-2.0.1 → thth-2.3.0}/thth/adapters/__init__.py +13 -1
  53. {thth-2.0.1 → thth-2.3.0}/thth/adapters/base.py +166 -0
  54. {thth-2.0.1 → thth-2.3.0}/thth/adapters/bluesky.py +132 -3
  55. {thth-2.0.1 → thth-2.3.0}/thth/adapters/mastodon.py +158 -8
  56. thth-2.3.0/thth/adapters/threads.py +1284 -0
  57. thth-2.3.0/thth/after_cli.py +342 -0
  58. {thth-2.0.1 → thth-2.3.0}/thth/approval.py +87 -5
  59. {thth-2.0.1 → thth-2.3.0}/thth/bundle.py +53 -11
  60. {thth-2.0.1 → thth-2.3.0}/thth/cli.py +149 -19
  61. {thth-2.0.1 → thth-2.3.0}/thth/collect.py +163 -11
  62. {thth-2.0.1 → thth-2.3.0}/thth/core.py +119 -13
  63. {thth-2.0.1 → thth-2.3.0}/thth/doctor.py +70 -2
  64. thth-2.3.0/thth/engagements.py +195 -0
  65. {thth-2.0.1 → thth-2.3.0}/thth/lint.py +87 -1
  66. {thth-2.0.1 → thth-2.3.0}/thth/oauth.py +86 -3
  67. {thth-2.0.1 → thth-2.3.0}/thth/queuefile.py +81 -5
  68. {thth-2.0.1 → thth-2.3.0}/thth/redact.py +46 -0
  69. {thth-2.0.1 → thth-2.3.0}/thth/report.py +40 -0
  70. thth-2.3.0/thth/retract_cli.py +350 -0
  71. thth-2.3.0/thth/runs.py +125 -0
  72. {thth-2.0.1 → thth-2.3.0}/thth/select.py +29 -2
  73. {thth-2.0.1 → thth-2.3.0}/thth/selfupdate.py +24 -0
  74. {thth-2.0.1 → thth-2.3.0}/thth/sent.py +31 -0
  75. thth-2.3.0/thth/thread_read.py +415 -0
  76. thth-2.3.0/thth/threads_read_cli.py +587 -0
  77. thth-2.3.0/thth/where_cli.py +384 -0
  78. thth-2.3.0/thth/who_cli.py +401 -0
  79. {thth-2.0.1 → thth-2.3.0}/thth/writeback.py +42 -0
  80. thth-2.0.1/skills/thth/SKILL.md +0 -117
  81. thth-2.0.1/tests/helpers/share_ledger.py +0 -92
  82. thth-2.0.1/tests/test_mcp.py +0 -108
  83. thth-2.0.1/tests/test_share.py +0 -413
  84. thth-2.0.1/tests/test_share_safety.py +0 -239
  85. thth-2.0.1/tests/test_share_sync_concurrent.py +0 -93
  86. thth-2.0.1/thth/VERSION +0 -1
  87. thth-2.0.1/thth/adapters/threads.py +0 -603
  88. thth-2.0.1/thth/runs.py +0 -61
  89. thth-2.0.1/thth/share.py +0 -666
  90. thth-2.0.1/thth/share_cli.py +0 -140
  91. {thth-2.0.1 → thth-2.3.0}/.gitignore +0 -0
  92. {thth-2.0.1 → thth-2.3.0}/LICENSE +0 -0
  93. {thth-2.0.1 → thth-2.3.0}/accounts.example/bluesky.json +0 -0
  94. {thth-2.0.1 → thth-2.3.0}/accounts.example/mastodon.json +0 -0
  95. {thth-2.0.1 → thth-2.3.0}/accounts.example/threads.json +0 -0
  96. {thth-2.0.1 → thth-2.3.0}/bin/thth +0 -0
  97. {thth-2.0.1 → thth-2.3.0}/bin/thth-remote +0 -0
  98. {thth-2.0.1 → thth-2.3.0}/bin/thth-run +0 -0
  99. {thth-2.0.1 → thth-2.3.0}/pyproject.toml +0 -0
  100. {thth-2.0.1 → thth-2.3.0}/systemd/thth@.service +0 -0
  101. {thth-2.0.1 → thth-2.3.0}/tests/__init__.py +0 -0
  102. {thth-2.0.1 → thth-2.3.0}/tests/conftest.py +0 -0
  103. {thth-2.0.1 → thth-2.3.0}/tests/fixtures/build_injection_eval.py +0 -0
  104. {thth-2.0.1 → thth-2.3.0}/tests/fixtures/injection_eval.json +0 -0
  105. {thth-2.0.1 → thth-2.3.0}/tests/fixtures/umami-bile.md +0 -0
  106. {thth-2.0.1 → thth-2.3.0}/tests/helpers/__init__.py +0 -0
  107. {thth-2.0.1 → thth-2.3.0}/tests/helpers/fake_push_adapter.py +0 -0
  108. {thth-2.0.1 → thth-2.3.0}/tests/helpers/fake_redirect_server.py +0 -0
  109. {thth-2.0.1 → thth-2.3.0}/tests/helpers/fake_threads_reader.py +0 -0
  110. {thth-2.0.1 → thth-2.3.0}/tests/helpers/hold_repo_lock.py +0 -0
  111. {thth-2.0.1 → thth-2.3.0}/tests/test_account_name_check.py +0 -0
  112. {thth-2.0.1 → thth-2.3.0}/tests/test_account_report.py +0 -0
  113. {thth-2.0.1 → thth-2.3.0}/tests/test_accounts_dir.py +0 -0
  114. {thth-2.0.1 → thth-2.3.0}/tests/test_api_error_body.py +0 -0
  115. {thth-2.0.1 → thth-2.3.0}/tests/test_approval.py +0 -0
  116. {thth-2.0.1 → thth-2.3.0}/tests/test_ask.py +0 -0
  117. {thth-2.0.1 → thth-2.3.0}/tests/test_atlas_fifth_review.py +0 -0
  118. {thth-2.0.1 → thth-2.3.0}/tests/test_atlas_fourth_review.py +0 -0
  119. {thth-2.0.1 → thth-2.3.0}/tests/test_atlas_review.py +0 -0
  120. {thth-2.0.1 → thth-2.3.0}/tests/test_atlas_second_review.py +0 -0
  121. {thth-2.0.1 → thth-2.3.0}/tests/test_atlas_third_review.py +0 -0
  122. {thth-2.0.1 → thth-2.3.0}/tests/test_audit1_fixes.py +0 -0
  123. {thth-2.0.1 → thth-2.3.0}/tests/test_audit2_fixes.py +0 -0
  124. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_b_counterexamples.py +0 -0
  125. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_b_m1_semantics.py +0 -0
  126. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_b_n1_depth.py +0 -0
  127. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_b_r1_closure.py +0 -0
  128. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_v201.py +0 -0
  129. {thth-2.0.1 → thth-2.3.0}/tests/test_audit_v23_details.py +0 -0
  130. {thth-2.0.1 → thth-2.3.0}/tests/test_auth.py +0 -0
  131. {thth-2.0.1 → thth-2.3.0}/tests/test_bluesky_secret_scrub.py +0 -0
  132. {thth-2.0.1 → thth-2.3.0}/tests/test_board_needs_review.py +0 -0
  133. {thth-2.0.1 → thth-2.3.0}/tests/test_bundle.py +0 -0
  134. {thth-2.0.1 → thth-2.3.0}/tests/test_charcount.py +0 -0
  135. {thth-2.0.1 → thth-2.3.0}/tests/test_cli_entrypoint.py +0 -0
  136. {thth-2.0.1 → thth-2.3.0}/tests/test_collect.py +0 -0
  137. {thth-2.0.1 → thth-2.3.0}/tests/test_collect_sent.py +0 -0
  138. {thth-2.0.1 → thth-2.3.0}/tests/test_comparability.py +0 -0
  139. {thth-2.0.1 → thth-2.3.0}/tests/test_conversation_paging.py +0 -0
  140. {thth-2.0.1 → thth-2.3.0}/tests/test_doctor.py +0 -0
  141. {thth-2.0.1 → thth-2.3.0}/tests/test_doctor_next_step.py +0 -0
  142. {thth-2.0.1 → thth-2.3.0}/tests/test_editorial_audit.py +0 -0
  143. {thth-2.0.1 → thth-2.3.0}/tests/test_editorial_n3_n4.py +0 -0
  144. {thth-2.0.1 → thth-2.3.0}/tests/test_external_acceptance.py +0 -0
  145. {thth-2.0.1 → thth-2.3.0}/tests/test_form_spec.py +0 -0
  146. {thth-2.0.1 → thth-2.3.0}/tests/test_form_spec_cli.py +0 -0
  147. {thth-2.0.1 → thth-2.3.0}/tests/test_forms.py +0 -0
  148. {thth-2.0.1 → thth-2.3.0}/tests/test_fresh_install.py +0 -0
  149. {thth-2.0.1 → thth-2.3.0}/tests/test_front_matter_injection.py +0 -0
  150. {thth-2.0.1 → thth-2.3.0}/tests/test_hypotheses.py +0 -0
  151. {thth-2.0.1 → thth-2.3.0}/tests/test_hypothesis_boundaries.py +0 -0
  152. {thth-2.0.1 → thth-2.3.0}/tests/test_hypothesis_units.py +0 -0
  153. {thth-2.0.1 → thth-2.3.0}/tests/test_impact_cli.py +0 -0
  154. {thth-2.0.1 → thth-2.3.0}/tests/test_independent_thread_review.py +0 -0
  155. {thth-2.0.1 → thth-2.3.0}/tests/test_independent_topic_review.py +0 -0
  156. {thth-2.0.1 → thth-2.3.0}/tests/test_independent_topic_review_r2.py +0 -0
  157. {thth-2.0.1 → thth-2.3.0}/tests/test_inflight.py +0 -0
  158. {thth-2.0.1 → thth-2.3.0}/tests/test_jst.py +0 -0
  159. {thth-2.0.1 → thth-2.3.0}/tests/test_lint.py +0 -0
  160. {thth-2.0.1 → thth-2.3.0}/tests/test_list_projections.py +0 -0
  161. {thth-2.0.1 → thth-2.3.0}/tests/test_llm_trial.py +0 -0
  162. {thth-2.0.1 → thth-2.3.0}/tests/test_lock.py +0 -0
  163. {thth-2.0.1 → thth-2.3.0}/tests/test_m2_independent.py +0 -0
  164. {thth-2.0.1 → thth-2.3.0}/tests/test_maintain.py +0 -0
  165. {thth-2.0.1 → thth-2.3.0}/tests/test_max_per_run.py +0 -0
  166. {thth-2.0.1 → thth-2.3.0}/tests/test_measured.py +0 -0
  167. {thth-2.0.1 → thth-2.3.0}/tests/test_media_accounts.py +0 -0
  168. {thth-2.0.1 → thth-2.3.0}/tests/test_media_next_steps.py +0 -0
  169. {thth-2.0.1 → thth-2.3.0}/tests/test_media_roundtrip.py +0 -0
  170. {thth-2.0.1 → thth-2.3.0}/tests/test_mismatch_diagnostics.py +0 -0
  171. {thth-2.0.1 → thth-2.3.0}/tests/test_n5_reply_null_element.py +0 -0
  172. {thth-2.0.1 → thth-2.3.0}/tests/test_n6_replies_incomplete_ledger.py +0 -0
  173. {thth-2.0.1 → thth-2.3.0}/tests/test_n7_doctor_error_body.py +0 -0
  174. {thth-2.0.1 → thth-2.3.0}/tests/test_no_limits.py +0 -0
  175. {thth-2.0.1 → thth-2.3.0}/tests/test_observation_provenance.py +0 -0
  176. {thth-2.0.1 → thth-2.3.0}/tests/test_preview.py +0 -0
  177. {thth-2.0.1 → thth-2.3.0}/tests/test_production_mode.py +0 -0
  178. {thth-2.0.1 → thth-2.3.0}/tests/test_queue.py +0 -0
  179. {thth-2.0.1 → thth-2.3.0}/tests/test_quiet_hours.py +0 -0
  180. {thth-2.0.1 → thth-2.3.0}/tests/test_r2_r4_r5.py +0 -0
  181. {thth-2.0.1 → thth-2.3.0}/tests/test_reason_adoption.py +0 -0
  182. {thth-2.0.1 → thth-2.3.0}/tests/test_redact.py +0 -0
  183. {thth-2.0.1 → thth-2.3.0}/tests/test_redirect_token_leak.py +0 -0
  184. {thth-2.0.1 → thth-2.3.0}/tests/test_refresh.py +0 -0
  185. {thth-2.0.1 → thth-2.3.0}/tests/test_refresh_destination.py +0 -0
  186. {thth-2.0.1 → thth-2.3.0}/tests/test_rejudge_new.py +0 -0
  187. {thth-2.0.1 → thth-2.3.0}/tests/test_rejudge_r2_r3.py +0 -0
  188. {thth-2.0.1 → thth-2.3.0}/tests/test_replies.py +0 -0
  189. {thth-2.0.1 → thth-2.3.0}/tests/test_replies_refresh.py +0 -0
  190. {thth-2.0.1 → thth-2.3.0}/tests/test_repo_lock.py +0 -0
  191. {thth-2.0.1 → thth-2.3.0}/tests/test_repo_sync.py +0 -0
  192. {thth-2.0.1 → thth-2.3.0}/tests/test_reverse_audit.py +0 -0
  193. {thth-2.0.1 → thth-2.3.0}/tests/test_review_case_id_cluster_m2.py +0 -0
  194. {thth-2.0.1 → thth-2.3.0}/tests/test_review_case_id_cluster_r2.py +0 -0
  195. {thth-2.0.1 → thth-2.3.0}/tests/test_review_cli.py +0 -0
  196. {thth-2.0.1 → thth-2.3.0}/tests/test_review_evidence_account.py +0 -0
  197. {thth-2.0.1 → thth-2.3.0}/tests/test_review_form_spec_and_case_id.py +0 -0
  198. {thth-2.0.1 → thth-2.3.0}/tests/test_review_provenance.py +0 -0
  199. {thth-2.0.1 → thth-2.3.0}/tests/test_review_records.py +0 -0
  200. {thth-2.0.1 → thth-2.3.0}/tests/test_revoke.py +0 -0
  201. {thth-2.0.1 → thth-2.3.0}/tests/test_roundtrip.py +0 -0
  202. {thth-2.0.1 → thth-2.3.0}/tests/test_run_missing_env.py +0 -0
  203. {thth-2.0.1 → thth-2.3.0}/tests/test_run_record_failclosed.py +0 -0
  204. {thth-2.0.1 → thth-2.3.0}/tests/test_runs_collected.py +0 -0
  205. {thth-2.0.1 → thth-2.3.0}/tests/test_schedule_multiple.py +0 -0
  206. {thth-2.0.1 → thth-2.3.0}/tests/test_schedule_view.py +0 -0
  207. {thth-2.0.1 → thth-2.3.0}/tests/test_secrets_grep.py +0 -0
  208. {thth-2.0.1 → thth-2.3.0}/tests/test_security_small_fixes.py +0 -0
  209. {thth-2.0.1 → thth-2.3.0}/tests/test_select.py +0 -0
  210. {thth-2.0.1 → thth-2.3.0}/tests/test_send.py +0 -0
  211. {thth-2.0.1 → thth-2.3.0}/tests/test_send_cli.py +0 -0
  212. {thth-2.0.1 → thth-2.3.0}/tests/test_send_media.py +0 -0
  213. {thth-2.0.1 → thth-2.3.0}/tests/test_sent_integrity.py +0 -0
  214. {thth-2.0.1 → thth-2.3.0}/tests/test_server_json.py +0 -0
  215. {thth-2.0.1 → thth-2.3.0}/tests/test_systemd.py +0 -0
  216. {thth-2.0.1 → thth-2.3.0}/tests/test_thread_publish.py +0 -0
  217. {thth-2.0.1 → thth-2.3.0}/tests/test_threadrun.py +0 -0
  218. {thth-2.0.1 → thth-2.3.0}/tests/test_threadshape.py +0 -0
  219. {thth-2.0.1 → thth-2.3.0}/tests/test_thth_root.py +0 -0
  220. {thth-2.0.1 → thth-2.3.0}/tests/test_token_exists.py +0 -0
  221. {thth-2.0.1 → thth-2.3.0}/tests/test_token_set.py +0 -0
  222. {thth-2.0.1 → thth-2.3.0}/tests/test_topic.py +0 -0
  223. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_advice.py +0 -0
  224. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_cli.py +0 -0
  225. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_models.py +0 -0
  226. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_shelf_observers.py +0 -0
  227. {thth-2.0.1 → thth-2.3.0}/tests/test_topic_store.py +0 -0
  228. {thth-2.0.1 → thth-2.3.0}/tests/test_topics_record_concurrent.py +0 -0
  229. {thth-2.0.1 → thth-2.3.0}/tests/test_topics_tool.py +0 -0
  230. {thth-2.0.1 → thth-2.3.0}/tests/test_trial_frictions.py +0 -0
  231. {thth-2.0.1 → thth-2.3.0}/tests/test_value_domains_shown.py +0 -0
  232. {thth-2.0.1 → thth-2.3.0}/tests/test_version.py +0 -0
  233. {thth-2.0.1 → thth-2.3.0}/tests/test_writeback_rebase.py +0 -0
  234. {thth-2.0.1 → thth-2.3.0}/tests/test_writeback_validate.py +0 -0
  235. {thth-2.0.1 → thth-2.3.0}/thth/__init__.py +0 -0
  236. {thth-2.0.1 → thth-2.3.0}/thth/__main__.py +0 -0
  237. {thth-2.0.1 → thth-2.3.0}/thth/account_cli.py +0 -0
  238. {thth-2.0.1 → thth-2.3.0}/thth/appenv.py +0 -0
  239. {thth-2.0.1 → thth-2.3.0}/thth/ask.py +0 -0
  240. {thth-2.0.1 → thth-2.3.0}/thth/ask_cli.py +0 -0
  241. {thth-2.0.1 → thth-2.3.0}/thth/forms.py +0 -0
  242. {thth-2.0.1 → thth-2.3.0}/thth/httpsafe.py +0 -0
  243. {thth-2.0.1 → thth-2.3.0}/thth/inflight.py +0 -0
  244. {thth-2.0.1 → thth-2.3.0}/thth/jst.py +0 -0
  245. {thth-2.0.1 → thth-2.3.0}/thth/lock.py +0 -0
  246. {thth-2.0.1 → thth-2.3.0}/thth/maintain.py +0 -0
  247. {thth-2.0.1 → thth-2.3.0}/thth/measured.py +0 -0
  248. {thth-2.0.1 → thth-2.3.0}/thth/postid.py +0 -0
  249. {thth-2.0.1 → thth-2.3.0}/thth/replies.py +0 -0
  250. {thth-2.0.1 → thth-2.3.0}/thth/scopes.py +0 -0
  251. {thth-2.0.1 → thth-2.3.0}/thth/secrets_fs.py +0 -0
  252. {thth-2.0.1 → thth-2.3.0}/thth/systemd_gen.py +0 -0
  253. {thth-2.0.1 → thth-2.3.0}/thth/threadrun.py +0 -0
  254. {thth-2.0.1 → thth-2.3.0}/thth/threadshape.py +0 -0
  255. {thth-2.0.1 → thth-2.3.0}/thth/threadthrow.py +0 -0
  256. {thth-2.0.1 → thth-2.3.0}/thth/topic_advice.py +0 -0
  257. {thth-2.0.1 → thth-2.3.0}/thth/topic_cli.py +0 -0
  258. {thth-2.0.1 → thth-2.3.0}/thth/topic_models.py +0 -0
  259. {thth-2.0.1 → thth-2.3.0}/thth/topic_store.py +0 -0
  260. {thth-2.0.1 → thth-2.3.0}/thth/topics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: thth
3
- Version: 2.0.1
3
+ Version: 2.3.0
4
4
  Summary: 承認済みの投稿だけを投げる門(Threads・Bluesky・Mastodon)。人が承認し、記録は利用者の git に残る。
5
5
  Project-URL: Homepage, https://thth.me
6
6
  Project-URL: Source, https://github.com/aokings/thth
@@ -33,7 +33,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
33
33
 
34
34
  ## 版
35
35
 
36
- **2.0.1**。門を無料で開ける版(2.0.1: 同席送信の採集・セキュリティ監査 2 回の直し)(`pip install`・`thth ask`・`thth share`・台帳を repo の外へ)。範囲と裁定は [docs/設計_v2_泉と門_2026-09-13.md](docs/設計_v2_泉と門_2026-09-13.md) §3・§7、その前の版は [docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md](docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md)、導入は [docs/導入_自分のMetaアプリで動かす.md](docs/導入_自分のMetaアプリで動かす.md)、文書の索引は [docs/README.md](docs/README.md)。
36
+ **2.0.1**。門を無料で開ける版(2.0.1: 同席送信の採集・セキュリティ監査 2 回の直し)(`pip install`・`thth ask`・台帳を repo の外へ)。範囲と裁定は [docs/設計_v2_泉と門_2026-09-13.md](docs/設計_v2_泉と門_2026-09-13.md) §3・§7、その前の版は [docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md](docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md)、導入は [docs/導入_自分のMetaアプリで動かす.md](docs/導入_自分のMetaアプリで動かす.md)、文書の索引は [docs/README.md](docs/README.md)。
37
37
 
38
38
  `thth/VERSION` は `2.0.1`(版は `thth/VERSION` の 1 か所・`server.json` はテストで一致を強制)。`origin/release` は v2.0.0(`216af9f`)で、**v2.0.1 はまだ配っていない**(配布は masaru の一言)。出口条件の試験は(運用日誌 thth-notes: `記録/試験_LLMに選ばせる_2026-09-13.md`)。
39
39
 
@@ -45,7 +45,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
45
45
 
46
46
  ## いまの状態(2026-09-13)
47
47
 
48
- 版 **2.0.1**。全件テスト **1990 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
48
+ 版 **2.3.0**。全件テスト **2274 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
49
49
 
50
50
  **媒体**: Threads(稼働)・Bluesky・Mastodon(同席用の台帳あり・未稼働)。
51
51
 
@@ -55,7 +55,7 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
55
55
 
56
56
  **入口は 4 つ**: 厚い CLI(`bin/thth`・`python -m thth`)、薄い MCP(`mcp/server.py`・読み取りと
57
57
  同席の投稿・`before_you_post`)、timer、そして `pip install`(`thth`・`thth-mcp` の entry point・
58
- **依存 0**。ただし **PyPI への登録と upload は masaru の手で、まだしていない**)。
58
+ **依存 0**。PyPI に公開済み・MCP registry に `io.github.aokings/thth` として登録済み)。
59
59
 
60
60
  **トピックの棚**(`thth topics`): 観測者ごとに並ぶ・打ち消し `retract-note`・`history`。
61
61
 
@@ -74,20 +74,16 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
74
74
  は `local`。泉のサーバはまだ無い)。原稿本文は渡さないし、答えにも出ない。
75
75
  **n が閾値(既定 20)に満たない群は中央値を返さず `cannot_say` に理由を出す**——
76
76
  手元の水ではほとんどが `cannot_say` になる。それが正しい答えで、rc は 0。MCP からは `before_you_post`。
77
- - **`thth share on|off|status|log|sync`**(設計 v2 §3・裁定 §7-3)。**既定 off**、設定が無い・壊れて
78
- いるときも off。off のあいだは outbox が **0 バイト**。on にしても
79
- `$THTH_ROOT/state/share/outbox/<YYYY-MM>.ndjson` に積むだけで、**送り先はまだ無い**(v2-5)。
80
- 積んだ全部は `thth share log` で読める。落ちるのは語・audience・型・件数・時刻帯・
81
- post_id の**塩つき sha256**(塩は outbox に出ない)。本文・返信本文・返信者の username・
82
- 自分の判断・アカウント名・トークン・repo のパス・生の post_id は落ちない。
77
+ - **読む口(`thth where`・`thth thread`・`thth who`)は何も保存しない**(裁定
78
+ 2026-09-16「横断の泉はやめる」)。残るのは絡みの台帳(自分の行為と反応)だけ。
83
79
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
84
80
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
85
81
 
86
- - **動くもの**(`thth --help` の全 27 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`share`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`。
82
+ - **動くもの**(`thth --help` の全 34 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`。
87
83
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
88
84
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
89
85
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
90
- - **masaru の手が要るもの**: PyPI の登録と upload、repo を public にする切替と `LICENSE`、MCP registry への登録、tag と `release` を進める操作。
86
+ - **masaru の手が要るもの**: repo を public にする切替と `LICENSE`、tag と `release` を進める操作(PyPI と MCP registry は 2.0.1 まで公開済み)。
91
87
 
92
88
  ## MCP registry
93
89
 
@@ -9,7 +9,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
9
9
 
10
10
  ## 版
11
11
 
12
- **2.0.1**。門を無料で開ける版(2.0.1: 同席送信の採集・セキュリティ監査 2 回の直し)(`pip install`・`thth ask`・`thth share`・台帳を repo の外へ)。範囲と裁定は [docs/設計_v2_泉と門_2026-09-13.md](docs/設計_v2_泉と門_2026-09-13.md) §3・§7、その前の版は [docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md](docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md)、導入は [docs/導入_自分のMetaアプリで動かす.md](docs/導入_自分のMetaアプリで動かす.md)、文書の索引は [docs/README.md](docs/README.md)。
12
+ **2.0.1**。門を無料で開ける版(2.0.1: 同席送信の採集・セキュリティ監査 2 回の直し)(`pip install`・`thth ask`・台帳を repo の外へ)。範囲と裁定は [docs/設計_v2_泉と門_2026-09-13.md](docs/設計_v2_泉と門_2026-09-13.md) §3・§7、その前の版は [docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md](docs/設計_v1.0.0_他人が導入できる版_2026-09-12.md)、導入は [docs/導入_自分のMetaアプリで動かす.md](docs/導入_自分のMetaアプリで動かす.md)、文書の索引は [docs/README.md](docs/README.md)。
13
13
 
14
14
  `thth/VERSION` は `2.0.1`(版は `thth/VERSION` の 1 か所・`server.json` はテストで一致を強制)。`origin/release` は v2.0.0(`216af9f`)で、**v2.0.1 はまだ配っていない**(配布は masaru の一言)。出口条件の試験は(運用日誌 thth-notes: `記録/試験_LLMに選ばせる_2026-09-13.md`)。
15
15
 
@@ -21,7 +21,7 @@ watchtower(`~/Developer/watchtower`)の隣に同じ流儀で並べる。watc
21
21
 
22
22
  ## いまの状態(2026-09-13)
23
23
 
24
- 版 **2.0.1**。全件テスト **1990 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
24
+ 版 **2.3.0**。全件テスト **2274 件**(`python -m pytest tests/ -q -n auto -p no:cacheprovider`・1 skip・rc=0)。
25
25
 
26
26
  **媒体**: Threads(稼働)・Bluesky・Mastodon(同席用の台帳あり・未稼働)。
27
27
 
@@ -31,7 +31,7 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
31
31
 
32
32
  **入口は 4 つ**: 厚い CLI(`bin/thth`・`python -m thth`)、薄い MCP(`mcp/server.py`・読み取りと
33
33
  同席の投稿・`before_you_post`)、timer、そして `pip install`(`thth`・`thth-mcp` の entry point・
34
- **依存 0**。ただし **PyPI への登録と upload は masaru の手で、まだしていない**)。
34
+ **依存 0**。PyPI に公開済み・MCP registry に `io.github.aokings/thth` として登録済み)。
35
35
 
36
36
  **トピックの棚**(`thth topics`): 観測者ごとに並ぶ・打ち消し `retract-note`・`history`。
37
37
 
@@ -50,20 +50,16 @@ timer(systemd・`thth systemd` で生成)で毎時投稿、返信の採集
50
50
  は `local`。泉のサーバはまだ無い)。原稿本文は渡さないし、答えにも出ない。
51
51
  **n が閾値(既定 20)に満たない群は中央値を返さず `cannot_say` に理由を出す**——
52
52
  手元の水ではほとんどが `cannot_say` になる。それが正しい答えで、rc は 0。MCP からは `before_you_post`。
53
- - **`thth share on|off|status|log|sync`**(設計 v2 §3・裁定 §7-3)。**既定 off**、設定が無い・壊れて
54
- いるときも off。off のあいだは outbox が **0 バイト**。on にしても
55
- `$THTH_ROOT/state/share/outbox/<YYYY-MM>.ndjson` に積むだけで、**送り先はまだ無い**(v2-5)。
56
- 積んだ全部は `thth share log` で読める。落ちるのは語・audience・型・件数・時刻帯・
57
- post_id の**塩つき sha256**(塩は outbox に出ない)。本文・返信本文・返信者の username・
58
- 自分の判断・アカウント名・トークン・repo のパス・生の post_id は落ちない。
53
+ - **読む口(`thth where`・`thth thread`・`thth who`)は何も保存しない**(裁定
54
+ 2026-09-16「横断の泉はやめる」)。残るのは絡みの台帳(自分の行為と反応)だけ。
59
55
  - **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
60
56
  - **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
61
57
 
62
- - **動くもの**(`thth --help` の全 27 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`share`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`。
58
+ - **動くもの**(`thth --help` の全 34 サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`。
63
59
  - **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
64
60
  - **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
65
61
  - **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
66
- - **masaru の手が要るもの**: PyPI の登録と upload、repo を public にする切替と `LICENSE`、MCP registry への登録、tag と `release` を進める操作。
62
+ - **masaru の手が要るもの**: repo を public にする切替と `LICENSE`、tag と `release` を進める操作(PyPI と MCP registry は 2.0.1 まで公開済み)。
67
63
 
68
64
  ## MCP registry
69
65
 
@@ -139,6 +139,118 @@ TOOLS = [
139
139
  "required": ["account", "topic"],
140
140
  },
141
141
  },
142
+ {
143
+ # **名前と説明文がそのまま売り文句**(設計「自分の泉」§2.2)。
144
+ # 実装が 1 語でも足したら設計書でなく実装を戻す(§2 の頭書きそのまま)。
145
+ "name": "after_you_posted",
146
+ # **設計「自分の泉」§2.2 の文言そのまま。**
147
+ "description": (
148
+ "出したあとに呼ぶ。この語・この型・この枝で、自分の投稿と返信が"
149
+ "どう受け取られたかを、件数と期間つきで返す"
150
+ ),
151
+ "inputSchema": {
152
+ "type": "object",
153
+ "properties": {
154
+ "account": {"type": "string", "description": "account 名"},
155
+ "topic": {"type": "string", "description": "語(トピック)"},
156
+ "kind": {"type": "string", "description": "型(行動・年度付き 等)"},
157
+ "hour_band": {"type": "string",
158
+ "description": "時刻帯(朝・昼・夕・深夜)で絞る"},
159
+ "reply_to": {"type": "string",
160
+ "description": "この post_id への返信だけに絞る"},
161
+ "author_key": {"type": "string",
162
+ "description": "この仮名(16 進 16 桁)への返信だけに絞る"},
163
+ "window_days": {"type": "integer", "description": "直近何日(既定 30)"},
164
+ "min_n": {"type": "integer",
165
+ "description": "中央値を返す下限(既定 5)"},
166
+ },
167
+ "required": ["account"],
168
+ },
169
+ },
170
+ {
171
+ # **名前と説明文がそのまま売り文句**(設計「自分の泉」§2.1)。
172
+ # 実装が 1 語でも足したら設計書でなく実装を戻す(§2 の頭書きそのまま)。
173
+ "name": "thread_read",
174
+ # **設計「自分の泉」§2.1 の文言そのまま。**
175
+ "description": (
176
+ "返信を書く前に呼ぶ。この投稿の枝を、誰が・いつ・何を・誰に向けて"
177
+ "言ったかの順で返す。何も保存しない"
178
+ ),
179
+ "inputSchema": {
180
+ "type": "object",
181
+ "properties": {
182
+ "account": {"type": "string", "description": "account 名"},
183
+ "post_id": {"type": "string", "description": "枝の根の post_id"},
184
+ "since": {"type": "string", "description": "この時刻以降だけ"},
185
+ "max_messages": {"type": "integer",
186
+ "description": "読む上限(既定 200・上限 1000)"},
187
+ },
188
+ "required": ["account", "post_id"],
189
+ },
190
+ },
191
+ {
192
+ # **名前と説明文がそのまま売り文句**(設計「自分の泉」§2.3)。
193
+ # 実装が 1 語でも足したら設計書でなく実装を戻す(§2 の頭書きそのまま)。
194
+ "name": "where_to_appear",
195
+ # **設計「自分の泉」§2.3 の文言そのまま。**
196
+ "description": (
197
+ "絡みに行く先を選ぶ前に呼ぶ。検索の一覧に、自分の履歴(この語・"
198
+ "この相手で何が起きたか)を重ねて返す。選ぶのは呼ぶ側"
199
+ ),
200
+ "inputSchema": {
201
+ "type": "object",
202
+ "properties": {
203
+ "account": {"type": "string",
204
+ "description": "account 名(project の代わり)"},
205
+ "project": {"type": "string",
206
+ "description": "この project の account 全部"
207
+ "(account の代わり)"},
208
+ "words": {"type": "array", "items": {"type": "string"},
209
+ "description": "検索の語(1〜5 個)"},
210
+ "recent": {"type": "boolean",
211
+ "description": "TOP でなく RECENT(新しい順)で検索する"},
212
+ "limit": {"type": "integer",
213
+ "description": "1 account・1 語あたりの上限(既定 25)"},
214
+ },
215
+ # **`account` か `project` のどちらか必須**(両方無ければ
216
+ # `validate_arguments()` が `-32602`)。`required` は AND
217
+ # (全部要る)の意味しか持てないので、ここには入れない——OR の
218
+ # 検査は `validate_arguments()` の `where_to_appear` 専用の分岐で行う。
219
+ "required": ["words"],
220
+ },
221
+ },
222
+ {
223
+ # **名前と説明文がそのまま売り文句**(設計「自分の泉」§2.4)。
224
+ # 実装が 1 語でも足したら設計書でなく実装を戻す(§2 の頭書きそのまま)。
225
+ "name": "who_is_this",
226
+ # **設計「自分の泉」§2.4 の文言そのまま。**
227
+ "description": (
228
+ "返信する相手を確かめる。この仮名と自分のアカウントの接触の"
229
+ "回数・時期・反応を返す。発言の内容は持たない"
230
+ ),
231
+ "inputSchema": {
232
+ "type": "object",
233
+ "properties": {
234
+ "account": {"type": "string",
235
+ "description": "account 名(project の代わり)"},
236
+ "project": {"type": "string",
237
+ "description": "この project の account 全部"
238
+ "(account の代わり)"},
239
+ "author_key": {"type": "string",
240
+ "description": "仮名(16 進 16 桁・username の代わり)"},
241
+ "username": {"type": "string",
242
+ "description": "@ 無しでも可。その場で author_key に"
243
+ "写す(保存しない・author_key の代わり)"},
244
+ "profile": {"type": "boolean",
245
+ "description": "その場で公開プロフィールを引く"
246
+ "(Threads だけ・保存しない・既定 false)"},
247
+ },
248
+ # **`account`/`project` と `author_key`/`username`、どちらも
249
+ # 「片方だけ必須」の OR**(`where_to_appear` と同じ理由で
250
+ # `required` には入れない——`validate_arguments()` の
251
+ # `who_is_this` 専用の分岐で見る)。
252
+ },
253
+ },
142
254
  ]
143
255
 
144
256
 
@@ -168,6 +280,10 @@ _TYPES = {
168
280
  "boolean": ((bool,), "真偽値"),
169
281
  "integer": ((int,), "整数"),
170
282
  "number": ((int, float), "数"),
283
+ # `where_to_appear` の `words`(T2-3)で初めて使う型。**配列だけを通す**
284
+ # ——足しておかないと `_TYPES.get(...)` の既定(`(object,)`)に落ちて、
285
+ # 文字列 1 個を渡しても素通りしてしまう。
286
+ "array": ((list,), "配列"),
171
287
  }
172
288
 
173
289
 
@@ -198,7 +314,12 @@ def ledger_roots() -> list:
198
314
  cfg = accounts_mod.load_account(account)
199
315
  except Exception: # noqa: BLE001(1 本壊れていても続ける)
200
316
  continue
201
- repo_dir = cfg.get("repo_dir")
317
+ # **正規の函数を通す**(監査 2026-09-16)。ここが台帳の `repo_dir` を
318
+ # 生のまま `os.path.realpath()` していたので、相対パスは**この
319
+ # プロセスの cwd** 基準で畳まれていた。`thth/accounts.py::resolved_repo_dir()`
320
+ # は `$THTH_ROOT` 基準——**同じ台帳で許可する場所が MCP だけ食い違って
321
+ # いた**(cwd は systemd 経由なら `/`、手で打てば人それぞれ)。
322
+ repo_dir = accounts_mod.resolved_repo_dir(cfg)
202
323
  if repo_dir:
203
324
  roots.append(os.path.realpath(repo_dir))
204
325
  return roots
@@ -261,8 +382,35 @@ def validate_arguments(name: str, arguments) -> dict:
261
382
  raise ToolInputError(
262
383
  f"{name}: {key} が `-` で始まっています({value!r})。"
263
384
  f"CLI の旗と区別できないので受け取りません")
385
+ if (spec.get("type") == "array"
386
+ and (spec.get("items") or {}).get("type") == "string"):
387
+ # **`words` の各要素も同じ理由で縛る**(監査 2026-09-14・P3-2 と同じ筋・
388
+ # T2-3)。`call_tool()` は要素を argv にそのまま並べるので、ここで
389
+ # 文字列でないもの・`-` で始まるものを通すと `thth where` の別の旗と
390
+ # 区別できなくなる。
391
+ for item in value:
392
+ if not isinstance(item, str):
393
+ raise ToolInputError(
394
+ f"{name}: {key} の要素は文字列です"
395
+ f"(受け取った: {type(item).__name__})")
396
+ if item.startswith("-"):
397
+ raise ToolInputError(
398
+ f"{name}: {key} の要素が `-` で始まっています({item!r})。"
399
+ f"CLI の旗と区別できないので受け取りません")
264
400
  if key == "file":
265
401
  check_file_argument(name, value)
402
+ if name in ("where_to_appear", "who_is_this") and not arguments.get("account") \
403
+ and not arguments.get("project"):
404
+ # **`account` か `project` のどちらか必須**(`required` は AND の意味しか
405
+ # 持てないので、ここで OR を見る・T2-3 発注書・T3-3 も同じ形)。
406
+ raise ToolInputError(
407
+ f"{name}: account か project のどちらかが要ります")
408
+ if name == "who_is_this" and not arguments.get("author_key") \
409
+ and not arguments.get("username"):
410
+ # **`author_key` か `username` のどちらか必須**(同じ理由で OR を
411
+ # ここで見る・T3-3 発注書)。
412
+ raise ToolInputError(
413
+ f"{name}: author_key か username のどちらかが要ります")
266
414
  return arguments
267
415
 
268
416
 
@@ -337,15 +485,86 @@ def call_tool(name: str, arguments: dict | None) -> dict:
337
485
  args.append("--json")
338
486
  proc = run_cli(args)
339
487
  text = proc.stdout
488
+ elif name == "after_you_posted":
489
+ # **`before_you_post` と同じ型**(発注 T0-2): CLI を `--json` で呼ぶだけ。
490
+ args = ["after", arguments["account"]]
491
+ if arguments.get("topic"):
492
+ args += ["--topic", arguments["topic"]]
493
+ if arguments.get("kind"):
494
+ args += ["--kind", arguments["kind"]]
495
+ if arguments.get("hour_band"):
496
+ args += ["--hour-band", arguments["hour_band"]]
497
+ if arguments.get("reply_to"):
498
+ args += ["--reply-to", arguments["reply_to"]]
499
+ if arguments.get("author_key"):
500
+ args += ["--author-key", arguments["author_key"]]
501
+ if arguments.get("window_days") is not None:
502
+ args += ["--window-days", str(arguments["window_days"])]
503
+ if arguments.get("min_n") is not None:
504
+ args += ["--min-n", str(arguments["min_n"])]
505
+ args.append("--json")
506
+ proc = run_cli(args)
507
+ text = proc.stdout
508
+ elif name == "thread_read":
509
+ # **`after_you_posted` と同じ型**: CLI(`thth thread`)を `--json` で
510
+ # 呼ぶだけ(設計「自分の泉」§2.1・T1-3)。
511
+ args = ["thread", arguments["account"], arguments["post_id"]]
512
+ if arguments.get("since"):
513
+ args += ["--since", arguments["since"]]
514
+ if arguments.get("max_messages") is not None:
515
+ args += ["--max-messages", str(arguments["max_messages"])]
516
+ args.append("--json")
517
+ proc = run_cli(args)
518
+ text = proc.stdout
519
+ elif name == "where_to_appear":
520
+ # **`thread_read` と同じ型**: CLI(`thth where`)を `--json` で呼ぶ
521
+ # だけ(設計「自分の泉」§2.3・T2-3)。`account`/`project` のどちらか
522
+ # 必須は `validate_arguments()` が先に見ているので、ここでは
523
+ # `project` を優先するだけでよい。
524
+ args = ["where"]
525
+ if arguments.get("project"):
526
+ args += ["--project", arguments["project"]]
527
+ else:
528
+ args.append(arguments["account"])
529
+ args += list(arguments.get("words") or [])
530
+ if arguments.get("recent"):
531
+ args.append("--recent")
532
+ if arguments.get("limit") is not None:
533
+ args += ["--limit", str(arguments["limit"])]
534
+ args.append("--json")
535
+ proc = run_cli(args)
536
+ text = proc.stdout
537
+ elif name == "who_is_this":
538
+ # **`where_to_appear` と同じ型**: CLI(`thth who`)を `--json` で呼ぶ
539
+ # だけ(設計「自分の泉」§2.4・T3-3)。`account`/`project` と
540
+ # `author_key`/`username` のどちらかずつ必須は `validate_arguments()`
541
+ # が先に見ているので、ここでは組み立てるだけでよい。
542
+ args = ["who"]
543
+ if arguments.get("project"):
544
+ args += ["--project", arguments["project"]]
545
+ else:
546
+ args.append(arguments["account"])
547
+ if arguments.get("author_key"):
548
+ args.append(arguments["author_key"])
549
+ else:
550
+ args.append("@" + arguments["username"])
551
+ if arguments.get("profile"):
552
+ args.append("--profile")
553
+ args.append("--json")
554
+ proc = run_cli(args)
555
+ text = proc.stdout
340
556
  else:
341
557
  return {"content": [{"type": "text", "text": f"unknown tool: {name}"}], "isError": True}
342
558
 
343
- if name.startswith("thth_topic_") or name == "before_you_post":
559
+ if name.startswith("thth_topic_") or name in (
560
+ "before_you_post", "after_you_posted", "thread_read", "where_to_appear",
561
+ "who_is_this"):
344
562
  # **新しい道具は exit 1 も isError**(設計 §7)。lint の exit 1(検査結果)
345
563
  # とは意味が違う——こちらは stale_context・不正な候補比較で、
346
564
  # **そのまま使ってはいけない**応答。既存の扱いは変えない。
347
- # `before_you_post` も同じ(設計 v2 §1): exit 1 は台帳が無い、2 は問いが
348
- # 受け取れない。**`cannot_say` だらけの答えは exit 0 で、error ではない。**
565
+ # `before_you_post`/`after_you_posted` も同じ(設計 v2 §1・「自分の泉」§5):
566
+ # exit 1 は台帳が無い、2 は問いが受け取れない。**`cannot_say` だらけの
567
+ # 答えは exit 0 で、error ではない。**
349
568
  is_error = proc.returncode != 0
350
569
  else:
351
570
  is_error = proc.returncode not in (0, 1) # lint は 1 も正常な「検査結果」
@@ -429,9 +648,16 @@ def main() -> None:
429
648
  continue
430
649
  try:
431
650
  req = json.loads(line)
432
- except json.JSONDecodeError:
433
- # id が判らないので返しようがない(JSON-RPC も id:null を許すが、
434
- # 従来どおり黙って次の行へ)。
651
+ except (ValueError, RecursionError):
652
+ # **`json.JSONDecodeError` だけでは足りない**(監査 2026-09-16)。
653
+ # `json.JSONDecodeError` は `ValueError` の子だが、Python 3.12 は
654
+ # 4300 桁を超える整数で別の `ValueError`
655
+ # ("Exceeds the limit (4300 digits) for integer string conversion")
656
+ # を上げる——`except json.JSONDecodeError` だけではここが漏れて
657
+ # プロセスが死ぬ。深い入れ子(`[` の連続)の `RecursionError` も同じ
658
+ # 筋で、ここでしか捕まえられない。**id が判らないので返しようがない**
659
+ # (JSON-RPC も id:null を許すが、従来どおり黙って次の行へ・方針は
660
+ # 変えない)。
435
661
  continue
436
662
  if isinstance(req, list):
437
663
  # **batch(配列)は受けない。** 受けたふりをして 1 件目だけ処理する
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: thth
3
+ description: 投稿する前・返信を書く前・出したあと・絡みに行く先を選ぶときに呼ぶ道具。Threads・Bluesky・Mastodon への下書きを人の承認を通してから出し、枝を読み、相手を知り、伸びを測る。
4
+ ---
5
+
6
+ # THTH — 投稿する前に呼ぶ道具
7
+
8
+ ## 何を保証するか
9
+
10
+ - **人が承認していない本文は 1 文字も出ない。** 承認は 2 段(見せる → digest を渡す)。
11
+ digest が違えば拒否する。**「見せた本文」と「出す本文」が同じであることを機械が確かめる。**
12
+ - **`--production` を付けるまで出ない。** 付けない実行は、投げるはずの本文と digest を
13
+ 表示して終わる。
14
+ - **記録は利用者の git に残る。** post_id・承認者・承認時刻・返信・実測は、あなたの repo の
15
+ commit として残る。THTH のサーバには何も無い。
16
+ - **数値には分母が付く。** 件数・期間・揃えた条件が無い数字は返さない。取れていない刻みは
17
+ `null` であって `0` ではない。
18
+ - **黙って間違えない。** 拒むときは理由と次の一手を言って非ゼロで終わる(loud reject)。
19
+
20
+ ## 何を拒むか
21
+
22
+ - **`--confirm` の無い本番。** digest の食い違う本番。承認後に本文が変わった下書き
23
+ (`approval_stale`)。
24
+ - **同期を確認していない commit の本文。** ディスクに置いただけのファイルは選ばれない。
25
+ - **静かな時間帯・最短間隔に反する投稿。**
26
+ - **語(トピック)の未確認。** 承認の一段目が「未確認です」と言う。
27
+
28
+ ## 何を絶対にしないか
29
+
30
+ - **何を書くかを決めない。** 文体・頻度・時刻・語の選び方はあなたのプロジェクトのもの。
31
+ 渡された本文を**整形も切り詰めもせず**そのまま出す。
32
+ - **読む口は何も保存しない。** `thread_read`・`where_to_appear`・`who_is_this` はどれも、
33
+ 読んで見せるだけで**台帳に 1 バイトも書かない**。残るのは**自分の行為と反応**
34
+ (絡みの台帳)だけで、**相手の本文・相手の名前・あなたの判断は台帳に入らない**。
35
+ - **人の代わりに承認しない。** あなた(エージェント)は下書きと digest を人に見せ、
36
+ 人が `--confirm` を打つ。
37
+
38
+ ---
39
+
40
+ ## 使う順番——場面で
41
+
42
+ ### 1. 探す——どこに絡みに行くか
43
+
44
+ ```bash
45
+ thth where <account> <語…> # 検索の一覧に自分の履歴を重ねて返す
46
+ thth where --project <P> <語…> # project 単位で全媒体を一度に
47
+ ```
48
+
49
+ MCP `where_to_appear`。**順位は無い**——材料を並べるだけで、選ぶのは LLM。
50
+ **残すもの: なし**(この口自身は何も書かない。材料は `after_you_posted` と
51
+ 絡みの台帳から来ている)。
52
+ **呼ばなくてよいとき**: 返す先がもう決まっている・自分の投稿への返信。
53
+
54
+ ### 2. 読む——枝をその場で
55
+
56
+ ```bash
57
+ thth thread <account> <post_id>
58
+ ```
59
+
60
+ MCP `thread_read`。枝の会話を**生で**読み、誰がどんな立場かをその場で判断する。
61
+ **`already_replied` を見落とさない**——もうこの枝に返しているかがここに乗る。
62
+ **残すもの: なし**(読んで捨てる。runs には件数だけ)。
63
+ **呼ばなくてよいとき**: 自分の根の枝は `thth threads` の形で足りるとき。
64
+
65
+ ### 3. 相手を知る——この仮名と何度
66
+
67
+ ```bash
68
+ thth who <account> <author_key>
69
+ ```
70
+
71
+ MCP `who_is_this`。この仮名と自分のアカウントが何度・いつ・どんな反応だったか
72
+ だけを返す。**人物像ではない**——発言の内容は持たない。
73
+ **残すもの: なし**(絡みの台帳・返信の台帳から導くだけで、この口自体は書かない)。
74
+
75
+ ### 4. 書く——下書きに宛先を乗せる
76
+
77
+ 下書きの front-matter に:
78
+
79
+ ```yaml
80
+ reply_to: <post_id>
81
+ reply_to_author_key: <一覧の --json の author_key をそのまま写す>
82
+ found_by: where_to_appear|manual|mention
83
+ ```
84
+
85
+ **残すもの**: この 3 つの欄(下書きファイルそのもの。承認まではあなたの repo の
86
+ untracked/コミット前のファイル)。語を選ぶときだけ `before_you_post`
87
+ (原稿本文は渡さない・件数と期間つきで返す)。
88
+
89
+ ### 5. 承認——二段(そのまま)
90
+
91
+ ```bash
92
+ thth lint <ファイル…> # front-matter と字数の検査
93
+ thth preview <ファイル> # 実際に投げる本文そのもの
94
+ thth approve <ファイル or ディレクトリ> # 一段目: 本文と digest を見せる
95
+ thth approve <同じ> --confirm <digest> --by "<承認した人>" # 二段目: 承認して commit
96
+ ```
97
+
98
+ **一段目は非ゼロで終わる。** それは失敗ではなく「まだ承認していない」。
99
+ **`--by` に人の名前を書く。** 承認したのは人であって、あなたではない。
100
+ **残すもの**: post_id・承認者・承認時刻(あなたの repo の commit)。
101
+
102
+ ### 6. 測る——出したあとの反応
103
+
104
+ ```bash
105
+ thth after <account> [--reply-to <post_id>] [--topic …]
106
+ ```
107
+
108
+ MCP `after_you_posted`。**24h の刻みが無ければ `null`**(0 と混ぜない・
109
+ `covered: false` を付ける)。`one_thing_to_change` は 1 個か `null`。
110
+ **残すもの: なし**(この口は読むだけ。絡みの台帳は**公開の瞬間**に 1 行書かれる——
111
+ 自分の行為と反応だけで、相手の本文・名前・自分の判断は入らない)。
112
+ **媒体をまたぐ数は無い**——project 単位で並べるだけで、足さない・割らない・
113
+ 順位も付けない。
114
+
115
+ ---
116
+
117
+ ## 詰まったら
118
+
119
+ - `thth --help` / `thth <subcommand> --help` が正本。
120
+ - `thth doctor <account>` が「いま投稿できる状態か」を最初から最後まで言う。
121
+ - 使い方の全文: `docs/使い方_プロジェクトのセッション向け_2026-09-09.md`。
@@ -8,7 +8,7 @@
8
8
  "referrer-policy": "no-referrer",
9
9
  "x-robots-tag": "noindex, nofollow"
10
10
  },
11
- "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>THTH</h1>\n <p class=\"sub\">Threads の認可の受け口です。ここを直接開いても何もありません。</p>\n <p class=\"note\">ターミナルで <code>thth auth &lt;account&gt;</code> を実行すると、\n 認可 URL が表示されます。承認するとこのページに戻ってきます。</p></main></body></html>"
11
+ "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>THTH</h1>\n <p class=\"sub\">Threads の認可の受け口です。ここを直接開いても何もありません。</p>\n <p class=\"note\">ターミナルで <code>thth auth &lt;account&gt;</code> を実行すると、\n 認可 URL が表示されます。承認するとこのページに戻ってくるので、\n 表示された <strong>URL 全体</strong>をターミナルに貼ります。</p></main></body></html>"
12
12
  },
13
13
  {
14
14
  "url": "https://thth.me/callback",
@@ -19,7 +19,18 @@
19
19
  "referrer-policy": "no-referrer",
20
20
  "x-robots-tag": "noindex, nofollow"
21
21
  },
22
- "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>THTH</h1>\n <p class=\"sub\">Threads の認可の受け口です。ここを直接開いても何もありません。</p>\n <p class=\"note\">ターミナルで <code>thth auth &lt;account&gt;</code> を実行すると、\n 認可 URL が表示されます。承認するとこのページに戻ってきます。</p></main></body></html>"
22
+ "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>THTH</h1>\n <p class=\"sub\">Threads の認可の受け口です。ここを直接開いても何もありません。</p>\n <p class=\"note\">ターミナルで <code>thth auth &lt;account&gt;</code> を実行すると、\n 認可 URL が表示されます。承認するとこのページに戻ってくるので、\n 表示された <strong>URL 全体</strong>をターミナルに貼ります。</p></main></body></html>"
23
+ },
24
+ {
25
+ "url": "https://thth.me/callback/?code=FAKE-CODE-abc123&state=FAKE-STATE-xyz",
26
+ "status": 200,
27
+ "headers": {
28
+ "cache-control": "no-store",
29
+ "content-type": "text/html; charset=utf-8",
30
+ "referrer-policy": "no-referrer",
31
+ "x-robots-tag": "noindex, nofollow"
32
+ },
33
+ "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>認可コードを受け取りました</h1>\n <p class=\"sub\">下の <strong>URL 全体</strong>をターミナルに貼ってください\n (<code>code</code> だけでは足りません)。1 時間で切れる使い捨てです。</p>\n <p class=\"code\" id=\"c\">https://thth.me/callback/?code=FAKE-CODE-abc123&amp;state=FAKE-STATE-xyz</p>\n <button id=\"b\">コピー</button>\n \n <p class=\"note\">この画面は撮らないでください。貼り終えたら閉じて構いません。</p>\n <script>\n // アドレスバーからコードを消す(撮影・肩越しの覗き見への備え)。\n try { history.replaceState(null, \"\", location.pathname); } catch (e) {}\n document.getElementById(\"b\").onclick = async () => {\n try {\n await navigator.clipboard.writeText(document.getElementById(\"c\").textContent);\n document.getElementById(\"b\").textContent = \"コピーしました\";\n } catch (e) { document.getElementById(\"b\").textContent = \"手で選んでコピーしてください\"; }\n };\n </script></main></body></html>"
23
34
  },
24
35
  {
25
36
  "url": "https://thth.me/callback/?code=FAKE-CODE-abc123",
@@ -30,7 +41,7 @@
30
41
  "referrer-policy": "no-referrer",
31
42
  "x-robots-tag": "noindex, nofollow"
32
43
  },
33
- "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>認可コードを受け取りました</h1>\n <p class=\"sub\">下の文字列をターミナルに貼ってください。1 時間で切れる使い捨てです。</p>\n <p class=\"code\" id=\"c\">FAKE-CODE-abc123</p>\n <button id=\"b\">コピー</button>\n <p class=\"note\">この画面は撮らないでください。貼り終えたら閉じて構いません。</p>\n <script>\n // アドレスバーからコードを消す(撮影・肩越しの覗き見への備え)。\n try { history.replaceState(null, \"\", location.pathname); } catch (e) {}\n document.getElementById(\"b\").onclick = async () => {\n try {\n await navigator.clipboard.writeText(document.getElementById(\"c\").textContent);\n document.getElementById(\"b\").textContent = \"コピーしました\";\n } catch (e) { document.getElementById(\"b\").textContent = \"手で選んでコピーしてください\"; }\n };\n </script></main></body></html>"
44
+ "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>認可コードを受け取りました</h1>\n <p class=\"sub\">下の <strong>URL 全体</strong>をターミナルに貼ってください\n (<code>code</code> だけでは足りません)。1 時間で切れる使い捨てです。</p>\n <p class=\"code\" id=\"c\">https://thth.me/callback/?code=FAKE-CODE-abc123</p>\n <button id=\"b\">コピー</button>\n <p class=\"note err\">state が付いていません。この戻りは <code>thth auth</code> に\n 受け付けられません(もう一度 <code>thth auth</code> から始めてください)。</p>\n <p class=\"note\">この画面は撮らないでください。貼り終えたら閉じて構いません。</p>\n <script>\n // アドレスバーからコードを消す(撮影・肩越しの覗き見への備え)。\n try { history.replaceState(null, \"\", location.pathname); } catch (e) {}\n document.getElementById(\"b\").onclick = async () => {\n try {\n await navigator.clipboard.writeText(document.getElementById(\"c\").textContent);\n document.getElementById(\"b\").textContent = \"コピーしました\";\n } catch (e) { document.getElementById(\"b\").textContent = \"手で選んでコピーしてください\"; }\n };\n </script></main></body></html>"
34
45
  },
35
46
  {
36
47
  "url": "https://thth.me/callback/?error=access_denied&error_description=The+user+denied+your+request",
@@ -52,7 +63,7 @@
52
63
  "referrer-policy": "no-referrer",
53
64
  "x-robots-tag": "noindex, nofollow"
54
65
  },
55
- "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>認可コードを受け取りました</h1>\n <p class=\"sub\">下の文字列をターミナルに貼ってください。1 時間で切れる使い捨てです。</p>\n <p class=\"code\" id=\"c\">&lt;script&gt;</p>\n <button id=\"b\">コピー</button>\n <p class=\"note\">この画面は撮らないでください。貼り終えたら閉じて構いません。</p>\n <script>\n // アドレスバーからコードを消す(撮影・肩越しの覗き見への備え)。\n try { history.replaceState(null, \"\", location.pathname); } catch (e) {}\n document.getElementById(\"b\").onclick = async () => {\n try {\n await navigator.clipboard.writeText(document.getElementById(\"c\").textContent);\n document.getElementById(\"b\").textContent = \"コピーしました\";\n } catch (e) { document.getElementById(\"b\").textContent = \"手で選んでコピーしてください\"; }\n };\n </script></main></body></html>"
66
+ "body": "<!doctype html>\n<html lang=\"ja\"><head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<meta name=\"robots\" content=\"noindex,nofollow,noarchive\">\n<title>THTH 認可の受け口</title>\n<style>\n :root { color-scheme: light dark; }\n body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center;\n font: 16px/1.7 -apple-system, BlinkMacSystemFont, \"Hiragino Sans\", \"Noto Sans JP\", sans-serif;\n background:#faf9f7; color:#1a1a1a; padding:24px; }\n @media (prefers-color-scheme: dark) { body { background:#16161a; color:#eee; } }\n main { max-width: 640px; width:100%; }\n h1 { font-size:1.15rem; font-weight:600; margin:0 0 4px; letter-spacing:.02em; }\n .sub { font-size:.85rem; opacity:.65; margin:0 0 24px; }\n .code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:.95rem;\n word-break:break-all; background:rgba(127,127,127,.12); border-radius:10px;\n padding:16px; margin:0 0 12px; user-select:all; }\n button { font:inherit; font-size:.9rem; padding:9px 18px; border-radius:8px; cursor:pointer;\n border:1px solid rgba(127,127,127,.4); background:transparent; color:inherit; }\n button:hover { background:rgba(127,127,127,.12); }\n .note { font-size:.85rem; opacity:.7; margin-top:20px; }\n .err { color:#b3261e; } @media (prefers-color-scheme: dark) { .err { color:#f2b8b5; } }\n</style></head><body><main><h1>認可コードを受け取りました</h1>\n <p class=\"sub\">下の <strong>URL 全体</strong>をターミナルに貼ってください\n (<code>code</code> だけでは足りません)。1 時間で切れる使い捨てです。</p>\n <p class=\"code\" id=\"c\">https://thth.me/callback/?code=%3Cscript%3E</p>\n <button id=\"b\">コピー</button>\n <p class=\"note err\">state が付いていません。この戻りは <code>thth auth</code> に\n 受け付けられません(もう一度 <code>thth auth</code> から始めてください)。</p>\n <p class=\"note\">この画面は撮らないでください。貼り終えたら閉じて構いません。</p>\n <script>\n // アドレスバーからコードを消す(撮影・肩越しの覗き見への備え)。\n try { history.replaceState(null, \"\", location.pathname); } catch (e) {}\n document.getElementById(\"b\").onclick = async () => {\n try {\n await navigator.clipboard.writeText(document.getElementById(\"c\").textContent);\n document.getElementById(\"b\").textContent = \"コピーしました\";\n } catch (e) { document.getElementById(\"b\").textContent = \"手で選んでコピーしてください\"; }\n };\n </script></main></body></html>"
56
67
  },
57
68
  {
58
69
  "url": "https://thth.me/deauthorize",