charness 3.6.0 → 3.7.0

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 (582) hide show
  1. package/CHANGELOG.md +274 -0
  2. package/DEVELOPMENT.md +31 -5
  3. package/MIGRATION_RENAME_TO_CHARNESS.md +3 -2
  4. package/README.md +84 -782
  5. package/SETUP.md +37 -10
  6. package/agent-plugin/.claude-plugin/plugin.json +7 -0
  7. package/agent-plugin/README.md +35 -0
  8. package/agent-plugin/skills/charness-delegate-claude/SKILL.md +85 -0
  9. package/agent-plugin/skills/charness-delegate-claude/summarize-events.py +90 -0
  10. package/agent-plugin/skills/charness-delegate-opencode/SKILL.md +68 -0
  11. package/agent-plugin/skills/charness-manager/SKILL.md +152 -0
  12. package/dist/adapters/adapterNames.d.ts +6 -0
  13. package/dist/adapters/adapterNames.d.ts.map +1 -1
  14. package/dist/adapters/adapterNames.js +8 -2
  15. package/dist/adapters/adapterNames.js.map +1 -1
  16. package/dist/adapters/claudeCliAdapter.d.ts +8 -0
  17. package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
  18. package/dist/adapters/claudeCliAdapter.js +42 -5
  19. package/dist/adapters/claudeCliAdapter.js.map +1 -1
  20. package/dist/adapters/claudeJsonStreamAdapter.d.ts +10 -9
  21. package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
  22. package/dist/adapters/claudeJsonStreamAdapter.js +53 -55
  23. package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
  24. package/dist/adapters/createAdapter.d.ts +8 -0
  25. package/dist/adapters/createAdapter.d.ts.map +1 -1
  26. package/dist/adapters/createAdapter.js +14 -2
  27. package/dist/adapters/createAdapter.js.map +1 -1
  28. package/dist/adapters/openCodeAdapter.d.ts +43 -6
  29. package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
  30. package/dist/adapters/openCodeAdapter.js +95 -6
  31. package/dist/adapters/openCodeAdapter.js.map +1 -1
  32. package/dist/agentLogin/agentLogin.d.ts +1 -1
  33. package/dist/agentLogin/agentLogin.d.ts.map +1 -1
  34. package/dist/apiRetryKick.d.ts +4 -3
  35. package/dist/apiRetryKick.d.ts.map +1 -1
  36. package/dist/apiRetryKick.js +16 -9
  37. package/dist/apiRetryKick.js.map +1 -1
  38. package/dist/bot.d.ts.map +1 -1
  39. package/dist/bot.js +1313 -381
  40. package/dist/bot.js.map +1 -1
  41. package/dist/cli/claudeProfiles.d.ts +11 -6
  42. package/dist/cli/claudeProfiles.d.ts.map +1 -1
  43. package/dist/cli/claudeProfiles.js +50 -22
  44. package/dist/cli/claudeProfiles.js.map +1 -1
  45. package/dist/cli/hot.d.ts +5 -0
  46. package/dist/cli/hot.d.ts.map +1 -1
  47. package/dist/cli/hot.js +71 -25
  48. package/dist/cli/hot.js.map +1 -1
  49. package/dist/cli/tscWatchGuard.d.ts +95 -0
  50. package/dist/cli/tscWatchGuard.d.ts.map +1 -0
  51. package/dist/cli/tscWatchGuard.js +295 -0
  52. package/dist/cli/tscWatchGuard.js.map +1 -0
  53. package/dist/cli.js +12 -1
  54. package/dist/cli.js.map +1 -1
  55. package/dist/connectors/jira/answerSink.d.ts +10 -0
  56. package/dist/connectors/jira/answerSink.d.ts.map +1 -1
  57. package/dist/connectors/jira/answerSink.js +13 -0
  58. package/dist/connectors/jira/answerSink.js.map +1 -1
  59. package/dist/connectors/jira/config.d.ts +2 -0
  60. package/dist/connectors/jira/config.d.ts.map +1 -1
  61. package/dist/connectors/jira/config.js +9 -1
  62. package/dist/connectors/jira/config.js.map +1 -1
  63. package/dist/connectors/jira/connector.d.ts +11 -1
  64. package/dist/connectors/jira/connector.d.ts.map +1 -1
  65. package/dist/connectors/jira/connector.js +13 -1
  66. package/dist/connectors/jira/connector.js.map +1 -1
  67. package/dist/connectors/jira/inbound.d.ts +29 -4
  68. package/dist/connectors/jira/inbound.d.ts.map +1 -1
  69. package/dist/connectors/jira/inbound.js +71 -8
  70. package/dist/connectors/jira/inbound.js.map +1 -1
  71. package/dist/connectors/jira/issueBlocks.d.ts +5 -0
  72. package/dist/connectors/jira/issueBlocks.d.ts.map +1 -1
  73. package/dist/connectors/jira/issueBlocks.js +11 -0
  74. package/dist/connectors/jira/issueBlocks.js.map +1 -1
  75. package/dist/connectors/jira/prompt.d.ts +4 -0
  76. package/dist/connectors/jira/prompt.d.ts.map +1 -1
  77. package/dist/connectors/jira/prompt.js +11 -2
  78. package/dist/connectors/jira/prompt.js.map +1 -1
  79. package/dist/connectors/telegram/bindPicker.d.ts +5 -0
  80. package/dist/connectors/telegram/bindPicker.d.ts.map +1 -1
  81. package/dist/connectors/telegram/bindPicker.js +18 -1
  82. package/dist/connectors/telegram/bindPicker.js.map +1 -1
  83. package/dist/connectors/telegram/bindPickerClaims.d.ts +46 -0
  84. package/dist/connectors/telegram/bindPickerClaims.d.ts.map +1 -0
  85. package/dist/connectors/telegram/bindPickerClaims.js +83 -0
  86. package/dist/connectors/telegram/bindPickerClaims.js.map +1 -0
  87. package/dist/connectors/telegram/bindSweep.d.ts +32 -0
  88. package/dist/connectors/telegram/bindSweep.d.ts.map +1 -0
  89. package/dist/connectors/telegram/bindSweep.js +47 -0
  90. package/dist/connectors/telegram/bindSweep.js.map +1 -0
  91. package/dist/connectors/telegram/bindingWelcome.d.ts +23 -0
  92. package/dist/connectors/telegram/bindingWelcome.d.ts.map +1 -0
  93. package/dist/connectors/telegram/bindingWelcome.js +26 -0
  94. package/dist/connectors/telegram/bindingWelcome.js.map +1 -0
  95. package/dist/connectors/telegram/botApiConnection.d.ts +74 -0
  96. package/dist/connectors/telegram/botApiConnection.d.ts.map +1 -0
  97. package/dist/connectors/telegram/botApiConnection.js +201 -0
  98. package/dist/connectors/telegram/botApiConnection.js.map +1 -0
  99. package/dist/connectors/telegram/commands/botCore.d.ts +8 -2
  100. package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -1
  101. package/dist/connectors/telegram/commands/botSkills.d.ts +21 -0
  102. package/dist/connectors/telegram/commands/botSkills.d.ts.map +1 -0
  103. package/dist/connectors/telegram/commands/botSkills.js +133 -0
  104. package/dist/connectors/telegram/commands/botSkills.js.map +1 -0
  105. package/dist/connectors/telegram/commands/callbackMessage.d.ts +5 -0
  106. package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
  107. package/dist/connectors/telegram/commands/callbackMessage.js +9 -0
  108. package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
  109. package/dist/connectors/telegram/commands/compaction.d.ts +65 -3
  110. package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -1
  111. package/dist/connectors/telegram/commands/compaction.js +512 -94
  112. package/dist/connectors/telegram/commands/compaction.js.map +1 -1
  113. package/dist/connectors/telegram/commands/displayModes.d.ts +1 -1
  114. package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
  115. package/dist/connectors/telegram/commands/displayModes.js +6 -18
  116. package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
  117. package/dist/connectors/telegram/commands/fallbackChain.js +5 -5
  118. package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
  119. package/dist/connectors/telegram/commands/reminders.d.ts +1 -1
  120. package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -1
  121. package/dist/connectors/telegram/commands/reminders.js +4 -15
  122. package/dist/connectors/telegram/commands/reminders.js.map +1 -1
  123. package/dist/connectors/telegram/commands/selfUpdate.d.ts.map +1 -1
  124. package/dist/connectors/telegram/commands/selfUpdate.js +1 -11
  125. package/dist/connectors/telegram/commands/selfUpdate.js.map +1 -1
  126. package/dist/connectors/telegram/commands/taskAgents.d.ts +22 -0
  127. package/dist/connectors/telegram/commands/taskAgents.d.ts.map +1 -0
  128. package/dist/connectors/telegram/commands/taskAgents.js +104 -0
  129. package/dist/connectors/telegram/commands/taskAgents.js.map +1 -0
  130. package/dist/connectors/telegram/commands/todo.d.ts +82 -0
  131. package/dist/connectors/telegram/commands/todo.d.ts.map +1 -0
  132. package/dist/connectors/telegram/commands/todo.js +384 -0
  133. package/dist/connectors/telegram/commands/todo.js.map +1 -0
  134. package/dist/connectors/telegram/commands/topicCommand.d.ts +6 -2
  135. package/dist/connectors/telegram/commands/topicCommand.d.ts.map +1 -1
  136. package/dist/connectors/telegram/commands/topicCommand.js +6 -9
  137. package/dist/connectors/telegram/commands/topicCommand.js.map +1 -1
  138. package/dist/connectors/telegram/openBindMessages.d.ts +45 -0
  139. package/dist/connectors/telegram/openBindMessages.d.ts.map +1 -0
  140. package/dist/connectors/telegram/openBindMessages.js +67 -0
  141. package/dist/connectors/telegram/openBindMessages.js.map +1 -0
  142. package/dist/connectors/telegram/outputFlushPlan.d.ts +49 -0
  143. package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -1
  144. package/dist/connectors/telegram/outputFlushPlan.js +46 -0
  145. package/dist/connectors/telegram/outputFlushPlan.js.map +1 -1
  146. package/dist/connectors/telegram/pendingTopicName.d.ts +26 -0
  147. package/dist/connectors/telegram/pendingTopicName.d.ts.map +1 -0
  148. package/dist/connectors/telegram/pendingTopicName.js +27 -0
  149. package/dist/connectors/telegram/pendingTopicName.js.map +1 -0
  150. package/dist/connectors/test/connector.d.ts.map +1 -1
  151. package/dist/connectors/test/connector.js +6 -1
  152. package/dist/connectors/test/connector.js.map +1 -1
  153. package/dist/i18n/de.d.ts.map +1 -1
  154. package/dist/i18n/de.js +90 -7
  155. package/dist/i18n/de.js.map +1 -1
  156. package/dist/i18n/en.d.ts.map +1 -1
  157. package/dist/i18n/en.js +90 -7
  158. package/dist/i18n/en.js.map +1 -1
  159. package/dist/i18n/es.d.ts.map +1 -1
  160. package/dist/i18n/es.js +90 -7
  161. package/dist/i18n/es.js.map +1 -1
  162. package/dist/i18n/fr.d.ts.map +1 -1
  163. package/dist/i18n/fr.js +90 -7
  164. package/dist/i18n/fr.js.map +1 -1
  165. package/dist/i18n/hi.d.ts.map +1 -1
  166. package/dist/i18n/hi.js +90 -7
  167. package/dist/i18n/hi.js.map +1 -1
  168. package/dist/i18n/ja.d.ts.map +1 -1
  169. package/dist/i18n/ja.js +90 -7
  170. package/dist/i18n/ja.js.map +1 -1
  171. package/dist/i18n/ka.d.ts.map +1 -1
  172. package/dist/i18n/ka.js +90 -7
  173. package/dist/i18n/ka.js.map +1 -1
  174. package/dist/i18n/pt.d.ts.map +1 -1
  175. package/dist/i18n/pt.js +90 -7
  176. package/dist/i18n/pt.js.map +1 -1
  177. package/dist/i18n/ru.d.ts.map +1 -1
  178. package/dist/i18n/ru.js +90 -7
  179. package/dist/i18n/ru.js.map +1 -1
  180. package/dist/i18n/uk.d.ts.map +1 -1
  181. package/dist/i18n/uk.js +90 -7
  182. package/dist/i18n/uk.js.map +1 -1
  183. package/dist/i18n/uz.d.ts.map +1 -1
  184. package/dist/i18n/uz.js +90 -7
  185. package/dist/i18n/uz.js.map +1 -1
  186. package/dist/i18n/zh.d.ts.map +1 -1
  187. package/dist/i18n/zh.js +90 -7
  188. package/dist/i18n/zh.js.map +1 -1
  189. package/dist/i18n.d.ts +1 -1
  190. package/dist/i18n.js +1 -1
  191. package/dist/installManager.d.ts +13 -0
  192. package/dist/installManager.d.ts.map +1 -1
  193. package/dist/installManager.js +38 -4
  194. package/dist/installManager.js.map +1 -1
  195. package/dist/instanceSettings.d.ts +40 -1
  196. package/dist/instanceSettings.d.ts.map +1 -1
  197. package/dist/instanceSettings.js +105 -26
  198. package/dist/instanceSettings.js.map +1 -1
  199. package/dist/openCodeQuestionFlow.d.ts +30 -0
  200. package/dist/openCodeQuestionFlow.d.ts.map +1 -1
  201. package/dist/openCodeQuestionFlow.js +43 -0
  202. package/dist/openCodeQuestionFlow.js.map +1 -1
  203. package/dist/postToSession.d.ts +20 -5
  204. package/dist/postToSession.d.ts.map +1 -1
  205. package/dist/postToSession.js +38 -7
  206. package/dist/postToSession.js.map +1 -1
  207. package/dist/remoteRun/cliArgs.d.ts +73 -0
  208. package/dist/remoteRun/cliArgs.d.ts.map +1 -0
  209. package/dist/remoteRun/cliArgs.js +197 -0
  210. package/dist/remoteRun/cliArgs.js.map +1 -0
  211. package/dist/remoteRun/commands.d.ts +56 -0
  212. package/dist/remoteRun/commands.d.ts.map +1 -0
  213. package/dist/remoteRun/commands.js +306 -0
  214. package/dist/remoteRun/commands.js.map +1 -0
  215. package/dist/remoteRun/depsPlan.d.ts +16 -0
  216. package/dist/remoteRun/depsPlan.d.ts.map +1 -0
  217. package/dist/remoteRun/depsPlan.js +82 -0
  218. package/dist/remoteRun/depsPlan.js.map +1 -0
  219. package/dist/remoteRun/exitCodes.d.ts +34 -0
  220. package/dist/remoteRun/exitCodes.d.ts.map +1 -0
  221. package/dist/remoteRun/exitCodes.js +38 -0
  222. package/dist/remoteRun/exitCodes.js.map +1 -0
  223. package/dist/remoteRun/foreground.d.ts +29 -0
  224. package/dist/remoteRun/foreground.d.ts.map +1 -0
  225. package/dist/remoteRun/foreground.js +159 -0
  226. package/dist/remoteRun/foreground.js.map +1 -0
  227. package/dist/remoteRun/interrupts.d.ts +17 -0
  228. package/dist/remoteRun/interrupts.d.ts.map +1 -0
  229. package/dist/remoteRun/interrupts.js +41 -0
  230. package/dist/remoteRun/interrupts.js.map +1 -0
  231. package/dist/remoteRun/jobFiles.d.ts +31 -0
  232. package/dist/remoteRun/jobFiles.d.ts.map +1 -0
  233. package/dist/remoteRun/jobFiles.js +84 -0
  234. package/dist/remoteRun/jobFiles.js.map +1 -0
  235. package/dist/remoteRun/jobFormat.d.ts +16 -0
  236. package/dist/remoteRun/jobFormat.d.ts.map +1 -0
  237. package/dist/remoteRun/jobFormat.js +130 -0
  238. package/dist/remoteRun/jobFormat.js.map +1 -0
  239. package/dist/remoteRun/jobId.d.ts +15 -0
  240. package/dist/remoteRun/jobId.d.ts.map +1 -0
  241. package/dist/remoteRun/jobId.js +85 -0
  242. package/dist/remoteRun/jobId.js.map +1 -0
  243. package/dist/remoteRun/jobMeta.d.ts +18 -0
  244. package/dist/remoteRun/jobMeta.d.ts.map +1 -0
  245. package/dist/remoteRun/jobMeta.js +33 -0
  246. package/dist/remoteRun/jobMeta.js.map +1 -0
  247. package/dist/remoteRun/jobQuery.d.ts +12 -0
  248. package/dist/remoteRun/jobQuery.d.ts.map +1 -0
  249. package/dist/remoteRun/jobQuery.js +22 -0
  250. package/dist/remoteRun/jobQuery.js.map +1 -0
  251. package/dist/remoteRun/jobRecords.d.ts +22 -0
  252. package/dist/remoteRun/jobRecords.d.ts.map +1 -0
  253. package/dist/remoteRun/jobRecords.js +145 -0
  254. package/dist/remoteRun/jobRecords.js.map +1 -0
  255. package/dist/remoteRun/jobState.d.ts +62 -0
  256. package/dist/remoteRun/jobState.d.ts.map +1 -0
  257. package/dist/remoteRun/jobState.js +132 -0
  258. package/dist/remoteRun/jobState.js.map +1 -0
  259. package/dist/remoteRun/leaseTiming.d.ts +18 -0
  260. package/dist/remoteRun/leaseTiming.d.ts.map +1 -0
  261. package/dist/remoteRun/leaseTiming.js +43 -0
  262. package/dist/remoteRun/leaseTiming.js.map +1 -0
  263. package/dist/remoteRun/output.d.ts +35 -0
  264. package/dist/remoteRun/output.d.ts.map +1 -0
  265. package/dist/remoteRun/output.js +82 -0
  266. package/dist/remoteRun/output.js.map +1 -0
  267. package/dist/remoteRun/pathPatterns.d.ts +33 -0
  268. package/dist/remoteRun/pathPatterns.d.ts.map +1 -0
  269. package/dist/remoteRun/pathPatterns.js +115 -0
  270. package/dist/remoteRun/pathPatterns.js.map +1 -0
  271. package/dist/remoteRun/probe.d.ts +56 -0
  272. package/dist/remoteRun/probe.d.ts.map +1 -0
  273. package/dist/remoteRun/probe.js +123 -0
  274. package/dist/remoteRun/probe.js.map +1 -0
  275. package/dist/remoteRun/project.d.ts +26 -0
  276. package/dist/remoteRun/project.d.ts.map +1 -0
  277. package/dist/remoteRun/project.js +159 -0
  278. package/dist/remoteRun/project.js.map +1 -0
  279. package/dist/remoteRun/projectConfig.d.ts +38 -0
  280. package/dist/remoteRun/projectConfig.d.ts.map +1 -0
  281. package/dist/remoteRun/projectConfig.js +87 -0
  282. package/dist/remoteRun/projectConfig.js.map +1 -0
  283. package/dist/remoteRun/remoteRunCli.d.ts +15 -0
  284. package/dist/remoteRun/remoteRunCli.d.ts.map +1 -0
  285. package/dist/remoteRun/remoteRunCli.js +144 -0
  286. package/dist/remoteRun/remoteRunCli.js.map +1 -0
  287. package/dist/remoteRun/remoteRunError.d.ts +14 -0
  288. package/dist/remoteRun/remoteRunError.d.ts.map +1 -0
  289. package/dist/remoteRun/remoteRunError.js +28 -0
  290. package/dist/remoteRun/remoteRunError.js.map +1 -0
  291. package/dist/remoteRun/rsync.d.ts +17 -0
  292. package/dist/remoteRun/rsync.d.ts.map +1 -0
  293. package/dist/remoteRun/rsync.js +39 -0
  294. package/dist/remoteRun/rsync.js.map +1 -0
  295. package/dist/remoteRun/runnerClient.d.ts +50 -0
  296. package/dist/remoteRun/runnerClient.d.ts.map +1 -0
  297. package/dist/remoteRun/runnerClient.js +139 -0
  298. package/dist/remoteRun/runnerClient.js.map +1 -0
  299. package/dist/remoteRun/runners.d.ts +57 -0
  300. package/dist/remoteRun/runners.d.ts.map +1 -0
  301. package/dist/remoteRun/runners.js +181 -0
  302. package/dist/remoteRun/runners.js.map +1 -0
  303. package/dist/remoteRun/runnersConfig.d.ts +44 -0
  304. package/dist/remoteRun/runnersConfig.d.ts.map +1 -0
  305. package/dist/remoteRun/runnersConfig.js +107 -0
  306. package/dist/remoteRun/runnersConfig.js.map +1 -0
  307. package/dist/remoteRun/safeCopy.d.ts +31 -0
  308. package/dist/remoteRun/safeCopy.d.ts.map +1 -0
  309. package/dist/remoteRun/safeCopy.js +137 -0
  310. package/dist/remoteRun/safeCopy.js.map +1 -0
  311. package/dist/remoteRun/sendPlan.d.ts +67 -0
  312. package/dist/remoteRun/sendPlan.d.ts.map +1 -0
  313. package/dist/remoteRun/sendPlan.js +139 -0
  314. package/dist/remoteRun/sendPlan.js.map +1 -0
  315. package/dist/remoteRun/ssh.d.ts +62 -0
  316. package/dist/remoteRun/ssh.d.ts.map +1 -0
  317. package/dist/remoteRun/ssh.js +202 -0
  318. package/dist/remoteRun/ssh.js.map +1 -0
  319. package/dist/remoteRun/submit.d.ts +29 -0
  320. package/dist/remoteRun/submit.d.ts.map +1 -0
  321. package/dist/remoteRun/submit.js +142 -0
  322. package/dist/remoteRun/submit.js.map +1 -0
  323. package/dist/requests/answerRequest.d.ts +5 -0
  324. package/dist/requests/answerRequest.d.ts.map +1 -1
  325. package/dist/requests/answerRequest.js +16 -1
  326. package/dist/requests/answerRequest.js.map +1 -1
  327. package/dist/requests/requestLedger.d.ts +13 -0
  328. package/dist/requests/requestLedger.d.ts.map +1 -1
  329. package/dist/requests/requestLedger.js +9 -0
  330. package/dist/requests/requestLedger.js.map +1 -1
  331. package/dist/requests/topicRequestSettle.d.ts +30 -0
  332. package/dist/requests/topicRequestSettle.d.ts.map +1 -0
  333. package/dist/requests/topicRequestSettle.js +23 -0
  334. package/dist/requests/topicRequestSettle.js.map +1 -0
  335. package/dist/requests/wakeUpEngine.d.ts +4 -0
  336. package/dist/requests/wakeUpEngine.d.ts.map +1 -1
  337. package/dist/requests/wakeUpEngine.js +5 -1
  338. package/dist/requests/wakeUpEngine.js.map +1 -1
  339. package/dist/scheduler/delivery.d.ts +2 -0
  340. package/dist/scheduler/delivery.d.ts.map +1 -1
  341. package/dist/scheduler/delivery.js +8 -4
  342. package/dist/scheduler/delivery.js.map +1 -1
  343. package/dist/scheduler/mcpSurface.d.ts +59 -10
  344. package/dist/scheduler/mcpSurface.d.ts.map +1 -1
  345. package/dist/scheduler/mcpSurface.js +276 -79
  346. package/dist/scheduler/mcpSurface.js.map +1 -1
  347. package/dist/scheduler/recurrence.d.ts +5 -0
  348. package/dist/scheduler/recurrence.d.ts.map +1 -1
  349. package/dist/scheduler/recurrence.js +12 -2
  350. package/dist/scheduler/recurrence.js.map +1 -1
  351. package/dist/scheduler/strictToolInput.d.ts +45 -0
  352. package/dist/scheduler/strictToolInput.d.ts.map +1 -0
  353. package/dist/scheduler/strictToolInput.js +155 -0
  354. package/dist/scheduler/strictToolInput.js.map +1 -0
  355. package/dist/scheduler/types.d.ts +5 -0
  356. package/dist/scheduler/types.d.ts.map +1 -1
  357. package/dist/scheduler/workerWatch.d.ts +23 -0
  358. package/dist/scheduler/workerWatch.d.ts.map +1 -0
  359. package/dist/scheduler/workerWatch.js +26 -0
  360. package/dist/scheduler/workerWatch.js.map +1 -0
  361. package/dist/selfUpdate/emittedSource.d.ts +11 -0
  362. package/dist/selfUpdate/emittedSource.d.ts.map +1 -0
  363. package/dist/selfUpdate/emittedSource.js +24 -0
  364. package/dist/selfUpdate/emittedSource.js.map +1 -0
  365. package/dist/selfUpdate/gitSource.d.ts +0 -6
  366. package/dist/selfUpdate/gitSource.d.ts.map +1 -1
  367. package/dist/selfUpdate/gitSource.js +2 -18
  368. package/dist/selfUpdate/gitSource.js.map +1 -1
  369. package/dist/selfUpdate/updatePending.d.ts +1 -1
  370. package/dist/sendErrorClassifier.d.ts +10 -0
  371. package/dist/sendErrorClassifier.d.ts.map +1 -1
  372. package/dist/sendErrorClassifier.js +22 -0
  373. package/dist/sendErrorClassifier.js.map +1 -1
  374. package/dist/state.d.ts +78 -7
  375. package/dist/state.d.ts.map +1 -1
  376. package/dist/state.js +125 -8
  377. package/dist/state.js.map +1 -1
  378. package/dist/stateFile.d.ts +9 -0
  379. package/dist/stateFile.d.ts.map +1 -0
  380. package/dist/stateFile.js +57 -0
  381. package/dist/stateFile.js.map +1 -0
  382. package/dist/stt/sttInstall.d.ts +7 -3
  383. package/dist/stt/sttInstall.d.ts.map +1 -1
  384. package/dist/stt/sttInstall.js +8 -4
  385. package/dist/stt/sttInstall.js.map +1 -1
  386. package/dist/stt/sttSettings.d.ts +2 -1
  387. package/dist/stt/sttSettings.d.ts.map +1 -1
  388. package/dist/stt/sttSettings.js +3 -2
  389. package/dist/stt/sttSettings.js.map +1 -1
  390. package/dist/stt/transcribeChain.js +2 -0
  391. package/dist/stt/transcribeChain.js.map +1 -1
  392. package/dist/testTimings.d.ts +13 -3
  393. package/dist/testTimings.d.ts.map +1 -1
  394. package/dist/testTimings.js +26 -2
  395. package/dist/testTimings.js.map +1 -1
  396. package/dist/todos/board.d.ts +83 -0
  397. package/dist/todos/board.d.ts.map +1 -0
  398. package/dist/todos/board.js +227 -0
  399. package/dist/todos/board.js.map +1 -0
  400. package/dist/todos/format.d.ts +33 -0
  401. package/dist/todos/format.d.ts.map +1 -0
  402. package/dist/todos/format.js +90 -0
  403. package/dist/todos/format.js.map +1 -0
  404. package/dist/todos/merge.d.ts +46 -0
  405. package/dist/todos/merge.d.ts.map +1 -0
  406. package/dist/todos/merge.js +137 -0
  407. package/dist/todos/merge.js.map +1 -0
  408. package/dist/todos/midTurn.d.ts +75 -0
  409. package/dist/todos/midTurn.d.ts.map +1 -0
  410. package/dist/todos/midTurn.js +214 -0
  411. package/dist/todos/midTurn.js.map +1 -0
  412. package/dist/todos/nag.d.ts +85 -0
  413. package/dist/todos/nag.d.ts.map +1 -0
  414. package/dist/todos/nag.js +103 -0
  415. package/dist/todos/nag.js.map +1 -0
  416. package/dist/todos/order.d.ts +25 -0
  417. package/dist/todos/order.d.ts.map +1 -0
  418. package/dist/todos/order.js +61 -0
  419. package/dist/todos/order.js.map +1 -0
  420. package/dist/todos/projects.d.ts +102 -0
  421. package/dist/todos/projects.d.ts.map +1 -0
  422. package/dist/todos/projects.js +342 -0
  423. package/dist/todos/projects.js.map +1 -0
  424. package/dist/todos/registry.d.ts +64 -0
  425. package/dist/todos/registry.d.ts.map +1 -0
  426. package/dist/todos/registry.js +116 -0
  427. package/dist/todos/registry.js.map +1 -0
  428. package/dist/todos/reminderEngine.d.ts +78 -0
  429. package/dist/todos/reminderEngine.d.ts.map +1 -0
  430. package/dist/todos/reminderEngine.js +162 -0
  431. package/dist/todos/reminderEngine.js.map +1 -0
  432. package/dist/todos/store.d.ts +34 -0
  433. package/dist/todos/store.d.ts.map +1 -0
  434. package/dist/todos/store.js +208 -0
  435. package/dist/todos/store.js.map +1 -0
  436. package/dist/todos/tools.d.ts +26 -0
  437. package/dist/todos/tools.d.ts.map +1 -0
  438. package/dist/todos/tools.js +25 -0
  439. package/dist/todos/tools.js.map +1 -0
  440. package/dist/todos/types.d.ts +85 -0
  441. package/dist/todos/types.d.ts.map +1 -0
  442. package/dist/todos/types.js +30 -0
  443. package/dist/todos/types.js.map +1 -0
  444. package/dist/trackers/taskAgentLimit.d.ts +58 -0
  445. package/dist/trackers/taskAgentLimit.d.ts.map +1 -0
  446. package/dist/trackers/taskAgentLimit.js +62 -0
  447. package/dist/trackers/taskAgentLimit.js.map +1 -0
  448. package/dist/trackers/taskAgentSlots.d.ts +109 -0
  449. package/dist/trackers/taskAgentSlots.d.ts.map +1 -0
  450. package/dist/trackers/taskAgentSlots.js +227 -0
  451. package/dist/trackers/taskAgentSlots.js.map +1 -0
  452. package/dist/trackers/types.d.ts +14 -0
  453. package/dist/trackers/types.d.ts.map +1 -0
  454. package/dist/trackers/types.js +3 -0
  455. package/dist/trackers/types.js.map +1 -0
  456. package/dist/types.d.ts +80 -4
  457. package/dist/types.d.ts.map +1 -1
  458. package/dist/utils/adoptReplayGate.d.ts +16 -0
  459. package/dist/utils/adoptReplayGate.d.ts.map +1 -0
  460. package/dist/utils/adoptReplayGate.js +35 -0
  461. package/dist/utils/adoptReplayGate.js.map +1 -0
  462. package/dist/utils/agentPlugin.d.ts +113 -0
  463. package/dist/utils/agentPlugin.d.ts.map +1 -0
  464. package/dist/utils/agentPlugin.js +192 -0
  465. package/dist/utils/agentPlugin.js.map +1 -0
  466. package/dist/utils/autoContinueOnLimit.d.ts +7 -4
  467. package/dist/utils/autoContinueOnLimit.d.ts.map +1 -1
  468. package/dist/utils/autoContinueOnLimit.js +8 -5
  469. package/dist/utils/autoContinueOnLimit.js.map +1 -1
  470. package/dist/utils/botSkills.d.ts +62 -0
  471. package/dist/utils/botSkills.d.ts.map +1 -0
  472. package/dist/utils/botSkills.js +53 -0
  473. package/dist/utils/botSkills.js.map +1 -0
  474. package/dist/utils/botSkillsSetting.d.ts +7 -0
  475. package/dist/utils/botSkillsSetting.d.ts.map +1 -0
  476. package/dist/utils/botSkillsSetting.js +9 -0
  477. package/dist/utils/botSkillsSetting.js.map +1 -0
  478. package/dist/utils/claudeCodeVersion.d.ts +7 -0
  479. package/dist/utils/claudeCodeVersion.d.ts.map +1 -1
  480. package/dist/utils/claudeCodeVersion.js +14 -1
  481. package/dist/utils/claudeCodeVersion.js.map +1 -1
  482. package/dist/utils/claudeCompactHook.d.ts +33 -15
  483. package/dist/utils/claudeCompactHook.d.ts.map +1 -1
  484. package/dist/utils/claudeCompactHook.js +36 -17
  485. package/dist/utils/claudeCompactHook.js.map +1 -1
  486. package/dist/utils/claudeCompactSummary.d.ts +15 -0
  487. package/dist/utils/claudeCompactSummary.d.ts.map +1 -0
  488. package/dist/utils/claudeCompactSummary.js +96 -0
  489. package/dist/utils/claudeCompactSummary.js.map +1 -0
  490. package/dist/utils/claudeLimitSwitch.d.ts +11 -9
  491. package/dist/utils/claudeLimitSwitch.d.ts.map +1 -1
  492. package/dist/utils/claudeLimitSwitch.js +18 -16
  493. package/dist/utils/claudeLimitSwitch.js.map +1 -1
  494. package/dist/utils/claudeProfiles.d.ts +5 -0
  495. package/dist/utils/claudeProfiles.d.ts.map +1 -1
  496. package/dist/utils/claudeProfiles.js +14 -0
  497. package/dist/utils/claudeProfiles.js.map +1 -1
  498. package/dist/utils/claudeSessionTitle.d.ts +2 -1
  499. package/dist/utils/claudeSessionTitle.d.ts.map +1 -1
  500. package/dist/utils/claudeSessionTitle.js +14 -14
  501. package/dist/utils/claudeSessionTitle.js.map +1 -1
  502. package/dist/utils/claudeUsage.d.ts +95 -15
  503. package/dist/utils/claudeUsage.d.ts.map +1 -1
  504. package/dist/utils/claudeUsage.js +241 -41
  505. package/dist/utils/claudeUsage.js.map +1 -1
  506. package/dist/utils/claudeUsageRefresher.d.ts +10 -0
  507. package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
  508. package/dist/utils/claudeUsageRefresher.js +11 -0
  509. package/dist/utils/claudeUsageRefresher.js.map +1 -1
  510. package/dist/utils/claudeUserEntry.d.ts +50 -0
  511. package/dist/utils/claudeUserEntry.d.ts.map +1 -0
  512. package/dist/utils/claudeUserEntry.js +120 -0
  513. package/dist/utils/claudeUserEntry.js.map +1 -0
  514. package/dist/utils/clearThreadOutputQueues.d.ts +4 -3
  515. package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -1
  516. package/dist/utils/clearThreadOutputQueues.js +1 -1
  517. package/dist/utils/clearThreadOutputQueues.js.map +1 -1
  518. package/dist/utils/compactOnIdle.d.ts +33 -13
  519. package/dist/utils/compactOnIdle.d.ts.map +1 -1
  520. package/dist/utils/compactOnIdle.js +35 -15
  521. package/dist/utils/compactOnIdle.js.map +1 -1
  522. package/dist/utils/fallbackBridge.d.ts +5 -0
  523. package/dist/utils/fallbackBridge.d.ts.map +1 -1
  524. package/dist/utils/fallbackBridge.js +19 -3
  525. package/dist/utils/fallbackBridge.js.map +1 -1
  526. package/dist/utils/fallbackChain.d.ts +9 -3
  527. package/dist/utils/fallbackChain.d.ts.map +1 -1
  528. package/dist/utils/fallbackChain.js +21 -3
  529. package/dist/utils/fallbackChain.js.map +1 -1
  530. package/dist/utils/fileSendPlan.d.ts +6 -0
  531. package/dist/utils/fileSendPlan.d.ts.map +1 -1
  532. package/dist/utils/fileSendPlan.js +11 -0
  533. package/dist/utils/fileSendPlan.js.map +1 -1
  534. package/dist/utils/fileSendService.d.ts +7 -1
  535. package/dist/utils/fileSendService.d.ts.map +1 -1
  536. package/dist/utils/fileSendService.js +6 -2
  537. package/dist/utils/fileSendService.js.map +1 -1
  538. package/dist/utils/groupFinalizePlan.d.ts +8 -10
  539. package/dist/utils/groupFinalizePlan.d.ts.map +1 -1
  540. package/dist/utils/groupFinalizePlan.js +8 -12
  541. package/dist/utils/groupFinalizePlan.js.map +1 -1
  542. package/dist/utils/jsonStreamHost.d.ts +13 -0
  543. package/dist/utils/jsonStreamHost.d.ts.map +1 -1
  544. package/dist/utils/jsonStreamHost.js +13 -0
  545. package/dist/utils/jsonStreamHost.js.map +1 -1
  546. package/dist/utils/messageSendService.d.ts +5 -3
  547. package/dist/utils/messageSendService.d.ts.map +1 -1
  548. package/dist/utils/messageSendService.js +12 -5
  549. package/dist/utils/messageSendService.js.map +1 -1
  550. package/dist/utils/openCodeCompactPlugin.d.ts +39 -14
  551. package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -1
  552. package/dist/utils/openCodeCompactPlugin.js +106 -38
  553. package/dist/utils/openCodeCompactPlugin.js.map +1 -1
  554. package/dist/utils/promptDelivery.d.ts +29 -12
  555. package/dist/utils/promptDelivery.d.ts.map +1 -1
  556. package/dist/utils/promptDelivery.js +13 -5
  557. package/dist/utils/promptDelivery.js.map +1 -1
  558. package/dist/utils/sentKindsSummary.d.ts +10 -0
  559. package/dist/utils/sentKindsSummary.d.ts.map +1 -0
  560. package/dist/utils/sentKindsSummary.js +18 -0
  561. package/dist/utils/sentKindsSummary.js.map +1 -0
  562. package/dist/utils/telegramError.d.ts +6 -0
  563. package/dist/utils/telegramError.d.ts.map +1 -1
  564. package/dist/utils/telegramError.js +12 -0
  565. package/dist/utils/telegramError.js.map +1 -1
  566. package/dist/utils/topicView.d.ts +1 -1
  567. package/dist/utils/topicView.js +1 -1
  568. package/dist/utils/transcribeAudio.d.ts +10 -0
  569. package/dist/utils/transcribeAudio.d.ts.map +1 -1
  570. package/dist/utils/transcribeAudio.js +22 -3
  571. package/dist/utils/transcribeAudio.js.map +1 -1
  572. package/dist/utils/transcriptErrorTime.d.ts +8 -0
  573. package/dist/utils/transcriptErrorTime.d.ts.map +1 -0
  574. package/dist/utils/transcriptErrorTime.js +56 -0
  575. package/dist/utils/transcriptErrorTime.js.map +1 -0
  576. package/dist/utils/wedgeRecovery.d.ts +2 -0
  577. package/dist/utils/wedgeRecovery.d.ts.map +1 -1
  578. package/dist/utils/wedgeRecovery.js +1 -1
  579. package/dist/utils/wedgeRecovery.js.map +1 -1
  580. package/package.json +11 -3
  581. package/remoteRun/runner.sh +1242 -0
  582. package/stt/gigaam_sidecar.py +555 -50
package/README.md CHANGED
@@ -6,68 +6,39 @@
6
6
  <a href="https://github.com/olosegres/charness/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/charness" alt="license"></a>
7
7
  </p>
8
8
 
9
- <h2 align="center">Run multiple Claude Code / OpenCode agents and connect them to your Telegram or Jira.</h2>
9
+ <h2 align="center">Harness for running Claude Code / OpenCode agents in the cloud or on your laptop, controlled from Telegram or Jira.</h2>
10
+
11
+ **You talk. Your J.A.R.V.I.S. does the rest.** Send a voice note from the street — and an agent on your server opens the browser, calls your APIs, writes code, edits a video, drafts a post, generates an image, answers your email or messages someone for you, and sends you the result. It never sleeps: it keeps working while you do. Plug in a tool — and that becomes one more thing it can do.
12
+
13
+ **Charness is a gateway between people and AI agents.** A Claude Code or OpenCode agent works in a project folder on a VPS / VDS or on your laptop, and you reach it from a Telegram topic or a Jira issue. On that gateway you set up AI pipelines for any work, not only code: a topic is one pipeline with its own folder, agent, schedule and checks.
10
14
 
11
15
  - **In Telegram** you control your agents through a bot or a group, so it becomes a terminal for OpenCode and Claude Code — every thread is like a terminal tab, plus scheduling and voice control.
12
16
  - **In Jira** it starts an agent when you assign a ticket to a special user: the agent reads the description, comments and attachments and does the work according to the ticket's status — like a real employee.
13
17
 
14
- <p align="center">It runs fine on your laptop. But it is called a <b>c</b>loud <b>harness</b> for a reason — put it on a server and your agents keep working while the laptop sleeps.</p>
18
+ <p align="center">It runs fine on your laptop. But it is called a <b>c</b>loud <b>harness</b> for a reason — put it on a VPS or VDS and your agents keep working while the laptop sleeps. Give an agent a cron schedule and it takes on a recurring task by itself, in the background, while you do something else.</p>
15
19
 
16
20
  <p align="center">
17
21
  <img src="docs/images/hero-v2.png" alt="Charness — all your agents in Telegram; chat, code, ship; powerful slash commands" width="900" />
18
22
  </p>
19
23
 
20
- ## What it does
21
-
22
- ### Common (any surface)
23
-
24
- These hold whichever surface you drive it from — the same core serves all of them:
25
-
26
- - **Agent backends** — OpenCode (native server API over HTTP+SSE) and Claude Code (three backends — json-stream, per-turn and tmux)
27
- - **Sleeping agents** — a Claude json-stream topic's process is stopped when idle (or after every turn in per-turn mode) and the next message resumes the same conversation by itself, prompt cache still warm
28
- - **Several Claude accounts** — keep more than one Claude subscription signed in side by side and choose per topic which one a session runs on (`/accounts`); every account's usage windows are shown next to it, and `/limits` is a one-tap switch board over all of them and over the OpenCode providers. See [Several accounts](#several-accounts-accounts)
29
- - **Waits out usage limits** — when the agent hits a usage / session limit the bot reads the reset time out of the error (honouring the timezone the provider quotes; a mere warning such as `Approaching session limit · resets …` or the switch onto extra usage is not a limit), waits, then tells the agent to continue by itself and pins the "work resumed" message so a muted topic still notifies you; `/limits_autoswitch` turns waiting off per topic and `/limits` skips a single resume. With a **fallback chain** (`/limits_autoswitch`: other Claude accounts, OpenCode providers and models, in your order of preference) the limit notice offers «🔁 Switch to …» buttons to move the topic to the next entry with room and continue at once — or the bot does it by itself when you turn switching on; across backends the new agent gets a context bridge with the previous one's last turns
30
- - **Updates itself** — checks for a new version hourly, installs it when no agent is working, restarts in place (same process, agents keep running) and says in General what changed; npm installs and git checkouts, hot mode and Docker included. Off with `/update` in General. See [Updating](SETUP.md#updating)
31
- - **Restart-safe** — agents run as external processes (tmux, `opencode serve`) that the bot re-adopts after a restart instead of killing them; a prompt sent while a session boots is buffered and replayed. See [Restart behaviour](#restart-behaviour)
32
-
33
- ### Telegram
34
-
35
- Charness turns Telegram into a terminal for running agentic CLIs. Open as
36
- many tabs as you need — even two on the same project for parallel work — and
37
- drive the agents from your phone or tablet, voice messages included.
38
-
39
- Think **OpenClaw for vibe coding**, driven entirely from Telegram: no complex
40
- setup, no extra dashboards — direct access to your own **OpenCode** /
41
- **Claude Code** running on your machine or server.
42
-
43
- - **Simple setup & management** — install the CLI, put your bot token in one `.env`, bind a topic to a folder, and run; everything after is managed from Telegram — no dashboards
44
- - **Multi-thread / topic** — one topic per directory with your agent; two topics can share one folder for parallel work — run almost unlimited agents in parallel
45
- - **Two chat surfaces** — forum-group topics and the owner's bot DM, both served at once by default, with tabs on either
46
- - **Raw terminal** — `/terminal` binds a topic to a real `$SHELL` in the project folder
47
- - **Notifications** — when the agent asks a question, the bot pins that message so even a muted topic notifies you: the pin pierces mute, giving exactly one notification per question (unpinned once you answer)
48
- - **Scheduled & self-driving runs** — `/schedule` arms cron / one-shot / N-times jobs per topic, and the agent can schedule *itself* via injected MCP tools: nightly reviews, recurring reports, a "finish this tomorrow at 9" hand-off. Each job fires as a fresh session with your prompt; restart-safe, with one catch-up for a run missed while the bot was down
49
- - **Reminders** — `/reminders` sets a plain reminder with inline buttons (how often, which day, what time, then the text by typing or voice); at the due time the bot posts and pins it itself, so even a muted topic notifies you. No agent, no bound folder, no date parsing — works in any topic, General included
50
- - **Inbound files** — photos / documents / video / audio sent to a topic are saved and announced to the agent; albums arrive as one prompt
51
- - **Outbound files** — the agent sends files back to you — a generated chart, a screenshot, a log, a rendered PDF — single files or albums, straight into the topic
52
- - **Voice input** — voice notes are transcribed by an ordered chain of engines — Groq, OpenAI or your own OpenAI-compatible server — set up in `/stt` (or `stt.json`); a local GigaAM engine is optional; if one fails, the next takes over
53
- - **Display verbosity and view** — `/verbosity` (plus `/thinking`, `/tool_results`, `/subagent`) per topic: detail `minimal|short|full`, and what the topic shows — `stream` (default), `stream_answers` or `answers` (only the agent's pinned answers, no stream)
54
- - **Time-aware prompts** — `/timestamps on` prepends each forwarded prompt's send time (local-offset ISO), so a days-long session knows what "yesterday" or "2 days ago" means; agent-facing only, per topic
55
- - **Reply context** — reply to a message in a topic and the quoted text is folded into the prompt the agent receives, so it sees what you point at without re-pasting; works for text, voice and files (a photo, a document, an album…), both agents; a reply TO a photo, file or voice note names it, with the path of the bot's saved copy when it has one
56
- - **Your timezone** — `/timezone Europe/Moscow` once, and schedules fire at your wall-clock time while the agent is told what time it actually is; no server-clock guessing
57
-
58
- #### Two surfaces: group topics, bot DM, or both
59
-
60
- The bot serves two Telegram surfaces:
61
-
62
- - **Forum supergroup** — every topic is a tab. The most familiar UX: a visible
63
- topic list, per-topic names and icons, quick switching between agents. The
64
- trade-off is Telegram's group throughput cap (about 20 messages/min per
65
- group), so the bot paces and coalesces heavy output streams.
66
- - **The bot's own DM** (owner only) — the private
67
- chat runs in topic mode too, so you get the same thread-per-agent tabs
68
- there, and live output streams through Telegram's native draft "cursor":
69
- the reply grows in place in one message instead of arriving as a flood of
70
- separate messages.
24
+ ## Use cases
25
+
26
+ One topic is one AI pipeline: its own project folder, its own agent, its own schedule and checks. Some ways to use it:
27
+
28
+ - **Development** — fix bugs, review changes and work through Jira issues: assign a ticket to the agent's user, or drive it from a topic by text or voice.
29
+ - **Marketing** — a topic with the product docs and brand notes: the agent drafts copy, landing-page variants and campaign plans and sends the files back.
30
+ - **Blogging and content** — draft, edit and illustrate posts in one folder; a scheduled agent can prepare the next draft or the content plan every week.
31
+ - **Research** — an agent reads the sources and papers in a notes folder and keeps a running write-up; ask follow-ups in the same topic.
32
+ - **Video production** — scripts, cuts, subtitles and renders on a server with the tools installed; the agent sends the finished clips back into the topic.
33
+ - **Images** — with an image tool or model connected (an MCP server or a CLI on the server), the agent generates pictures and sends them into the topic.
34
+ - **Email and messengers** — with a mail or messenger tool connected (an MCP server), the agent answers an email or sends a message for you; ask it by voice, check the result in the topic.
35
+ - **Monitoring and on-call** — a scheduled check runs a command (a service, a deploy, a disk, a URL), stays silent while it passes, and on the first failure pins an alert and wakes the agent with the output.
36
+ - **Personal assistant and planning** — reminders and recurring tasks: a morning plan, a weekly review, a "finish this tomorrow at 9" hand-off, all by voice from your phone.
37
+ - **Data analysis and reports** — drop a spreadsheet or a CSV into the topic; the agent analyses it and sends back charts and a report, and a cron job rebuilds the report every week.
38
+ - **Translation and localization** — an agent keeps a project's locale files in sync: translates new strings and checks the existing ones.
39
+ - **Inbox triage and support** — a scheduled agent sorts incoming messages or support requests (through the MCP servers you connect), drafts replies and posts what needs a human.
40
+ - **News aggregation** — a morning digest in a topic: the agent collects the sources you list and posts them as one digest message.
41
+ - **Tracking new research and publications** — arXiv, blogs, releases of the tools you depend on: a scheduled agent checks them and posts a short summary of what is new.
71
42
 
72
43
  ## Quick start
73
44
 
@@ -85,731 +56,7 @@ Full setup — Telegram, Jira, environment, updating: [SETUP.md](SETUP.md)
85
56
  limits: OpenCode runs, but Claude and `/terminal` need tmux, a self-update cannot restart the bot in place, hot mode refuses
86
57
  to start and the local speech engine cannot be installed. A native Windows bot says so at start (console, General, `/doctor`).
87
58
 
88
- ## Reference
89
-
90
- ### Commands (Telegram)
91
-
92
- #### In any topic
93
-
94
- Most commands work in any topic; a binding (`/bind`) is required only to
95
- actually start an agent or terminal in the folder.
96
-
97
- | Command | Description |
98
- |---|---|
99
- | `/claude`, `/opencode`, `/oc` | Start agent in this topic's bound folder |
100
- | `/terminal` | Open a raw `$SHELL` in the bound folder — see [Raw terminal](#raw-terminal-terminal-telegram) |
101
- | `/claude_mode` | Switch this topic's Claude backend (json-stream ⇄ per-turn ⇄ tmux-scrape); bare shows a picker — see [Claude Code backends](#claude-code-backends-claude_mode) |
102
- | `/model` | Switch model through a two-level picker: providers, then that provider's models (10 per page). On OpenCode the first row is 🕘 Recently used — a flat list of the models picked before, across providers (bot-wide, most recent first, up to 10), one tap to switch; it appears once something has been picked. Each provider row has a 🙈 to hide it from the picker (bot-wide, persisted; 👁 brings it back) — `/model <provider/model>` still reaches a hidden provider. If OpenCode is waiting on an old provider's retry, the next prompt interrupts that wait and starts with the selected model instead of sitting queued behind it |
103
- | `/accounts` | Every account the bot works with, on one screen. **Claude accounts** (when you keep several subscriptions signed in): each profile with its email, plan (personal Pro/Max or corporate Team/Enterprise), how many topics use it and its usage windows, ✓ on the active one; tap a profile to switch this topic (a running session is restarted under it, same conversation — at the end of its turn if it is working; on a topic that runs OpenCode the tap switches the topic to Claude on that account, after a confirmation when there is a conversation to hand over); in **General** the pick becomes the default for all topics. The title says where the topic really runs (`Claude «work»`, `OpenCode openai/gpt-5`) and the ✓ sits on that row — on no account while OpenCode runs. Per profile: 🔑 signs it in again, 🗑 removes it (after a confirmation; for `default`, which IS `~/.claude`, «remove» signs it out — see below). The usage lines give each window's percentage USED and when it resets as a time from now (`5h 5% 🔄 in 4h · week 20% 🔄 in 5d 8h · Fable 4% 🔄 in 5d 8h`; «🔄 in 2h», «🔄 in 2d 1h», «🔄 in 30m»; Fable only on a plan that has its own weekly window); when the 5h window's reset is unknown the weekly one is shown in its place, and with no reset at all only the percentage. The hint paragraph under the account list says the percentages are used. «➕ Claude account» asks for a name and runs the sign-in; «➕ Claude API account (Console credits)» adds an account billed to a Claude Console organization's API credits (`💳`, see [An account billed to API credits](#an-account-billed-to-api-credits-claude-console)). **OpenCode providers**: the connected ones are listed with how each is signed in (`ChatGPT subscription` with its ~5 h / ~7 d usage windows like an account's, `API key`). In a topic a provider row is `[provider] [🔑] [🔌]`: a tap switches the topic to the provider through its model page (✓ on the model it runs, ⛔ on a limited one; a provider with one model is picked at once), 🔑 connects it again, 🔌 disconnects it (it asks, then removes the stored credentials; a provider OpenCode enables from an environment variable, e.g. `OPENROUTER_API_KEY`, stays active — the reply says so and points at hiding it in `/model`); in **General** a provider row is «🔌 Disconnect <provider>» and 🔑 only; «➕ OpenCode provider» asks for the provider id and continues with the connect flow (OAuth methods when available, otherwise an API key whose message is deleted from Telegram). What happens when an account hits a usage limit is set in `/limits_autoswitch`. Text forms: `/accounts add <name>`, `/accounts add <name> api`, `/accounts use <name>`, `/accounts use <provider>/<model>` (an OpenCode model, in a topic), `/accounts login <name>`, `/accounts remove <name>`, `/accounts autoswitch on\|off`. `/account`, `/connect [provider [key]]` and `/disconnect [provider]` still work as hidden aliases (the menu lists only `/accounts`). See [Several accounts](#several-accounts-accounts) |
104
- | `/effort` | Reasoning effort. **In a topic:** shows the level its session runs and where the next start's level comes from («medium — as in General» / «high — set for this topic»; a running session keeps the level it started with when General moves on: «xhigh (medium from the next start) — as in General») with a button per level — Claude: the native `/effort` levels (`low…ultracode`); OpenCode: the current model's variants, applied per-prompt; General's `ultracode` is the model's highest variant and `auto` sends no variant — and «As in General (medium)», named with the level the topic will really get (OpenCode: rounded to the model's variants), which drops the topic's own pick (a live session is brought to the default at once). **In General:** the default effort for every topic that has no effort of its own (✓ on the current one; `medium` until you pick), saved to `settings.json` and applied from each topic's next session start or wake-up — running sessions are not restarted. No env configuration — see [Instance settings](SETUP.md#instance-settings-settingsjson) |
105
- | `/verbosity` | Output-verbosity macro (`minimal\|short\|full`): sets the thinking, tool-results and sub-agent display prefs at once; `/thinking`, `/tool_results`, `/subagent` point-override afterwards. Mixed prefs show as "custom" in the picker. The picker's second row (or `/verbosity stream\|stream_answers\|answers`) sets what the topic SHOWS — "Stream only" (default), "Stream + answers" (the stream plus the agent's pinned answers) or "Answers only" (the pinned answers, no stream; an agent that keeps writing after its final answer is reminded once to send what matters); the answers are written like for a manager or QA — brief and to the point; a switch applies from the next message, and `/status` names the view |
106
- | `/thinking` | Chain-of-thought display: `full` keeps the reasoning, `short` collapses to "💭 thought for Ns", `minimal` keeps only the live working cue |
107
- | `/tool_results` | Completed tool-call output: `full` whole body, `short` capped (15 lines / 1200 chars), `minimal` transient 🔧 status only |
108
- | `/subagent` | Sub-agent transcript: status-only with a ticking elapsed counter (`minimal`/`short`), or streamed "🤖 ⤷" chunks (`full`) |
109
- | `/sessions` | List & resume previous sessions in this folder |
110
- | `/rename_session` | Rename the current live session (OpenCode; Claude transcripts have no title) |
111
- | `/quit`, `/q` | End the session — Claude: graceful double Ctrl+C; OpenCode/terminal: `stopSession`. Releases the persisted session id, so a bot restart won't auto-reattach it (resume later via `/sessions`). A topic whose agent process is gone but whose session is kept (it "sleeps": the next message, file, voice note, `/compact`, `/model` or `/effort` resumes it by itself) is released the same way |
112
- | `/new`, `/clear_session` | End the current session and start a fresh one (same adapter) |
113
- | `/status` | This thread's status: agent, bound folder, the Claude account it runs on and the resolved workdir; a live session also shows the model it is actually running, the effort it runs (when the default moved on under it: «xhigh (medium from the next start)»), start time, runtime version and context use (anything the backend cannot report renders as unknown). On a Claude topic the account is its profile (name · email as in `/accounts`, `· 💳 API` for an account billed to API credits — with a line of what it spent through the bot — marked ⛔ while it is at its limit, with a second row for a switch still pending); an OpenCode topic's model already names its provider |
114
- | `/output` | Last 500 lines of agent output (sent as at most 5 chunks; the overflow is reported as omitted) |
115
- | `/c`, `/y`, `/n` | Ctrl+C / "y" / "n" |
116
- | `/enter`, `/up`, `/down`, `/tab` | tmux key passthrough |
117
- | `/interrupt`, `/esc`, `/escape` | Interrupt the running turn — Claude gets a raw Escape (which also dismisses a selector), OpenCode aborts the generation; the conversation stays and your next message steers the agent. A question the agent is waiting on is closed first (rejected on OpenCode, declined on the Claude stream-json backends) and its buttons are retired. A pending sign-in, account-name prompt or `/topic` wizard is cancelled first instead (the agent is not interrupted). Not supported in a `/terminal` topic |
118
- | `/schedule` | Schedule a prompt in free text — the agent parses the time and owns the job; see [Scheduler](#scheduler-schedule-telegram) |
119
- | `/stt` | Voice recognition, from any topic (General included); instance-wide, applied to the next voice note — no restart. The main screen holds only what applies to recognition as a whole: the ordered list of **engines** (the first that works answers; when one fails the next takes over) — one full-width button each (`1. GigaAM (local) · running · 10s+25% · ✓` — name, the local engine's short state, timeout `base s + % of the voice`, ✓ usable / ✗ disabled or skipped) — then «➕ Add engine», «🌐 Language» (auto or `ru en uk de fr es`) and «✖️ Close». A tap on an engine opens its **card**: ⬆️ Up / ⬇️ Down, ✅ Use / 🚫 Don't use, ⏱ Timeout (presets), then what the kind adds — 🔑 key for **Groq**, **OpenAI** or a **custom** OpenAI-compatible server (its address, a base like `http://host:8000` is completed; then «does it need a key?»), and 🗑 Remove. A pasted key is deleted from the topic the moment it is accepted, never shown again and kept out of the logs; an address with a login in it is refused. **GigaAM (local)** (Russian only, runs on this machine) is added through «➕ Add engine» like the others: installed, it joins the list at once; not installed, a confirmation (≈ 400 MB on disk; ≈ 0.5 GB RAM while it runs, up to ≈ 0.9 GB on a long voice, nothing while stopped) leads to the install with a live progress message, and the message becomes its card. Its card holds everything local: the status line (ready · running / stopped / paused after failed starts / installing / not installed), «⏱ Exit after idle» (5 / 15 / 30 / 60 min or never), «⚙️ Workers» (1 / 2 / 4 / 8), «🔄 Try again now» (while paused), «Remove from list» (the files stay) and «🗑 Remove model files». Everything lives in one file, `stt.json` — see [Voice input](SETUP.md#voice-input). `/voice` and `/speech` are hidden aliases |
120
- | `/reminders` | Reminders set with buttons only — the bot posts and pins them itself, with no agent involved; works in any topic, bound or not. See [Reminders](#reminders-reminders-telegram) |
121
- | `/clear_messages` | Delete bot messages in this topic (up to 48h, Telegram limit) |
122
- | `/clear` | Forwarded to the agent (Claude wipes context; OpenCode plain text) — not a bot command anymore. Also purges the topic's file-intake dir |
123
- | `/compact` | Compact the agent's context — a real, confirmed compaction on OpenCode and on the default (stream) Claude backend; the tmux Claude backend has the literal command forwarded (its TUI compacts); terminal: not supported |
124
- | `/compact_on_idle` | Toggle auto-compaction after ~55 min idle (Enable/Disable picker; `AGENT_IDLE_MINUTES` sets the window). Per topic; run it in **General** to set the default for all topics. On by default. Each idle compaction posts one short notice; the picker's «Show summary» / «Hide summary» row (the `/compact_summary` switch) adds the agent's full summary after it. Fires at most once per active period (until you write again); if a question was pending it is re-asked with tappable buttons after the compaction. On the json-stream backends the idle mark also STOPS the agent process, compaction or not — the topic sleeps and your next message resumes it (`/status` says so) |
125
- | `/compact_summary` | Toggle posting the agent's full summary after a compaction — `/compact`, the idle compaction and the agent's own `compact_conversation` (Enable/Disable picker). Per topic; run it in **General** to set the default for all topics. Off by default: a compaction ends in a short line. On OpenCode it also hides the summary OpenCode streams itself |
126
- | `/limits` | The **switch board**: every signed-in Claude account and every connected OpenCode provider on one screen. The **message** carries the figures — `Usage (% used)` and a line per account in the same wording `/accounts` uses (`default — ⏱ 5h 14% 🔄 in 3h · week 3% 🔄 in 6d`; `⛔ at its limit until …` while the account is limited; `(stale)` when the last poll could not refresh), a provider signed in through a ChatGPT subscription after them (`openai (ChatGPT) — ⏱ 5h 12% 🔄 in 3h · week 40% 🔄 in 4d`), the two automatic-switching settings' state with a link to `/limits_autoswitch`, and the caption «Choose the active model:». The **buttons** are one full-width name each — `✓ Claude · work`, `Claude · default`, `OpenCode · openai`, `Claude · console 💳` for an account billed to API credits — with no figure on them (the figures do not fit a phone's button); ✓ marks where the topic runs; a signed-out account is not listed. **A tap switches this topic**: another Claude account (the running session restarts under it, same conversation — at the end of its turn when it is working), an OpenCode provider (opens its model page, 10 models per page with ⛔ on a limited one; a provider with one model switches at once), the ✓ row (the model page of where the topic runs: change the model). Across backends with a conversation to hand over the bot asks first (✅ Continue / ✖️ Cancel); while a usage-limit wait is armed the tap is the limit move — the wait ends and the work continues, no question — and «⏭ Skip once» is offered. An unbound topic is told to bind a folder first. In **General** a Claude row sets the default account for all topics (the caption reads «Choose the default Claude account:»), and an OpenCode row only says that a model is picked inside a topic. The limit notice names this command. The old text forms `/limits rebuild`, `/limits clear`, `/limits switch …`, `/limits wait …` still work (they are `/limits_autoswitch`'s verbs). See [Usage limits and the fallback models](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch) |
127
- | `/limits_autoswitch` | The setup of automatic switching on a usage limit, with a short text: a title, what the topic runs on and where its two settings come from. **Fallback models** — an ordered list of Claude accounts (with an optional model) and OpenCode providers with a model, top = preferred, where a topic continues when its agent hits a limit. One full-width button per entry (`✓ 1. Claude · work · opus`, `2. Claude · default ⛔ 14:00`, `3. OpenCode · openai · gpt-5 🚫` — ✓ where the topic runs, ⛔ limited until, 🔒 not signed in, ❓ no longer offered, 💸 API credits used up, 🚫 left out of automatic switching); a tap opens that entry in the same message with ⬆️ Up / ⬇️ Down, 🚫 Don't use / ✅ Use (a left-out entry stays usable by hand through `/model` and `/accounts`), 🗑 Remove and ⬅️ Back. Below the list: «➕ Add» (account or provider, then the model; an entry of a backend the list does not use yet — an OpenCode model next to Claude accounts, or the reverse — asks first, because a limit then hands a conversation over in compacted form; including a 🚫 entry that does that asks the same way), «🔄 Fill from connected» (a draft from what is connected; entries you left out stay left out; an account billed to API credits comes last and left out unless you had included it; asks first when a list exists), «🗑 Clear». The list is ONE instance-wide list and is editable from every topic. **Two settings** decide what a limit does: «🔁 Auto switch» — auto switch to a fallback model (OFF by default: the limit notice carries «🔁 Switch to …» buttons instead) and «⏳ Wait for reset» — wait for the reset and continue (ON by default; while a wait is armed the topic screen also offers «⏭ Skip once»). In General they are the defaults for all topics and each is one button that flips the value. In a regular topic each is one short full-width button showing the effective value (`🔁 Auto switch: ON`; one line of text above the list says where each value comes from — `🔁 as in General · ⏳ set for this topic`); a tap opens the setting in the same message with ON / OFF / «As in General (<the General value>)» — the topic's own choice marked ✓ — and ⬅️ Back, and a choice applies and returns to the screen. Switch ON: the topic moves to the first available entry at once. OFF: buttons on the notice, and if none is pressed the bot waits for the reset (waiting ON); both OFF: it only reports the limit. When every enabled entry, the current one included, is limited, the wait ends at the EARLIEST reset and the topic continues on that entry (the notice names it and the time). Text forms: `/limits_autoswitch rebuild`, `/limits_autoswitch clear`, `/limits_autoswitch switch on\|off\|same`, `/limits_autoswitch wait on\|off\|same` (the same verbs work after `/limits`). `/fallback` and `/auto_continue_limits [on\|off]` still work as hidden aliases. See [Usage limits and the fallback models](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch) |
128
- | `/bind` | Bare: current binding + folder picker, with «leave current dir» (the old `/unbind`) and «create new folder» buttons |
129
- | `/topic [name]` | Open another forum topic on a folder, without leaving this one. Asks the new topic's name (`/topic <name>` takes it from the command), then the folder: «📁 <folder> (as here)» — the folder this topic is bound to — or «📂 Other folder», which is the `/bind` picker with «create new folder»; in General and in an unbound topic the picker comes at once. Then the bot creates the topic (it needs the `can_manage_topics` right — `/doctor` checks it), binds it, shows the usual binding welcome there and turns the question into the answer with an «Open topic →» button. «✖️ Cancel» on the question and in the picker, and `/esc` at any step, end it. No agent starts by itself: start one in the new topic as in any bound topic. Works in the group only — in the bot's private chat it says so |
130
- | `/mcp` | List MCP servers active for this thread |
131
-
132
- #### Info / ops (work anywhere)
133
-
134
- | Command | Description |
135
- |---|---|
136
- | `/start` | Intro: work root + available agents |
137
- | `/help` | Context-aware help |
138
- | `/ls` | List the project subfolders of the folder the bot was launched from |
139
- | `/list` | List existing topics and their bindings |
140
- | `/status` | This topic's status; in General — a global view of all topics + active agents. A non-General topic also reports the account it runs on (Claude profile · email, ⛔ at its limit) and the resolved workdir; a live one the model actually running, runtime version and context use (unknown when the backend cannot report it) |
141
- | `/doctor` | Self-diagnose: admin rights, privacy mode, paths, CLIs |
142
- | `/version` | Versions: bot, claude, opencode, node, tmux; a line when an update is available |
143
- | `/update` | Self-update screen: what this instance runs (npm / git, hot), automatic updates `Automatic` · `Notify only` · `Off` (default automatic), the «what's new» switch, Check now, Update now. Controls in General, read-only elsewhere; typed forms `/update on\|notify\|off`, `/update whatsnew on\|off`, `/update check`, `/update now` — see [Updating](SETUP.md#updating) |
144
- | `/whoami` | Show userId, chatId, threadId, isAllowed, binding |
145
- | `/trace` | Output-trace recorder (`on`/`off`, `on all`/`off all`; bare = status) — see [Observability](#observability) |
146
- | `/timestamps` | Prepend the send time to prompts forwarded to the agent (`on`/`off`; bare = status) |
147
- | `/language`, `/lang` | Set the bot UI language for this chat; bare opens an endonym picker (sorted A→Z by English name), `/language auto` returns to automatic per-chat detection |
148
- | `/timezone` | Set the timezone every clock the bot touches speaks in — schedules, agent time context, time renders. `/timezone Europe/Moscow`, `/timezone +04:00`, `/timezone auto` (host zone); bare opens a region → zone picker. Instance-wide, not per chat |
149
- | `/pair` | Bind this forum supergroup to the bot (re-point auto-pairing) — works from any topic of the target group; a numeric `ALLOWED_GROUP_ID` env locks pairing |
150
-
151
- #### General-only
152
-
153
- | Command | Description |
154
- |---|---|
155
- | `/quit-all`, `/quitall` | End every active agent in every bound topic (also releases their session ids) |
156
-
157
- #### Natural language
158
-
159
- In a bound topic you can also type:
160
- - `claude fix the bug` / `opencode add tests`
161
- - A plain message after `/claude` is already running → routed as input.
162
-
163
- Voice messages are transcribed by the speech engines you set up with `/stt` (Groq,
164
- OpenAI, any OpenAI-compatible server, or the local GigaAM engine — see [Voice input](SETUP.md#voice-input)) and follow
165
- the same routing. The engines form an ordered chain: when one fails, the next one answers within the
166
- same voice note. The last one retries a temporary failure automatically (after 5 s, then 15 s) and
167
- says so in the topic — no need to re-record the voice note.
168
-
169
- `/terminal` is never started from a natural-language phrase — only the
170
- explicit command opens a shell.
171
-
172
- ### Claude Code backends (`/claude_mode`)
173
-
174
- "Claude Code" in a topic is one user-facing choice with three interchangeable
175
- backends:
176
-
177
- - **json-stream** (`/claude_mode json`) — `claude -p` in stream-json mode as
178
- an external tmux-hosted process emitting structured events. Cleaner
179
- output, and it survives bot restarts (the bot re-adopts the process and
180
- replays what was produced during the downtime). After ~55 min idle
181
- (`AGENT_IDLE_MINUTES`) the process is stopped — right after the idle
182
- compaction when `/compact_on_idle` allows one — but never while the agent's
183
- own work is pending: a background command, Monitor or sub-agent it started, a
184
- finished task's result it is about to read, a `CronCreate` job or a `/loop`
185
- wakeup it scheduled.
186
- That work keeps the process for up to 6 h after the agent's last turn, so a
187
- task nobody hears from does not keep it forever. When that work is lost
188
- anyway (a crash, a process gone after a restart, a `/model` / `/effort` /
189
- account restart, a task let go after 6 h), the topic gets one plain line and
190
- the agent is resumed with a note to check that work again — or, after a
191
- forgotten task or a second loss within 10 minutes, your next message carries
192
- the note. The **default**.
193
- - **per-turn** (`/claude_mode per-turn`) — the same host, stopped right
194
- after every answer. Nothing is lost: the conversation sleeps and your next
195
- message resumes it. Needs Claude Code 2.1.287 or newer; the switch is
196
- refused when the topic's last known version is older.
197
- - **tmux-scrape** (`/claude_mode tmux`) — the classic TUI driven by
198
- keystrokes inside tmux; output is scraped from the pane. Never stopped
199
- automatically.
200
-
201
- A stopped json-stream conversation **sleeps**: `/status` says so, and the next
202
- message, file, voice note, `/compact`, `/model` or `/effort` resumes it by
203
- itself — same session, prompt cache still warm, no "started" notice. A
204
- process is never stopped while it works (a running turn, a background task,
205
- a message not yet taken in). Only `/quit`, `/new` and leaving the folder end
206
- the conversation.
207
-
208
- `/login` works on all three backends. tmux-scrape hosts the sign-in inside its
209
- TUI; the json-stream backends (which have no TUI) run it **out-of-band** — the bot spawns
210
- `claude auth login` in a pty, posts the sign-in link into the topic, and takes
211
- your pasted code back (the code message is deleted and a 🔐 confirmation is
212
- posted). While the sign-in waits for the code, a message that is not a code
213
- is answered with a waiting hint instead of being swallowed; `/esc` cancels
214
- the pending sign-in (the link message says so).
215
-
216
- All three drive the same `claude` CLI against the same on-disk
217
- transcript, so `/claude_mode` switches a live topic seamlessly — the
218
- conversation resumes on the other backend. The pick persists per topic
219
- (▶️ Claude and `/claude` reopen it; on a sleeping topic they resume it).
220
- Billing note: the json-stream backends strip `ANTHROPIC_API_KEY` from the
221
- agent's env, so usage bills to your Claude subscription rather than an API key.
222
-
223
- ### Several accounts (`/accounts`)
224
-
225
- If you hold more than one Claude subscription (a personal Pro/Max and a
226
- corporate Team/Enterprise seat, say), the bot keeps each signed in as its own
227
- **profile** and lets you choose, per topic, which one a Claude session runs on —
228
- so one account's usage limit does not stop every topic.
229
-
230
- - The profile `default` is your existing `~/.claude` login; nothing is migrated.
231
- An added profile is a Claude config directory of its own under
232
- `DATA_DIR/claude-profiles/<name>/` (the CLI gets it through
233
- `CLAUDE_CONFIG_DIR`): it holds that account's login, while the transcripts,
234
- settings, skills, plugins and your user memory are symlinks back into
235
- `~/.claude` — every account sees the same hooks and skills, and a conversation
236
- started under one account resumes under another.
237
- - **Adding an account:** `/accounts` → «➕ Claude account» (or `/accounts add work`) — give it
238
- a short name, then open the sign-in link the bot posts **in a browser that is
239
- signed in as that other account** (an incognito window or another browser
240
- profile keeps your current login out of the way), finish the sign-in and paste
241
- the code back into the topic. The code message is deleted; the bot confirms
242
- with the account's email and plan.
243
- - **Switching:** tap a profile in `/accounts` or an account on the `/limits`
244
- board (or `/accounts use <name>`). A
245
- running session on the default (stream) backend is restarted under the new
246
- account with the same conversation — right away when idle, at the end of its
247
- turn when it is working; a sleeping topic simply resumes under it. The
248
- tmux-scrape backend is reopened the same way when idle and waits for the next
249
- start when busy. In the **General** topic the pick is the default for all
250
- topics; a topic with its own pick keeps it («↩ Follow the default» drops it).
251
- A profile that is not signed in cannot be picked. On a topic that runs OpenCode the
252
- pick is a switch to Claude on that account (see *A manual switch between Claude and
253
- OpenCode asks first* under the fallback models). While a usage-limit wait is armed a
254
- pick is the limit move: the wait ends and the work continues on the account you tapped.
255
- - **Removing:** «🗑 Remove» deletes the profile's login directory. The profile
256
- that is currently the instance default cannot be removed (pick another one in
257
- General first); topics that had picked the removed profile return to the
258
- default and the confirmation says how many. For `default` (`~/.claude`
259
- itself) «remove» means **sign out**: after a confirmation that says running
260
- sessions on it will stop working, only the stored subscription login
261
- (`claudeAiOauth` in `~/.claude/.credentials.json`; MCP tokens beside it stay)
262
- is deleted — `projects/`, settings, skills, plugins and memory are never
263
- touched — and the account is hidden from the list, the recommendation and the
264
- fallback ranking until it is signed in again (`/accounts login default`).
265
- - A child `claude` the agent starts inherits the same `CLAUDE_CONFIG_DIR`, so
266
- it runs on the topic's account too.
267
- - **Usage per account.** Under each profile `/accounts` shows how much of each
268
- subscription window is used and when it resets:
269
- the 5-hour session window, the weekly window (all models) and Fable's own
270
- weekly window (left out entirely on a plan without one). A line reads
271
- `5h 5% 🔄 in 4h · week 20% 🔄 in 5d 8h · Fable 4% 🔄 in 5d 8h` — the
272
- percentage is USED. A reset is shown as a time from
273
- now (`🔄 in 2d 1h`; the two largest units, minutes only under an hour); an
274
- unknown 5h reset shows the weekly one, and with no reset at all only the
275
- percentage is shown. `charness claude-profiles` prints
276
- the same relative form; its `--json` and the MCP tool keep ISO instants. The bot reads the figures with
277
- each profile's own login every 10 minutes, when `/accounts` or `/limits` opens and when an
278
- agent asks (the ChatGPT subscription of an OpenCode provider is read the same way, see below); a json-stream session's own rate-limit events keep them current
279
- between polls. A token the CLI has not renewed yet shows the last figures as
280
- `(stale)`; a profile whose session was refused on a window is marked
281
- `⛔ at its limit until …` and is skipped by the recommendation below.
282
- - **Another account takes over on a limit.** When a topic's account hits a
283
- usage limit the bot looks at the [fallback chain](#usage-limits-and-the-fallback-models-limits-and-limits_autoswitch);
284
- without one it ranks the other signed-in profiles by headroom on the windows
285
- the topic's model drains (fresh figures, not marked limited). The limit
286
- notice carries «🔁 Switch to <entry>» buttons, best first: a tap moves the
287
- topic there (the running session is restarted under the new account with the
288
- same conversation), drops the wait for the reset and tells the agent to
289
- continue right away. With **switching** on («🔁 Auto switch to a
290
- fallback model» in `/limits_autoswitch`, or `/limits_autoswitch switch on`; in General the default for all
291
- topics) the bot does the same by itself and posts one short line — «Account
292
- «a» hit its usage limit — this topic continues on «b». The limit resets
293
- <time>. Switch: /limits». The target is named by its account alone
294
- and gets its model only when the switch changes it («b» (sonnet)); an
295
- OpenCode rung is written without quotes on either side — the target as
296
- «OpenCode provider/model», the side that hit its limit as «Model
297
- provider/model». It is OFF by default: moving
298
- work between subscriptions is your call. The exhausted profile is marked
299
- limited until its window resets, so it is not picked again before then. The
300
- reset itself does not move the topic back: `/limits`, which the notice names,
301
- lists every account and provider with its usage and one tap moves the topic
302
- (see below). Only a wait that ends on another entry
303
- moves a topic by itself (see below). When nothing can take over the bot waits
304
- for the reset as before. A Claude session that works when a limit hits is
305
- moved at once (the turn is dead; background tasks it started are stopped and
306
- the notice says so); only a tmux Claude session with nothing to resume yet is
307
- left to the end of its turn.
308
- - **Which account for a child process.** An agent that launches a detached
309
- `claude` (a worker, a reviewer) asks the bot's `telegramBot` MCP tool
310
- `claude_profiles` — optionally with the model it will run — and gets every
311
- profile with its windows plus `recommended`: the signed-in, unlimited profile
312
- with the most headroom (the session window and the shared weekly window bound
313
- every model; Fable additionally drains its own weekly window) — never an
314
- account billed to API credits. It then
315
- starts the child with `CLAUDE_CONFIG_DIR=<configDir>` (`null` = the default
316
- profile: set nothing). The same answer from a shell, for a worker without
317
- MCP: `charness claude-profiles [--best] [--model <name>] [--json]` —
318
- `--best` prints just the directory (an empty line for the default), so
319
- `CLAUDE_CONFIG_DIR="$(charness claude-profiles --best --model fable)"`
320
- works as is; it reads the bot's cache off `DATA_DIR/state.json`.
321
-
322
- #### An account billed to API credits (Claude Console)
323
-
324
- Besides subscriptions, an account can bill the **API credits of a Claude Console
325
- organization** — useful as a fallback when every subscription is at its limit.
326
- Such an account pays per request (no 5-hour or weekly windows: its limit is the
327
- balance), so the bot never uses it without you choosing it.
328
-
329
- - **Adding one:** `/accounts` → «➕ Claude API account (Console credits)» (or
330
- `/accounts add console api`) — give it a name. The bot runs
331
- `claude auth login --console` for it: open the link, sign in to the Console
332
- organization whose credits it should spend and paste the code back. Claude
333
- Code then creates an API key in that organization's workspace and keeps it in
334
- the account's own directory (`DATA_DIR/claude-profiles/<name>/`), where only
335
- that account's sessions read it. Your Console role must allow creating a key
336
- («Claude Code» or «Developer»).
337
- - **Or paste a key:** while that sign-in waits, paste an existing API key
338
- (`sk-ant-api…`, on its own or as an `ANTHROPIC_API_KEY=…` line) instead of the
339
- code. The message is deleted at once (so is a key pasted where the name is
340
- asked, or typed on the `/accounts` command line), the key
341
- is checked with the API (a refused key is not stored), then kept in the
342
- account's directory (`0600`) and handed to its sessions only, through an
343
- `apiKeyHelper` in that account's launch settings — never in `state.json`, never
344
- in an environment variable an agent or a child process could read, never in
345
- the trace (any `sk-ant-…` text is redacted from it). `/accounts login <name>`
346
- of an API account runs the same flow again; a later Console sign-in replaces
347
- a pasted key.
348
- - The Console's keyless sign-in («Sign in with your Console account» in
349
- Claude Code's own `/login`) is **not** used: it is stored outside the account's
350
- directory, for the whole machine, and signs the subscriptions out.
351
- - Every other account stays a subscription exactly as before: the bot still
352
- removes any `ANTHROPIC_API_KEY` from the environment of every session and of
353
- every sign-in.
354
- - **Recognising one:** `/accounts` shows it as `console — you@example.com, 💳 API
355
- · pay per use` (`💳 API key · pay per use` for a pasted key), `/status` marks
356
- the account `· 💳 API`, and picking it — in a topic, or as the default in
357
- General — says that every request is billed. The bot reads the billing from
358
- `claude auth status` each time it checks the account, so a mistaken
359
- subscription login shows as a subscription.
360
- - **Never used by itself.** It has no windows, so to every headroom rule it
361
- would look inexhaustible: the `claude_profiles` recommendation (and
362
- `charness claude-profiles --best`) never names it, the within-Claude switch
363
- never offers it, and «🔄 Fill from connected» in `/limits_autoswitch` puts it
364
- LAST in the fallback list and left out of automatic switching (🚫). To use it,
365
- tap it on the `/limits` board (`Claude · console 💳`) — or include its entry
366
- («✅ Use») so a limit notice offers it, or switches to it by itself with
367
- switching on. A rebuilt list keeps it included once you did.
368
- - **When the credits run out** («Credit balance is too low») the bot does not
369
- wait for a reset — there is none — and never retries hourly. The topic gets
370
- one `💸` notice, the account is marked as out of credits (`💸` in
371
- `/limits_autoswitch`, `/accounts`, `/limits` and `/status`) and the fallback
372
- list decides as for a limit: switching on moves the topic at once, otherwise
373
- the notice carries «🔁 Switch to …» buttons. If nothing can take over now but
374
- another entry comes back at a known time (a subscription's weekly reset), the
375
- topic waits for that and continues there; otherwise the notice is all. The
376
- mark lasts until you act: pick the account again (after topping the balance
377
- up), or sign it in again — or until a request on it goes through again. A
378
- rate limit of the API (429) is still a short retry.
379
- - **What it spent.** On the default (stream) backend every turn reports the
380
- conversation's running cost; the bot counts the part each turn on the API
381
- account added (a subscription turn of the same conversation is never
382
- counted) and shows the total under the account in `/accounts` and `/limits`
383
- (`💳 $12.34 spent since 23.10 · last turn $0.21`), on `/status`, in the line a
384
- limit switch onto the account adds («billed per request … $X spent through
385
- the bot so far») and in its «credits used up» notice. It is what the bot
386
- saw, not the organization's balance — that one is on the Console's Billing
387
- page. The tmux backend reports no cost.
388
-
389
- ### Usage limits and the fallback models (`/limits` and `/limits_autoswitch`)
390
-
391
- When a topic's agent stops on a usage limit — a Claude account window, an
392
- OpenAI quota through OpenCode, a Google quota spent for the day or the plan — the
393
- bot can keep the work going somewhere else instead of waiting (a per-minute
394
- throttle, or a reset a few minutes away, is simply retried). Two screens split
395
- the job:
396
- **`/limits` is the switch board** — every signed-in Claude account and every connected
397
- OpenCode provider with its usage, one tap to move the topic there by hand — and
398
- **`/limits_autoswitch` is the setup of the automatic switching**: ONE
399
- instance-wide, ordered list of **fallback models** (where to go), top = preferred,
400
- and the two settings (auto switch, wait for the reset):
401
-
402
- - **The board.** `/limits` (see the command table) is where the notice sends you
403
- («The limit resets <time>. Switch: /limits»): a Claude account row changes the
404
- account (model and conversation kept), an OpenCode provider row opens its model page
405
- and a pick changes the model (the same session), the ✓ row changes the model of where
406
- the topic runs, and across backends with a conversation the bot asks first. Every
407
- entry point — a board row, an `/accounts` row, a model page, `/accounts use` — takes the
408
- one switch path, so a pick behaves the same wherever it is made. While a limit wait is
409
- armed a tap is the limit move: another account, another provider or model, and — on the
410
- limited Claude account itself — a model the limit does not cover (a Fable-only weekly limit
411
- does not stop `opus` or `sonnet`; the usage windows say what is exhausted) continue the work
412
- at once; a pick still inside the limit's scope (Fable again, or any model while the session or
413
- the shared weekly window is exhausted — Fable drains both too — or when the windows are unknown)
414
- only changes the model and the wait stays. A tap that switched answers with a short «✅ This topic runs on …».
415
- - **OpenCode usage.** A provider signed in through a **ChatGPT subscription** (the `openai`
416
- provider with an `oauth` record in OpenCode's `auth.json`) shows its ~5 h and ~7 d
417
- windows in the `/limits` message and in `/accounts`, read the way a Claude account's are: the stored
418
- token as it is (never refreshed by the bot; an expired one marks the figures out of date),
419
- every 10 minutes and when a screen opens, cached in `state.json`. API-key providers and
420
- other subscriptions show only how they are signed in (`/accounts`); a model that hit a limit carries `⛔ <reset>` on its model page.
421
- A failed read just leaves the provider without a usage line.
422
-
423
- - **Left out of switching.** ✅ / 🚫 on a row includes or excludes that entry from
424
- automatic switching and from the notice's switch buttons. A connected
425
- provider (say OpenAI on OpenCode) can stay usable by hand through `/model`
426
- while the bot never moves a limited topic onto it. With an EMPTY chain the
427
- bot still ranks the other signed-in Claude accounts by headroom; once the
428
- chain has entries, only its ✅ entries count — all of them left out means
429
- no switch, the wait stands.
430
- - **Entries.** A Claude account profile (`/accounts`) with an optional model
431
- (`fable`, `opus`, `sonnet`, `haiku`; «keep the topic's model» otherwise), or
432
- an OpenCode provider with a model (`openai/gpt-5`, `google/gemini-2.5-pro`,
433
- …). Only what is actually connected is offered: the signed-in profiles, and
434
- the providers the OpenCode server is authenticated for (its live catalog).
435
- - **Drafting.** «🔄 Fill from connected» (or `/limits_autoswitch rebuild`) writes a draft from what
436
- is signed in: the Claude accounts in their `/accounts` order (keeping the
437
- topic's model), then the OpenCode models topics picked recently, then every
438
- other provider's strongest model; an account or provider you had left out of
439
- switching (🚫) stays left out. Tap an entry to move it (⬆️ Up / ⬇️ Down),
440
- include or leave it out, or remove it; add with «➕ Add» (pick the account or
441
- provider, then the model). Editing works from **every** topic (it is one
442
- instance-wide list); a topic also sees ✓ on the entry it runs on now.
443
- - **On a limit** the bot marks the exhausted entry limited until its window
444
- resets (the reset the error names, else the account's session window, else
445
- an hour), skips entries that are limited, not signed in or no longer offered,
446
- and takes the first remaining one. A topic whose account or model was picked
447
- by hand (`/accounts`, `/model`) is left alone until it hits its own limit —
448
- then the chain decides, from the top. The «🔁 Auto switch»
449
- setting (auto switch to a fallback model) applies: OFF (the default) puts «🔁 Switch to …» buttons on the
450
- limit notice, ON switches by itself. No entry available → the wait
451
- (when «⏳ Wait for reset» is ON) — **for the entry that
452
- comes back first**: when every enabled entry, the topic's current one
453
- included, is limited, the armed wait is re-aimed at the entry whose limit
454
- resets EARLIEST (the notice says which model the topic will continue on and
455
- when, also with auto switch OFF). The target rides in the persisted retry
456
- record, so a restart keeps it; when the timer fires the topic is moved there
457
- first (a context bridge across backends), then the usual continue prompt
458
- follows. If the current entry resets first (or within a minute of the first),
459
- nothing changes.
460
- - **Within a backend** the conversation is kept: Claude account → account keeps
461
- the transcript (`--resume` under the other account), OpenCode provider →
462
- provider keeps the session and changes the model. **Across backends**
463
- (Claude ↔ OpenCode) the topic's agent changes: the old session is stopped
464
- and its id parked, the new one is started (or resumed, when the topic ran on
465
- that backend before) and its first prompt is a **context bridge** — why it
466
- takes over, the previous agent's last turns verbatim, and the instruction to
467
- check the project state (git status / diff / log) before carrying on and to
468
- report in one short message what the task is, what is done and what remains.
469
- A later `/claude` or `/opencode` returns to the parked conversation; `/new`
470
- and `/quit` release it as usual.
471
- - **A manual switch between Claude and OpenCode asks first.** `/claude` or `/opencode` (also the agent
472
- buttons and the natural-language «start …» phrases) on a topic that runs the other backend, and a Claude
473
- account tapped in `/accounts` or on the `/limits` board (or `/accounts use <name>`) — and an OpenCode provider's model picked on a topic that runs Claude — move the topic the same
474
- way as a limit does — parked conversation of the target resumed, the context bridge as its first prompt (text typed
475
- with the command, `/opencode do X`, rides in it). When the topic holds a conversation to hand over (an agent with at
476
- least one turn, running or asleep) the bot first asks «🔁 Switch this topic from OpenCode to Claude «work»? The
477
- conversation context will be handed over in compacted form.» with ✅ Continue / ✖️ Cancel: Continue switches and
478
- turns the question into one line saying so, Cancel changes nothing; either consumes the keyboard, and an old
479
- question only says it is out of date. A topic with nothing to hand over (no agent, or one just started and not yet
480
- spoken to) switches at once, and a Claude account tapped on a Claude topic applies at once, as before. `/model`
481
- lists only the current backend's models, so it never switches backend.
482
- - The reset of the exhausted entry does **not** move the topic back by itself —
483
- the switch notice names the reset time. The one thing that can is the wait: when
484
- every enabled entry is limited, the wait is re-aimed at the entry that resets
485
- first, and that may be the one the topic left (see above).
486
-
487
- ### Scheduler (`/schedule`) (Telegram)
488
-
489
- A topic can have scheduled prompts: at fire time the bot posts the prompt
490
- into the topic, pins the announcement (pins accumulate as run history),
491
- waits for a busy agent to go idle (up to 10 min, rather than interrupting
492
- live work) and delivers it — reusing the active session or starting one
493
- with the thread's last-used backend.
494
-
495
- - `/schedule <free text>` is a thin wrapper — the bot owns no date parsing.
496
- The agent interviews you (bare `/schedule`) or parses "every day at 9" /
497
- "tomorrow 15:00" itself, then calls the bot-injected `schedule_create` /
498
- `schedule_list` / `schedule_cancel` MCP tools (cron, one-shot, or
499
- N-times; min interval 5 min; up to 30 agent jobs per topic — reminders are
500
- counted separately, see below).
501
- - Restart-safe: timers re-arm at boot; a run missed during downtime fires
502
- one catch-up annotated with the missed time.
503
- - Leaving a folder pauses the topic's scheduled prompts; `/bind` resumes
504
- them (an expired one-shot is dropped). Reminders keep running — see
505
- below. Run history: `DATA_DIR/scheduler-runs.jsonl`.
506
-
507
- ### Reminders (`/reminders`) (Telegram)
508
-
509
- A reminder is the bot's own notification, not a job for an agent: at the due
510
- time the bot posts your text into the topic and pins it — and the pin is what
511
- notifies you through a muted topic. Nothing is started and nothing is
512
- interrupted, so reminders work in **any** topic, with or without a bound
513
- folder and with or without a running agent.
514
-
515
- `/reminders` opens a hub with «➕ Add», «📋 List (N)» and «✕ Close». Add walks
516
- four steps, all in the same message:
517
-
518
- 1. **How often** — once, every day, weekdays, weekly, monthly.
519
- 2. **Which day** — a date (today / tomorrow / a date grid), a weekday, or a
520
- day of the month. Skipped for "every day" and "weekdays".
521
- 3. **What time** — four quick presets, or «🕐 Other time» → the hour, then
522
- the minutes in 5-minute steps.
523
- 4. **The text** — type it or send a voice note, up to 1000 characters.
524
-
525
- Steps 1–3 are buttons only: the reminder text is the one thing you type.
526
- `‹ Back` changes a pick, `✕ Cancel` drops the wizard, and any command cancels
527
- it. Picking a time that has already passed says so instead of quietly moving
528
- the reminder to tomorrow, and a text over the limit is refused with nothing
529
- saved — send a shorter one and the reminder is created from that.
530
-
531
- The finished reminder shows when it fires, its text and its next run. «📋
532
- List» pages through the topic's reminders (8 per page); tapping one opens it
533
- with a «🗑 Delete» button — the card shows exactly what you are deleting, so
534
- there is no extra confirmation step. A topic holds up to 100 reminders — counted
535
- separately from the agent's scheduled prompts, so neither takes slots from the
536
- other — and they survive a restart, with one missed while the bot was down firing
537
- a single catch-up.
538
-
539
- Reminders are yours, not the agent's: the scheduling tools an agent can call
540
- do not see them, so it can neither list nor delete them.
541
-
542
- ### Raw terminal (`/terminal`) (Telegram)
543
-
544
- `/terminal` binds the topic to a real interactive `$SHELL` (in tmux) in the
545
- bound folder — a third backend alongside the agents, mutually exclusive
546
- with them. Every plain message is typed in as a command; output streams
547
- back as one rolling message per command. Raw keys reuse the TUI commands:
548
- `/c` (Ctrl-C), `/up` `/down` (history), `/tab` (completion), `/enter`.
549
- Restart-safe like the agents: the shell survives a bot restart and is
550
- re-adopted. Known v1 limitation: full-screen TUIs (vim, htop, less) render
551
- messy; normal commands, builds, and logs stream cleanly. The shell comes
552
- from the `SHELL` env (fallback `/bin/bash`).
553
-
554
- ### File intake (Telegram)
555
-
556
- Send a file to a bound topic with an active agent and the bot hands it to
557
- the agent:
558
-
559
- - Six kinds: photo, document (incl. PDF), video, video note, audio,
560
- animation. Voice is **not** intake — it stays on the transcription path.
561
- - The file is downloaded to `DATA_DIR/files/<chatId>_<threadId>/`
562
- (bot-owned, never inside your project folder) and announced to the agent
563
- as `[Telegram file] … saved to: <path>` plus your caption.
564
- - A media album (several files sent as one message) is batched into ONE
565
- combined `[Telegram album]` prompt instead of N separate ones.
566
- - Limits and cleanup: 20 MB per file (Telegram Bot API cap); forwarding a
567
- bare `/clear` purges the topic's intake dir (the agent's context is gone,
568
- so the files are useless); files older than 30 days are swept daily.
569
-
570
- ### Observability
571
-
572
- - **Output trace** (Telegram) — ON by default for every thread: each incoming update,
573
- adapter emit, and outgoing Bot API call (with outcome, incl. 429 details)
574
- is recorded into hourly buckets `DATA_DIR/output-trace-*.jsonl` (pruned
575
- after 6h). `/trace off all` turns it off durably; bare `/trace` reports
576
- status. This is the source of truth when debugging "a message never
577
- reached the topic".
578
- - **Console tee** — the bot's stdout/stderr is mirrored to
579
- `DATA_DIR/bot-console-*.log` (same hourly buckets, same 6h prune), so
580
- boot logs stay readable post-incident without the operator's terminal.
581
- - **`/timestamps on`** (Telegram) — prepends each forwarded prompt's send time
582
- (local-offset ISO) so a days-long session knows what "yesterday" means.
583
- Agent-facing only, default off, persisted per topic.
584
-
585
- ### Timezone (Telegram)
586
-
587
- The bot runs on the host clock, which on a typical server is UTC. `/timezone`
588
- declares yours ONCE, instance-wide, and every clock the bot touches follows:
589
-
590
- - **Schedules** fire at your wall-clock time. Changing the zone PRESERVES the
591
- wall clock — "9am" stays 9am rather than drifting by the offset — and every
592
- job's next run is recomputed on the spot (an expired one-shot is dropped).
593
- - **The agent** is told the current time and zone with each `/schedule` request,
594
- and carries the zone in its thread-context preamble, so "remind me tomorrow at
595
- 9" resolves against your clock instead of the model's assumption.
596
- - **Time renders** — "missed at HH:MM", schedule descriptions, `/timestamps`
597
- injection and the `schedule_list` next-run stamp all read in your zone.
598
-
599
- ```
600
- /timezone Europe/Moscow # IANA name
601
- /timezone +04:00 # fixed offset (accepted; does not follow DST)
602
- /timezone # region → zone picker
603
- /timezone auto # back to the host zone
604
- ```
605
-
606
- The current zone and its wall clock also show in `/status`. Nothing is stored
607
- until you set one, so an install that never runs `/timezone` behaves exactly as
608
- it did before.
609
-
610
- ### Bot-injected agent tools
611
-
612
- The bot injects its own `telegramBot` MCP server into every bot-started session
613
- — for Claude via a generated `--mcp-config`, for OpenCode via runtime
614
- registration. It is loopback-only (`127.0.0.1`, on an OS-chosen free port unless
615
- `SCHEDULER_MCP_PORT` explicitly pins one) and authenticated with per-session
616
- HMAC bearer tokens scoped to the thread / directory. Every generated registration
617
- also carries a unique client identity, so cancellation affects only the originating
618
- agent even when two clients use the same JSON-RPC request id. It exposes:
619
-
620
- - `schedule_create` / `schedule_list` / `schedule_cancel` — the agent-side
621
- scheduling API behind `/schedule`;
622
- - `compact_conversation` — lets the agent compact its OWN session when you ask it
623
- to in plain words ("compact our conversation"). It arms the compaction and
624
- returns immediately; the bot runs the real compaction the moment the current
625
- turn finishes (never mid-turn);
626
- - `send_file_to_user` — lets the agent push files from the bound folder into the
627
- topic. A single MP4 uses Bot API `sendVideo`; eligible all-video and mixed
628
- photo/video albums use `sendMediaGroup`, with each MP4 represented by an
629
- `InputMediaVideo` entry. `as_file:true` explicitly forces documents. A file is
630
- opened so that a path swapped after validation cannot reach outside the bound
631
- folder: on Linux component by component beneath a pinned folder descriptor
632
- (`/proc/self/fd`); on Windows and macOS by its canonical path, held to the
633
- validated file's device/inode identity and resolved again inside the folder
634
- after the open. Existing
635
- path, count, caption, and size caps apply; canonical upload basenames have
636
- control characters and quoted-string metacharacters replaced before they enter multipart headers. Cancelling
637
- the MCP call removes queued work. While any upload stream is still being
638
- consumed, cancellation destroys the streams and aborts Telegraf, then returns
639
- `AbortError` after terminal cleanup. Once every request-body stream has ended,
640
- cancellation is too late to abort safely: the operation awaits Telegram's
641
- response, and any returned message IDs are durably recorded before success.
642
- Directory-scoped
643
- calls re-check the topic binding both before opening files and inside the
644
- per-topic delivery queue immediately before dispatch, so a queued call cannot
645
- continue after the topic is rebound elsewhere. Delivery and its durable
646
- `/clear_messages` tracking update share one per-topic queue transaction; the
647
- complete response-ID batch is on disk before success is reported. If Telegram
648
- has accepted a delivery but local tracking/cleanup then fails, the tool returns
649
- success with a warning and never retries. If the connection fails after an
650
- upload starts and Telegram's response never arrives, the tool returns a
651
- machine-readable, non-error `deliveryUnknown` outcome with `retryable:false`,
652
- preventing accidental duplicates.
653
- - `send_messages_to_user` — delivers a batch of DISCRETE messages into the topic,
654
- each item posted as its OWN Telegram message in order (never merged) — for a
655
- per-item digest and similar. Each item is either a plain text string OR an
656
- object `{text?, path?, as_file?}` that may carry ONE attachment: a `path` item
657
- reuses the SAME secure `send_file_to_user` pipeline (path relative to the bound
658
- folder, supported photos inline, `.gif` as animation, `.mp4` as video, everything else a document,
659
- `as_file` forces a document), with `text` used as the media caption (trimmed to
660
- 1024 chars — send long prose as its own text-only item). Items are validated up
661
- front (an empty object is rejected and nothing is sent); a total send failure
662
- returns an error, a partial one stays successful but reports how many landed,
663
- and an ambiguous attachment delivery surfaces as the same non-retryable
664
- `deliveryUnknown` outcome as `send_file_to_user`. Capped at 50 items per call.
665
-
666
- This server is bot-owned plumbing; if its port fails to bind, the bot still
667
- boots — only these agent-facing tools go inert. (Your own MCP servers still work
668
- independently: Claude auto-loads them from `~/.claude/settings.json` and a
669
- project's `.mcp.json`; inspect what's active in a thread with `/mcp`.)
670
-
671
- Third-party OpenCode MCP servers, including a separate `telegram-mcp`, belong to
672
- the OpenCode configuration of the Linux user that runs the bot. They are not
673
- injected by Charness; run `opencode mcp list` as that user to verify them.
674
-
675
- ### Restart behaviour
676
-
677
- Bot restarts are designed to be invisible: agents run in external
678
- processes (`tmux`, `opencode serve`), so the bot re-adopts them instead
679
- of killing them. In hot mode the long-lived supervisor starts the initial
680
- `opencode serve` before `nodemon`, outside the replaceable worker's process
681
- tree. If a worker later starts a replacement server after a crash, credential
682
- reload, or late install, a one-shot host reparents that generation outside the
683
- same tree before startup completes. An endpoint-bound process identity file
684
- under `DATA_DIR` records a bot-started generation as `starting` before its host
685
- releases it, then promotes it to `ready` after health succeeds. A successor can
686
- therefore stop a pre-bind startup by its process-group identity, while `ready`
687
- or adopted ownership is revalidated against the exact hostname and port before
688
- a signal is sent, without trusting a reused PID or assuming an adopted listener
689
- owns its process group.
690
- A source rebuild therefore does not terminate an in-flight OpenCode turn.
691
- When `opencode serve` itself dies, the bot brings it back the same way a session
692
- start does — installing the binary first if it is missing, retrying a failed
693
- start for about a minute — and restores the sessions silently: only a topic whose
694
- reply was in progress is told it was lost, and a message sent meanwhile is held
695
- and delivered once the server is back. Only if every attempt fails does a topic
696
- get one short line saying the next message will try again (the error itself goes
697
- to the bot log).
698
- A turn's failure is posted once, although OpenCode reports most failures twice
699
- (the session error and the failed message); a provider plugin's `[Debug Info]`
700
- block (opencode-antigravity-auth appends one with your Google project id) is kept
701
- out of the topic and stays in the bot log. Gemini's "Requests ending with a model
702
- turn are not supported" — OpenCode v1's agent loop sends a history that ends on
703
- the model's own turn, fixed upstream only in v2 — gets one short line and the
704
- bot's "continue" at once, which is a user turn; the same refusal right after it
705
- ends with the usual give-up line.
706
- The supervisor reads the same checkout `.env` as the worker before the initial
707
- start. Hot mode supports Linux and macOS; it refuses to start on Windows because
708
- nodemon cannot gracefully drain its worker tree there.
709
- On boot the bot:
710
-
711
- 1. Loads `state.json` (archives to `state.json.corrupted-<ts>` if parse
712
- fails, then starts fresh and notifies in General) and classifies the
713
- boot as a hot reload vs cold start from the persisted heartbeat gap.
714
- 2. Re-adopts live sessions per backend:
715
- - **Claude tmux-scrape** (`claude-…`) and **terminal** (`term-…`)
716
- sessions still in `state.json` are re-attached; orphan bot-owned
717
- tmux sessions are killed. The current pane seeds the baseline, so
718
- nothing is re-flooded into the topic.
719
- - **Claude json-stream** (`cjson-…`) runs as an external process
720
- (stdin held on a FIFO, stdout appended to a file), so the restart
721
- never kills it: the bot adopts it and replays the downtime tail
722
- from the persisted offset — an in-flight turn is delivered
723
- end-to-end, and a pending interactive question is restored from its
724
- on-disk sidecar. A topic whose process is gone (stopped at the idle
725
- mark, or after its turn in per-turn mode) is NOT restarted at boot:
726
- it sleeps, and your next message resumes it. An adopted process
727
- started under an earlier bot build keeps that build's bot tools, so it
728
- is stopped once idle and resumed with the current ones.
729
- - **OpenCode** threads re-resume their stored session ids over the
730
- shared SSE stream. `opencode serve` itself is reconciled: a dead
731
- server is auto-restarted and the sessions restored (the in-flight
732
- reply is lost); a live server running an outdated binary is killed
733
- and respawned on the current one.
734
- 3. Re-arms pending API-error retries and scheduler timers; a run missed
735
- during the downtime fires one catch-up annotated with the missed time.
736
- A usage-limit wait that the previous process never recognised is also
737
- picked up: for a Claude json-stream topic the bot re-reads the tail of
738
- the session's output log and arms the resume itself (only for a limit
739
- error younger than 12 hours, only when nothing is armed already, and
740
- never the same one twice — a resume you skipped stays skipped).
741
- 4. Stays silent on a quiet hot reload; a bounded recap of missed output
742
- is posted only when the agent kept working while the bot was down.
743
- 5. Schedules `setMyCommands` so the Telegram client picks up the menu.
744
-
745
- Explicit `/quit`, `/quit-all`, and leaving a folder (the `/bind` «leave
746
- current dir» button) release the persisted session ids — those sessions
747
- are not auto-reattached on the next boot (they stay reachable via
748
- `/sessions`).
749
-
750
- Closed-but-not-deleted topics keep their binding; only `400: message
751
- thread not found` from a send triggers binding cleanup. Closed topics
752
- are detected by `TOPIC_CLOSED` errors and surface a friendly message
753
- asking the user to reopen.
754
-
755
- In the Docker container (see [Run with Docker](SETUP.md#run-with-docker)) the same holds for a bot restart
756
- inside the container; a restart of the container itself ends every agent process, and each conversation
757
- resumes with its next message.
758
-
759
- If you run the bot under `systemd`/`systemd-run`, set `KillMode=process`.
760
- The default `control-group` mode kills tmux and `opencode serve` children,
761
- which defeats restart/reattach. Process restarts intentionally leave agent
762
- sessions alive; use `/quit` or `/quit-all` when you want to terminate them.
763
-
764
- ### Troubleshooting
765
-
766
- #### Bot doesn't see text (Telegram)
767
-
768
- You disabled privacy mode in @BotFather **after** adding the bot. The
769
- flag is cached on join. Fix:
770
-
771
- 1. Remove the bot from the group.
772
- 2. (Re-confirm in @BotFather → `/setprivacy` → this bot → `Disable`.)
773
- 3. Add the bot back.
774
-
775
- Verify with `/doctor` — the privacy line should report it as disabled.
776
-
777
- #### `/bind` says "folder not found" (Telegram)
778
-
779
- The folder you launched from doesn't contain a subfolder with that name (or
780
- it's a symlink pointing outside it). Run `/ls` to see what's available, or fix
781
- the host path. Path-traversal attempts (`../`, absolute paths,
782
- NUL bytes, NFC-vs-NFD mismatches) are rejected by design — see
783
- `validateSubdir` in `src/validation.ts`.
784
-
785
- #### "Missing right" / `/clear_messages` does nothing (Telegram)
786
-
787
- The bot needs admin rights `can_delete_messages` and `can_pin_messages`
788
- in addition to `can_manage_topics`. Open group settings → admins →
789
- the bot → tick the missing boxes. `/doctor` shows the exact gap.
790
-
791
- #### Two instances on the same machine fight each other
792
-
793
- You forgot to set distinct `DATA_DIR` and `OPENCODE_URL`. The bot logs
794
- `[startup] DATA_DIR=<path>` on boot — if both instances log the same
795
- path, give each its own `DATA_DIR` and `OPENCODE_URL` port and restart both.
796
-
797
- #### Claude opens a session picker instead of resuming
798
-
799
- The 1.x adapter invoked `claude --resume` with no UUID and relied on the
800
- interactive picker, which doesn't survive a non-interactive pty. 2.0
801
- generates a UUID with `crypto.randomUUID()`, passes it via
802
- `--session-id`, and resumes via `--resume <uuid>`. If you see a picker
803
- anyway, your local claude CLI is older than 2.1.x — upgrade with
804
- `npm i -g @anthropic-ai/claude-code` (the bot also auto-installs on
805
- first use, see `src/installManager.ts`).
806
-
807
- #### OpenCode "address already in use"
808
-
809
- Two instances on the same host with the same `OPENCODE_URL`. Pick a
810
- different port for the second instance (e.g. `4097`).
811
-
812
- ### FAQ
59
+ ## FAQ
813
60
 
814
61
  **How do I run Claude Code from my phone?**
815
62
  Install Charness on your own machine or VPS, create a Telegram bot, and bind a forum topic (or the bot's DM) to a project folder. Start a Claude Code session in that topic and you drive it entirely from Telegram: type prompts, send voice notes, or drop in files, and the agent's output streams back into the same topic. Your phone never runs the agent; it stays on your machine.
@@ -817,25 +64,80 @@ Install Charness on your own machine or VPS, create a Telegram bot, and bind a f
817
64
  **Can I control OpenCode without opening any ports?**
818
65
  Yes. The bot only makes outbound connections to Telegram, so nothing listens for inbound traffic and you never expose a port. OpenCode runs as a local HTTP server that the bot talks to on loopback, and you reach it from anywhere through Telegram.
819
66
 
820
- **How do I run a coding agent on a VPS and drive it from Telegram?**
67
+ **How do I run a coding agent on a VPS or VDS and drive it from Telegram?**
821
68
  Set up your VPS once with git, your repos, the agent CLIs, and `charness`, then launch the bot from your projects folder. You get an always-on dev box you drive from any device: phone, tablet, or voice. Because the agent lives on the server, it keeps working even when your laptop sleeps, and the same sessions are still reachable over plain SSH.
822
69
 
823
70
  **Does my session survive a restart?**
824
71
  Yes, both a bot restart and an agent-server restart. State is persisted to disk; on restart the bot re-attaches to the running Claude session in tmux or re-connects to OpenCode, and an in-flight turn is delivered once it is back. A prompt sent during startup is buffered and replayed, never dropped.
825
72
 
826
73
  **Does it run both Claude Code and OpenCode?**
827
- Yes. Each topic picks its backend independently: OpenCode over its native HTTP+SSE server, or Claude Code (tmux or json-stream). One topic is one project with its own isolated session, like terminal tabs, and two topics can point at the same folder for parallel work.
74
+ Yes. Each topic picks its backend independently: OpenCode over its native HTTP+SSE server, or Claude Code (json-stream, or the deprecated tmux). One topic is one project with its own isolated session, like terminal tabs, and two topics can point at the same folder for parallel work.
828
75
 
829
76
  **Can I talk to the agent by voice?**
830
77
  Yes. Send a voice message to a topic and it is transcribed (by Groq, OpenAI or your own Whisper-compatible server) and forwarded to the agent as a normal prompt, so you can vibe code hands-free from your phone.
831
78
 
79
+ **Can agents work in the background or on a schedule?**
80
+ Yes. `/schedule` arms cron, one-shot or N-times jobs per topic, so a scheduled agent can do recurring tasks — a nightly review, a morning digest, a weekly report — while you are away, and the agent can schedule itself. A scheduled check watches a service and wakes the agent only when it fails. Jobs survive a bot restart, and a run missed while the bot was down fires once as a catch-up.
81
+
82
+ ## Reference
83
+
84
+ ### Features
85
+
86
+ The same core serves every surface; each feature links to the commands that drive it ([all commands](docs/commands.md)).
87
+
88
+ #### Any surface
89
+
90
+ | Feature | What it does | Commands and docs |
91
+ |---|---|---|
92
+ | Agent backends | OpenCode (native server API over HTTP+SSE) and Claude Code (three backends — json-stream, per-turn and the deprecated tmux) | [`/claude`, `/opencode`](docs/commands.md#claude-opencode-oc), [`/claude_mode`](docs/commands.md#claude_mode), [`/model`](docs/commands.md#model), [`/effort`](docs/commands.md#effort), [backends](docs/backends.md) |
93
+ | Sleeping agents | A Claude json-stream topic's process is stopped when idle (or after every turn in per-turn mode) and the next message resumes the same conversation by itself, prompt cache still warm | [`/claude_mode`](docs/commands.md#claude_mode), [`/status`](docs/commands.md#status), [`/quit`](docs/commands.md#quit-q) |
94
+ | Several Claude accounts | Keep more than one Claude subscription signed in side by side and choose per topic which one a session runs on; every account's usage windows are shown next to it, and `/limits` is a one-tap switch board over all of them and over the OpenCode providers | [`/accounts`](docs/commands.md#accounts), [`/limits`](docs/commands.md#limits) |
95
+ | Waits out usage limits | When the agent hits a usage / session limit the bot waits for the reset and tells the agent to continue by itself; with a fallback list of other Claude accounts, OpenCode providers and models it moves the topic to the next entry with room and continues at once — or offers you the switch with a tap | [`/limits`](docs/commands.md#limits), [`/limits_autoswitch`](docs/commands.md#limits_autoswitch) |
96
+ | Updates itself | Checks for a new version hourly, installs it when no agent is working, restarts in place (agents keep running) and says in General what changed; npm installs and git checkouts, hot mode and Docker included | [`/update`](docs/commands.md#update), [Updating](SETUP.md#updating) |
97
+ | Restart-safe | Agents run as external processes (tmux, `opencode serve`) that the bot re-adopts after a restart instead of killing them; a prompt sent while a session boots is buffered and replayed | [Restart behaviour](docs/backends.md#restart-behaviour) |
98
+ | Shared todo lists | Every agent publishes a short stand-up list of what it is working on (in work, the queue, done), and the agents of one repository see each other's lists with every prompt, so two of them never fix the same thing unknowingly | [`/todo`](docs/commands.md#todo) |
99
+ | Compaction | Compact an agent's context by hand, after ~55 min idle, or by the agent's own request | [`/compact`](docs/commands.md#compact), [`/compact_on_idle`](docs/commands.md#compact_on_idle), [`/compact_summary`](docs/commands.md#compact_summary) |
100
+ | Tools and skills for agents | Every session the bot starts gets the bot's own MCP tools (schedule, todo, send files) and the manager / delegate skills | [Bot-injected agent tools](docs/backends.md#bot-injected-agent-tools), [`/bot_skills`](docs/commands.md#bot_skills) |
101
+ | Runners for heavy commands | Agents send a test suite, a build or an emulator run to another machine you reach by ssh: `charness remote-run` syncs the project, runs the command there and brings back the output, the exit code and the result files you declare; `remote-run runners` shows every machine's CPU, memory, disk, load and what it can do (Android, iOS, Docker, Playwright) | [docs/remote-run.md](docs/remote-run.md), [SETUP.md](SETUP.md#runners-for-heavy-commands-remote-run) |
102
+ | Jira | An issue is a conversation: assign a ticket to the agent's user and it works it; a cap on how many issues work at once | [Jira setup](SETUP.md#jira-setup), [`/task_agents`](docs/commands.md#task_agents) |
103
+
104
+ #### Telegram
105
+
106
+ Charness turns Telegram into a terminal for running agentic CLIs. Open as many tabs as you need — even two on the same project for parallel work — and drive the agents from your phone or tablet, voice messages included.
107
+
108
+ Think **OpenClaw for vibe coding**, driven entirely from Telegram: no complex setup, no extra dashboards — direct access to your own **OpenCode** / **Claude Code** running on your machine or server.
109
+
110
+ | Feature | What it does | Commands and docs |
111
+ |---|---|---|
112
+ | Simple setup & management | Install the CLI, put your bot token in one `.env`, bind a topic to a folder, and run; everything after is managed from Telegram — no dashboards | [Quick start](#quick-start), [SETUP.md](SETUP.md), [`/doctor`](docs/commands.md#doctor) |
113
+ | Multi-thread / topic | One topic per directory with your agent; two topics can share one folder for parallel work — run almost unlimited agents in parallel | [`/bind`](docs/commands.md#bind), [`/topic`](docs/commands.md#topic), [`/list`](docs/commands.md#list) |
114
+ | Two chat surfaces | Forum-group topics and the owner's bot DM, both served at once by default, with tabs on either | [Two surfaces](docs/backends.md#two-surfaces-group-topics-bot-dm-or-both) |
115
+ | Raw terminal | A topic bound to a real `$SHELL` in the project folder | [`/terminal`](docs/commands.md#terminal) |
116
+ | Notifications | When the agent asks a question, the bot pins that message so even a muted topic notifies you: the pin pierces mute, giving exactly one notification per question (unpinned once it ends). A question is never deleted: it stays in the topic as the agent asked it, and in place of its buttons it shows the answer you tapped — or, when you walked away from it, a note that it was left unanswered | — |
117
+ | Scheduled & self-driving runs | `/schedule` arms cron / one-shot / N-times jobs per topic, and the agent can schedule *itself* via injected MCP tools: nightly reviews, recurring reports, a "finish this tomorrow at 9" hand-off, checks that wake the agent on failure. Each job fires as a fresh session with your prompt; restart-safe, with one catch-up for a run missed while the bot was down | [`/schedule`](docs/commands.md#schedule) |
118
+ | Reminders | A plain reminder set with inline buttons (how often, which day, what time, then the text by typing or voice); at the due time the bot posts and pins it itself, so even a muted topic notifies you. No agent, no bound folder, no date parsing — works in any topic, General included | [`/reminders`](docs/commands.md#reminders) |
119
+ | Inbound files | Photos / documents / video / audio sent to a topic are saved and announced to the agent; albums arrive as one prompt | [File intake](docs/commands.md#file-intake) |
120
+ | Outbound files | The agent sends files back to you — a generated chart, a screenshot, a log, a rendered PDF — single files or albums, straight into the topic | [Bot-injected agent tools](docs/backends.md#bot-injected-agent-tools) |
121
+ | Voice input | Voice notes are transcribed by an ordered chain of engines — Groq, OpenAI or your own OpenAI-compatible server — set up in `/stt` (or `stt.json`); a local GigaAM engine is optional; if one fails, the next takes over | [`/stt`](docs/commands.md#stt), [Voice input](SETUP.md#voice-input) |
122
+ | Display verbosity and view | Per topic: detail `minimal\|short\|full`, and what the topic shows — `stream` (default), `stream_answers` or `answers` (only the agent's pinned answers, no stream) | [`/verbosity`](docs/commands.md#verbosity), [`/thinking`](docs/commands.md#thinking), [`/tool_results`](docs/commands.md#tool_results), [`/subagent`](docs/commands.md#subagent) |
123
+ | Time-aware prompts | `/timestamps on` prepends each forwarded prompt's send time (local-offset ISO), so a days-long session knows what "yesterday" or "2 days ago" means; agent-facing only, per topic | [`/timestamps`](docs/commands.md#timestamps) |
124
+ | Reply context | Reply to a message in a topic and the quoted text is folded into the prompt the agent receives, so it sees what you point at without re-pasting; works for text, voice and files, both agents | [Reply context](docs/commands.md#reply-context) |
125
+ | Your timezone | `/timezone Europe/Moscow` once, and schedules fire at your wall-clock time while the agent is told what time it actually is; no server-clock guessing | [`/timezone`](docs/commands.md#timezone) |
126
+ | Observability | An always-on output trace and a console log, the source of truth for "a message never reached the topic" | [`/trace`](docs/commands.md#trace), [`/doctor`](docs/commands.md#doctor), [Troubleshooting](docs/troubleshooting.md) |
127
+ | Languages | The bot speaks 12 languages, detected per chat and set with `/language` | [`/language`](docs/commands.md#language-lang) |
128
+
832
129
  ### Documentation
833
130
 
131
+ - **[docs/commands.md](docs/commands.md)** — every command, one section each, with the features behind it: accounts, usage limits and fallback, scheduler, reminders, todo lists, timezone, skills, file intake.
132
+ - **[docs/backends.md](docs/backends.md)** — Claude Code and OpenCode backends, restart behaviour, the tools the bot injects into every agent session, and the two Telegram surfaces (group topics, the bot's DM).
133
+ - **[docs/remote-run.md](docs/remote-run.md)** — running heavy commands on another machine: setting up a runner by hand, `runners.json`, `.remote-run`, every `charness remote-run` command, exit codes, what is never sent.
134
+ - **[docs/troubleshooting.md](docs/troubleshooting.md)** — the bot doesn't see text, `/bind` can't find a folder, missing rights, two instances on one machine.
834
135
  - **[SETUP.md](SETUP.md)** — installing and configuring: Telegram, Jira, environment, updating; Docker as an alternative to npm.
835
136
  - **[DEVELOPMENT.md](DEVELOPMENT.md)** — architecture and local development (build, test, hot-reload, Docker dev loop).
836
137
  - **[MIGRATION_1_TO_2.md](MIGRATION_1_TO_2.md)** — upgrading a 1.x install to 2.0.
138
+ - **[MIGRATION_RENAME_TO_CHARNESS.md](MIGRATION_RENAME_TO_CHARNESS.md)** — upgrading an install that still uses the TelegramCode names.
837
139
 
838
- ### The story behind it
140
+ ## The story behind it
839
141
 
840
142
  I started running Claude Code on my own machine, and I kept wanting to check on it after I stepped away from the desk. Back then there was no way to reach it from a phone, so I put together a small bot that launched the agent and let me talk to it over Telegram. It ran on my own computer, which had to stay on the whole time.
841
143
 
@@ -845,6 +147,6 @@ The project was called TelegramCode before it became Charness: its npm package w
845
147
 
846
148
  Full write-up: [Vibe-coding by voice, from my phone](https://dev.to/olosegres/vibe-coding-by-voice-from-my-phone-claude-code-and-opencode-over-telegram-j75).
847
149
 
848
- ### License
150
+ ## License
849
151
 
850
152
  MIT