charness 3.6.1 → 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.
- package/CHANGELOG.md +267 -0
- package/DEVELOPMENT.md +31 -5
- package/MIGRATION_RENAME_TO_CHARNESS.md +3 -2
- package/README.md +84 -782
- package/SETUP.md +37 -10
- package/agent-plugin/.claude-plugin/plugin.json +7 -0
- package/agent-plugin/README.md +35 -0
- package/agent-plugin/skills/charness-delegate-claude/SKILL.md +85 -0
- package/agent-plugin/skills/charness-delegate-claude/summarize-events.py +90 -0
- package/agent-plugin/skills/charness-delegate-opencode/SKILL.md +68 -0
- package/agent-plugin/skills/charness-manager/SKILL.md +152 -0
- package/dist/adapters/adapterNames.d.ts +6 -0
- package/dist/adapters/adapterNames.d.ts.map +1 -1
- package/dist/adapters/adapterNames.js +8 -2
- package/dist/adapters/adapterNames.js.map +1 -1
- package/dist/adapters/claudeCliAdapter.d.ts +8 -0
- package/dist/adapters/claudeCliAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeCliAdapter.js +42 -5
- package/dist/adapters/claudeCliAdapter.js.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.d.ts +10 -9
- package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
- package/dist/adapters/claudeJsonStreamAdapter.js +53 -55
- package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
- package/dist/adapters/createAdapter.d.ts +8 -0
- package/dist/adapters/createAdapter.d.ts.map +1 -1
- package/dist/adapters/createAdapter.js +14 -2
- package/dist/adapters/createAdapter.js.map +1 -1
- package/dist/adapters/openCodeAdapter.d.ts +43 -6
- package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
- package/dist/adapters/openCodeAdapter.js +95 -6
- package/dist/adapters/openCodeAdapter.js.map +1 -1
- package/dist/agentLogin/agentLogin.d.ts +1 -1
- package/dist/agentLogin/agentLogin.d.ts.map +1 -1
- package/dist/apiRetryKick.d.ts +4 -3
- package/dist/apiRetryKick.d.ts.map +1 -1
- package/dist/apiRetryKick.js +16 -9
- package/dist/apiRetryKick.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +1313 -381
- package/dist/bot.js.map +1 -1
- package/dist/cli/claudeProfiles.d.ts +11 -6
- package/dist/cli/claudeProfiles.d.ts.map +1 -1
- package/dist/cli/claudeProfiles.js +50 -22
- package/dist/cli/claudeProfiles.js.map +1 -1
- package/dist/cli/hot.d.ts +5 -0
- package/dist/cli/hot.d.ts.map +1 -1
- package/dist/cli/hot.js +71 -25
- package/dist/cli/hot.js.map +1 -1
- package/dist/cli/tscWatchGuard.d.ts +95 -0
- package/dist/cli/tscWatchGuard.d.ts.map +1 -0
- package/dist/cli/tscWatchGuard.js +295 -0
- package/dist/cli/tscWatchGuard.js.map +1 -0
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/connectors/jira/answerSink.d.ts +10 -0
- package/dist/connectors/jira/answerSink.d.ts.map +1 -1
- package/dist/connectors/jira/answerSink.js +13 -0
- package/dist/connectors/jira/answerSink.js.map +1 -1
- package/dist/connectors/jira/config.d.ts +2 -0
- package/dist/connectors/jira/config.d.ts.map +1 -1
- package/dist/connectors/jira/config.js +9 -1
- package/dist/connectors/jira/config.js.map +1 -1
- package/dist/connectors/jira/connector.d.ts +11 -1
- package/dist/connectors/jira/connector.d.ts.map +1 -1
- package/dist/connectors/jira/connector.js +7 -0
- package/dist/connectors/jira/connector.js.map +1 -1
- package/dist/connectors/jira/inbound.d.ts +27 -4
- package/dist/connectors/jira/inbound.d.ts.map +1 -1
- package/dist/connectors/jira/inbound.js +69 -8
- package/dist/connectors/jira/inbound.js.map +1 -1
- package/dist/connectors/jira/issueBlocks.d.ts +5 -0
- package/dist/connectors/jira/issueBlocks.d.ts.map +1 -1
- package/dist/connectors/jira/issueBlocks.js +11 -0
- package/dist/connectors/jira/issueBlocks.js.map +1 -1
- package/dist/connectors/telegram/bindPicker.d.ts +5 -0
- package/dist/connectors/telegram/bindPicker.d.ts.map +1 -1
- package/dist/connectors/telegram/bindPicker.js +18 -1
- package/dist/connectors/telegram/bindPicker.js.map +1 -1
- package/dist/connectors/telegram/bindPickerClaims.d.ts +46 -0
- package/dist/connectors/telegram/bindPickerClaims.d.ts.map +1 -0
- package/dist/connectors/telegram/bindPickerClaims.js +83 -0
- package/dist/connectors/telegram/bindPickerClaims.js.map +1 -0
- package/dist/connectors/telegram/bindSweep.d.ts +32 -0
- package/dist/connectors/telegram/bindSweep.d.ts.map +1 -0
- package/dist/connectors/telegram/bindSweep.js +47 -0
- package/dist/connectors/telegram/bindSweep.js.map +1 -0
- package/dist/connectors/telegram/bindingWelcome.d.ts +23 -0
- package/dist/connectors/telegram/bindingWelcome.d.ts.map +1 -0
- package/dist/connectors/telegram/bindingWelcome.js +26 -0
- package/dist/connectors/telegram/bindingWelcome.js.map +1 -0
- package/dist/connectors/telegram/botApiConnection.d.ts +74 -0
- package/dist/connectors/telegram/botApiConnection.d.ts.map +1 -0
- package/dist/connectors/telegram/botApiConnection.js +201 -0
- package/dist/connectors/telegram/botApiConnection.js.map +1 -0
- package/dist/connectors/telegram/commands/botCore.d.ts +8 -2
- package/dist/connectors/telegram/commands/botCore.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/botSkills.d.ts +21 -0
- package/dist/connectors/telegram/commands/botSkills.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/botSkills.js +133 -0
- package/dist/connectors/telegram/commands/botSkills.js.map +1 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts +5 -0
- package/dist/connectors/telegram/commands/callbackMessage.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/callbackMessage.js +9 -0
- package/dist/connectors/telegram/commands/callbackMessage.js.map +1 -1
- package/dist/connectors/telegram/commands/compaction.d.ts +65 -3
- package/dist/connectors/telegram/commands/compaction.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/compaction.js +512 -94
- package/dist/connectors/telegram/commands/compaction.js.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts +1 -1
- package/dist/connectors/telegram/commands/displayModes.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/displayModes.js +6 -18
- package/dist/connectors/telegram/commands/displayModes.js.map +1 -1
- package/dist/connectors/telegram/commands/fallbackChain.js +5 -5
- package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
- package/dist/connectors/telegram/commands/reminders.d.ts +1 -1
- package/dist/connectors/telegram/commands/reminders.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/reminders.js +4 -15
- package/dist/connectors/telegram/commands/reminders.js.map +1 -1
- package/dist/connectors/telegram/commands/selfUpdate.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/selfUpdate.js +1 -11
- package/dist/connectors/telegram/commands/selfUpdate.js.map +1 -1
- package/dist/connectors/telegram/commands/taskAgents.d.ts +22 -0
- package/dist/connectors/telegram/commands/taskAgents.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/taskAgents.js +104 -0
- package/dist/connectors/telegram/commands/taskAgents.js.map +1 -0
- package/dist/connectors/telegram/commands/todo.d.ts +82 -0
- package/dist/connectors/telegram/commands/todo.d.ts.map +1 -0
- package/dist/connectors/telegram/commands/todo.js +384 -0
- package/dist/connectors/telegram/commands/todo.js.map +1 -0
- package/dist/connectors/telegram/commands/topicCommand.d.ts +6 -2
- package/dist/connectors/telegram/commands/topicCommand.d.ts.map +1 -1
- package/dist/connectors/telegram/commands/topicCommand.js +6 -9
- package/dist/connectors/telegram/commands/topicCommand.js.map +1 -1
- package/dist/connectors/telegram/openBindMessages.d.ts +45 -0
- package/dist/connectors/telegram/openBindMessages.d.ts.map +1 -0
- package/dist/connectors/telegram/openBindMessages.js +67 -0
- package/dist/connectors/telegram/openBindMessages.js.map +1 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts +49 -0
- package/dist/connectors/telegram/outputFlushPlan.d.ts.map +1 -1
- package/dist/connectors/telegram/outputFlushPlan.js +46 -0
- package/dist/connectors/telegram/outputFlushPlan.js.map +1 -1
- package/dist/connectors/telegram/pendingTopicName.d.ts +26 -0
- package/dist/connectors/telegram/pendingTopicName.d.ts.map +1 -0
- package/dist/connectors/telegram/pendingTopicName.js +27 -0
- package/dist/connectors/telegram/pendingTopicName.js.map +1 -0
- package/dist/connectors/test/connector.d.ts.map +1 -1
- package/dist/connectors/test/connector.js +6 -1
- package/dist/connectors/test/connector.js.map +1 -1
- package/dist/i18n/de.d.ts.map +1 -1
- package/dist/i18n/de.js +90 -7
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +90 -7
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +90 -7
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/fr.d.ts.map +1 -1
- package/dist/i18n/fr.js +90 -7
- package/dist/i18n/fr.js.map +1 -1
- package/dist/i18n/hi.d.ts.map +1 -1
- package/dist/i18n/hi.js +90 -7
- package/dist/i18n/hi.js.map +1 -1
- package/dist/i18n/ja.d.ts.map +1 -1
- package/dist/i18n/ja.js +90 -7
- package/dist/i18n/ja.js.map +1 -1
- package/dist/i18n/ka.d.ts.map +1 -1
- package/dist/i18n/ka.js +90 -7
- package/dist/i18n/ka.js.map +1 -1
- package/dist/i18n/pt.d.ts.map +1 -1
- package/dist/i18n/pt.js +90 -7
- package/dist/i18n/pt.js.map +1 -1
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +90 -7
- package/dist/i18n/ru.js.map +1 -1
- package/dist/i18n/uk.d.ts.map +1 -1
- package/dist/i18n/uk.js +90 -7
- package/dist/i18n/uk.js.map +1 -1
- package/dist/i18n/uz.d.ts.map +1 -1
- package/dist/i18n/uz.js +90 -7
- package/dist/i18n/uz.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +90 -7
- package/dist/i18n/zh.js.map +1 -1
- package/dist/i18n.d.ts +1 -1
- package/dist/i18n.js +1 -1
- package/dist/installManager.d.ts +13 -0
- package/dist/installManager.d.ts.map +1 -1
- package/dist/installManager.js +38 -4
- package/dist/installManager.js.map +1 -1
- package/dist/instanceSettings.d.ts +40 -1
- package/dist/instanceSettings.d.ts.map +1 -1
- package/dist/instanceSettings.js +105 -26
- package/dist/instanceSettings.js.map +1 -1
- package/dist/openCodeQuestionFlow.d.ts +30 -0
- package/dist/openCodeQuestionFlow.d.ts.map +1 -1
- package/dist/openCodeQuestionFlow.js +43 -0
- package/dist/openCodeQuestionFlow.js.map +1 -1
- package/dist/postToSession.d.ts +20 -5
- package/dist/postToSession.d.ts.map +1 -1
- package/dist/postToSession.js +38 -7
- package/dist/postToSession.js.map +1 -1
- package/dist/remoteRun/cliArgs.d.ts +73 -0
- package/dist/remoteRun/cliArgs.d.ts.map +1 -0
- package/dist/remoteRun/cliArgs.js +197 -0
- package/dist/remoteRun/cliArgs.js.map +1 -0
- package/dist/remoteRun/commands.d.ts +56 -0
- package/dist/remoteRun/commands.d.ts.map +1 -0
- package/dist/remoteRun/commands.js +306 -0
- package/dist/remoteRun/commands.js.map +1 -0
- package/dist/remoteRun/depsPlan.d.ts +16 -0
- package/dist/remoteRun/depsPlan.d.ts.map +1 -0
- package/dist/remoteRun/depsPlan.js +82 -0
- package/dist/remoteRun/depsPlan.js.map +1 -0
- package/dist/remoteRun/exitCodes.d.ts +34 -0
- package/dist/remoteRun/exitCodes.d.ts.map +1 -0
- package/dist/remoteRun/exitCodes.js +38 -0
- package/dist/remoteRun/exitCodes.js.map +1 -0
- package/dist/remoteRun/foreground.d.ts +29 -0
- package/dist/remoteRun/foreground.d.ts.map +1 -0
- package/dist/remoteRun/foreground.js +159 -0
- package/dist/remoteRun/foreground.js.map +1 -0
- package/dist/remoteRun/interrupts.d.ts +17 -0
- package/dist/remoteRun/interrupts.d.ts.map +1 -0
- package/dist/remoteRun/interrupts.js +41 -0
- package/dist/remoteRun/interrupts.js.map +1 -0
- package/dist/remoteRun/jobFiles.d.ts +31 -0
- package/dist/remoteRun/jobFiles.d.ts.map +1 -0
- package/dist/remoteRun/jobFiles.js +84 -0
- package/dist/remoteRun/jobFiles.js.map +1 -0
- package/dist/remoteRun/jobFormat.d.ts +16 -0
- package/dist/remoteRun/jobFormat.d.ts.map +1 -0
- package/dist/remoteRun/jobFormat.js +130 -0
- package/dist/remoteRun/jobFormat.js.map +1 -0
- package/dist/remoteRun/jobId.d.ts +15 -0
- package/dist/remoteRun/jobId.d.ts.map +1 -0
- package/dist/remoteRun/jobId.js +85 -0
- package/dist/remoteRun/jobId.js.map +1 -0
- package/dist/remoteRun/jobMeta.d.ts +18 -0
- package/dist/remoteRun/jobMeta.d.ts.map +1 -0
- package/dist/remoteRun/jobMeta.js +33 -0
- package/dist/remoteRun/jobMeta.js.map +1 -0
- package/dist/remoteRun/jobQuery.d.ts +12 -0
- package/dist/remoteRun/jobQuery.d.ts.map +1 -0
- package/dist/remoteRun/jobQuery.js +22 -0
- package/dist/remoteRun/jobQuery.js.map +1 -0
- package/dist/remoteRun/jobRecords.d.ts +22 -0
- package/dist/remoteRun/jobRecords.d.ts.map +1 -0
- package/dist/remoteRun/jobRecords.js +145 -0
- package/dist/remoteRun/jobRecords.js.map +1 -0
- package/dist/remoteRun/jobState.d.ts +62 -0
- package/dist/remoteRun/jobState.d.ts.map +1 -0
- package/dist/remoteRun/jobState.js +132 -0
- package/dist/remoteRun/jobState.js.map +1 -0
- package/dist/remoteRun/leaseTiming.d.ts +18 -0
- package/dist/remoteRun/leaseTiming.d.ts.map +1 -0
- package/dist/remoteRun/leaseTiming.js +43 -0
- package/dist/remoteRun/leaseTiming.js.map +1 -0
- package/dist/remoteRun/output.d.ts +35 -0
- package/dist/remoteRun/output.d.ts.map +1 -0
- package/dist/remoteRun/output.js +82 -0
- package/dist/remoteRun/output.js.map +1 -0
- package/dist/remoteRun/pathPatterns.d.ts +33 -0
- package/dist/remoteRun/pathPatterns.d.ts.map +1 -0
- package/dist/remoteRun/pathPatterns.js +115 -0
- package/dist/remoteRun/pathPatterns.js.map +1 -0
- package/dist/remoteRun/probe.d.ts +56 -0
- package/dist/remoteRun/probe.d.ts.map +1 -0
- package/dist/remoteRun/probe.js +123 -0
- package/dist/remoteRun/probe.js.map +1 -0
- package/dist/remoteRun/project.d.ts +26 -0
- package/dist/remoteRun/project.d.ts.map +1 -0
- package/dist/remoteRun/project.js +159 -0
- package/dist/remoteRun/project.js.map +1 -0
- package/dist/remoteRun/projectConfig.d.ts +38 -0
- package/dist/remoteRun/projectConfig.d.ts.map +1 -0
- package/dist/remoteRun/projectConfig.js +87 -0
- package/dist/remoteRun/projectConfig.js.map +1 -0
- package/dist/remoteRun/remoteRunCli.d.ts +15 -0
- package/dist/remoteRun/remoteRunCli.d.ts.map +1 -0
- package/dist/remoteRun/remoteRunCli.js +144 -0
- package/dist/remoteRun/remoteRunCli.js.map +1 -0
- package/dist/remoteRun/remoteRunError.d.ts +14 -0
- package/dist/remoteRun/remoteRunError.d.ts.map +1 -0
- package/dist/remoteRun/remoteRunError.js +28 -0
- package/dist/remoteRun/remoteRunError.js.map +1 -0
- package/dist/remoteRun/rsync.d.ts +17 -0
- package/dist/remoteRun/rsync.d.ts.map +1 -0
- package/dist/remoteRun/rsync.js +39 -0
- package/dist/remoteRun/rsync.js.map +1 -0
- package/dist/remoteRun/runnerClient.d.ts +50 -0
- package/dist/remoteRun/runnerClient.d.ts.map +1 -0
- package/dist/remoteRun/runnerClient.js +139 -0
- package/dist/remoteRun/runnerClient.js.map +1 -0
- package/dist/remoteRun/runners.d.ts +57 -0
- package/dist/remoteRun/runners.d.ts.map +1 -0
- package/dist/remoteRun/runners.js +181 -0
- package/dist/remoteRun/runners.js.map +1 -0
- package/dist/remoteRun/runnersConfig.d.ts +44 -0
- package/dist/remoteRun/runnersConfig.d.ts.map +1 -0
- package/dist/remoteRun/runnersConfig.js +107 -0
- package/dist/remoteRun/runnersConfig.js.map +1 -0
- package/dist/remoteRun/safeCopy.d.ts +31 -0
- package/dist/remoteRun/safeCopy.d.ts.map +1 -0
- package/dist/remoteRun/safeCopy.js +137 -0
- package/dist/remoteRun/safeCopy.js.map +1 -0
- package/dist/remoteRun/sendPlan.d.ts +67 -0
- package/dist/remoteRun/sendPlan.d.ts.map +1 -0
- package/dist/remoteRun/sendPlan.js +139 -0
- package/dist/remoteRun/sendPlan.js.map +1 -0
- package/dist/remoteRun/ssh.d.ts +62 -0
- package/dist/remoteRun/ssh.d.ts.map +1 -0
- package/dist/remoteRun/ssh.js +202 -0
- package/dist/remoteRun/ssh.js.map +1 -0
- package/dist/remoteRun/submit.d.ts +29 -0
- package/dist/remoteRun/submit.d.ts.map +1 -0
- package/dist/remoteRun/submit.js +142 -0
- package/dist/remoteRun/submit.js.map +1 -0
- package/dist/requests/answerRequest.d.ts +5 -0
- package/dist/requests/answerRequest.d.ts.map +1 -1
- package/dist/requests/answerRequest.js +16 -1
- package/dist/requests/answerRequest.js.map +1 -1
- package/dist/requests/requestLedger.d.ts +13 -0
- package/dist/requests/requestLedger.d.ts.map +1 -1
- package/dist/requests/requestLedger.js +9 -0
- package/dist/requests/requestLedger.js.map +1 -1
- package/dist/requests/topicRequestSettle.d.ts +30 -0
- package/dist/requests/topicRequestSettle.d.ts.map +1 -0
- package/dist/requests/topicRequestSettle.js +23 -0
- package/dist/requests/topicRequestSettle.js.map +1 -0
- package/dist/requests/wakeUpEngine.d.ts +4 -0
- package/dist/requests/wakeUpEngine.d.ts.map +1 -1
- package/dist/requests/wakeUpEngine.js +5 -1
- package/dist/requests/wakeUpEngine.js.map +1 -1
- package/dist/scheduler/delivery.d.ts +2 -0
- package/dist/scheduler/delivery.d.ts.map +1 -1
- package/dist/scheduler/delivery.js +8 -4
- package/dist/scheduler/delivery.js.map +1 -1
- package/dist/scheduler/mcpSurface.d.ts +59 -10
- package/dist/scheduler/mcpSurface.d.ts.map +1 -1
- package/dist/scheduler/mcpSurface.js +276 -79
- package/dist/scheduler/mcpSurface.js.map +1 -1
- package/dist/scheduler/recurrence.d.ts +5 -0
- package/dist/scheduler/recurrence.d.ts.map +1 -1
- package/dist/scheduler/recurrence.js +12 -2
- package/dist/scheduler/recurrence.js.map +1 -1
- package/dist/scheduler/strictToolInput.d.ts +45 -0
- package/dist/scheduler/strictToolInput.d.ts.map +1 -0
- package/dist/scheduler/strictToolInput.js +155 -0
- package/dist/scheduler/strictToolInput.js.map +1 -0
- package/dist/scheduler/types.d.ts +5 -0
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/scheduler/workerWatch.d.ts +23 -0
- package/dist/scheduler/workerWatch.d.ts.map +1 -0
- package/dist/scheduler/workerWatch.js +26 -0
- package/dist/scheduler/workerWatch.js.map +1 -0
- package/dist/selfUpdate/emittedSource.d.ts +11 -0
- package/dist/selfUpdate/emittedSource.d.ts.map +1 -0
- package/dist/selfUpdate/emittedSource.js +24 -0
- package/dist/selfUpdate/emittedSource.js.map +1 -0
- package/dist/selfUpdate/gitSource.d.ts +0 -6
- package/dist/selfUpdate/gitSource.d.ts.map +1 -1
- package/dist/selfUpdate/gitSource.js +2 -18
- package/dist/selfUpdate/gitSource.js.map +1 -1
- package/dist/selfUpdate/updatePending.d.ts +1 -1
- package/dist/sendErrorClassifier.d.ts +10 -0
- package/dist/sendErrorClassifier.d.ts.map +1 -1
- package/dist/sendErrorClassifier.js +22 -0
- package/dist/sendErrorClassifier.js.map +1 -1
- package/dist/state.d.ts +78 -7
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +125 -8
- package/dist/state.js.map +1 -1
- package/dist/stateFile.d.ts +9 -0
- package/dist/stateFile.d.ts.map +1 -0
- package/dist/stateFile.js +57 -0
- package/dist/stateFile.js.map +1 -0
- package/dist/stt/sttInstall.d.ts +7 -3
- package/dist/stt/sttInstall.d.ts.map +1 -1
- package/dist/stt/sttInstall.js +8 -4
- package/dist/stt/sttInstall.js.map +1 -1
- package/dist/stt/sttSettings.d.ts +2 -1
- package/dist/stt/sttSettings.d.ts.map +1 -1
- package/dist/stt/sttSettings.js +3 -2
- package/dist/stt/sttSettings.js.map +1 -1
- package/dist/stt/transcribeChain.js +2 -0
- package/dist/stt/transcribeChain.js.map +1 -1
- package/dist/testTimings.d.ts +13 -3
- package/dist/testTimings.d.ts.map +1 -1
- package/dist/testTimings.js +26 -2
- package/dist/testTimings.js.map +1 -1
- package/dist/todos/board.d.ts +83 -0
- package/dist/todos/board.d.ts.map +1 -0
- package/dist/todos/board.js +227 -0
- package/dist/todos/board.js.map +1 -0
- package/dist/todos/format.d.ts +33 -0
- package/dist/todos/format.d.ts.map +1 -0
- package/dist/todos/format.js +90 -0
- package/dist/todos/format.js.map +1 -0
- package/dist/todos/merge.d.ts +46 -0
- package/dist/todos/merge.d.ts.map +1 -0
- package/dist/todos/merge.js +137 -0
- package/dist/todos/merge.js.map +1 -0
- package/dist/todos/midTurn.d.ts +75 -0
- package/dist/todos/midTurn.d.ts.map +1 -0
- package/dist/todos/midTurn.js +214 -0
- package/dist/todos/midTurn.js.map +1 -0
- package/dist/todos/nag.d.ts +85 -0
- package/dist/todos/nag.d.ts.map +1 -0
- package/dist/todos/nag.js +103 -0
- package/dist/todos/nag.js.map +1 -0
- package/dist/todos/order.d.ts +25 -0
- package/dist/todos/order.d.ts.map +1 -0
- package/dist/todos/order.js +61 -0
- package/dist/todos/order.js.map +1 -0
- package/dist/todos/projects.d.ts +102 -0
- package/dist/todos/projects.d.ts.map +1 -0
- package/dist/todos/projects.js +342 -0
- package/dist/todos/projects.js.map +1 -0
- package/dist/todos/registry.d.ts +64 -0
- package/dist/todos/registry.d.ts.map +1 -0
- package/dist/todos/registry.js +116 -0
- package/dist/todos/registry.js.map +1 -0
- package/dist/todos/reminderEngine.d.ts +78 -0
- package/dist/todos/reminderEngine.d.ts.map +1 -0
- package/dist/todos/reminderEngine.js +162 -0
- package/dist/todos/reminderEngine.js.map +1 -0
- package/dist/todos/store.d.ts +34 -0
- package/dist/todos/store.d.ts.map +1 -0
- package/dist/todos/store.js +208 -0
- package/dist/todos/store.js.map +1 -0
- package/dist/todos/tools.d.ts +26 -0
- package/dist/todos/tools.d.ts.map +1 -0
- package/dist/todos/tools.js +25 -0
- package/dist/todos/tools.js.map +1 -0
- package/dist/todos/types.d.ts +85 -0
- package/dist/todos/types.d.ts.map +1 -0
- package/dist/todos/types.js +30 -0
- package/dist/todos/types.js.map +1 -0
- package/dist/trackers/taskAgentLimit.d.ts +58 -0
- package/dist/trackers/taskAgentLimit.d.ts.map +1 -0
- package/dist/trackers/taskAgentLimit.js +62 -0
- package/dist/trackers/taskAgentLimit.js.map +1 -0
- package/dist/trackers/taskAgentSlots.d.ts +109 -0
- package/dist/trackers/taskAgentSlots.d.ts.map +1 -0
- package/dist/trackers/taskAgentSlots.js +227 -0
- package/dist/trackers/taskAgentSlots.js.map +1 -0
- package/dist/trackers/types.d.ts +14 -0
- package/dist/trackers/types.d.ts.map +1 -0
- package/dist/trackers/types.js +3 -0
- package/dist/trackers/types.js.map +1 -0
- package/dist/types.d.ts +80 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/adoptReplayGate.d.ts +16 -0
- package/dist/utils/adoptReplayGate.d.ts.map +1 -0
- package/dist/utils/adoptReplayGate.js +35 -0
- package/dist/utils/adoptReplayGate.js.map +1 -0
- package/dist/utils/agentPlugin.d.ts +113 -0
- package/dist/utils/agentPlugin.d.ts.map +1 -0
- package/dist/utils/agentPlugin.js +192 -0
- package/dist/utils/agentPlugin.js.map +1 -0
- package/dist/utils/autoContinueOnLimit.d.ts +7 -4
- package/dist/utils/autoContinueOnLimit.d.ts.map +1 -1
- package/dist/utils/autoContinueOnLimit.js +8 -5
- package/dist/utils/autoContinueOnLimit.js.map +1 -1
- package/dist/utils/botSkills.d.ts +62 -0
- package/dist/utils/botSkills.d.ts.map +1 -0
- package/dist/utils/botSkills.js +53 -0
- package/dist/utils/botSkills.js.map +1 -0
- package/dist/utils/botSkillsSetting.d.ts +7 -0
- package/dist/utils/botSkillsSetting.d.ts.map +1 -0
- package/dist/utils/botSkillsSetting.js +9 -0
- package/dist/utils/botSkillsSetting.js.map +1 -0
- package/dist/utils/claudeCodeVersion.d.ts +7 -0
- package/dist/utils/claudeCodeVersion.d.ts.map +1 -1
- package/dist/utils/claudeCodeVersion.js +14 -1
- package/dist/utils/claudeCodeVersion.js.map +1 -1
- package/dist/utils/claudeCompactHook.d.ts +33 -15
- package/dist/utils/claudeCompactHook.d.ts.map +1 -1
- package/dist/utils/claudeCompactHook.js +36 -17
- package/dist/utils/claudeCompactHook.js.map +1 -1
- package/dist/utils/claudeCompactSummary.d.ts +15 -0
- package/dist/utils/claudeCompactSummary.d.ts.map +1 -0
- package/dist/utils/claudeCompactSummary.js +96 -0
- package/dist/utils/claudeCompactSummary.js.map +1 -0
- package/dist/utils/claudeLimitSwitch.d.ts +11 -9
- package/dist/utils/claudeLimitSwitch.d.ts.map +1 -1
- package/dist/utils/claudeLimitSwitch.js +18 -16
- package/dist/utils/claudeLimitSwitch.js.map +1 -1
- package/dist/utils/claudeProfiles.d.ts +5 -0
- package/dist/utils/claudeProfiles.d.ts.map +1 -1
- package/dist/utils/claudeProfiles.js +14 -0
- package/dist/utils/claudeProfiles.js.map +1 -1
- package/dist/utils/claudeSessionTitle.d.ts +2 -1
- package/dist/utils/claudeSessionTitle.d.ts.map +1 -1
- package/dist/utils/claudeSessionTitle.js +14 -14
- package/dist/utils/claudeSessionTitle.js.map +1 -1
- package/dist/utils/claudeUsage.d.ts +95 -15
- package/dist/utils/claudeUsage.d.ts.map +1 -1
- package/dist/utils/claudeUsage.js +241 -41
- package/dist/utils/claudeUsage.js.map +1 -1
- package/dist/utils/claudeUsageRefresher.d.ts +10 -0
- package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
- package/dist/utils/claudeUsageRefresher.js +11 -0
- package/dist/utils/claudeUsageRefresher.js.map +1 -1
- package/dist/utils/claudeUserEntry.d.ts +50 -0
- package/dist/utils/claudeUserEntry.d.ts.map +1 -0
- package/dist/utils/claudeUserEntry.js +120 -0
- package/dist/utils/claudeUserEntry.js.map +1 -0
- package/dist/utils/clearThreadOutputQueues.d.ts +4 -3
- package/dist/utils/clearThreadOutputQueues.d.ts.map +1 -1
- package/dist/utils/clearThreadOutputQueues.js +1 -1
- package/dist/utils/clearThreadOutputQueues.js.map +1 -1
- package/dist/utils/compactOnIdle.d.ts +33 -13
- package/dist/utils/compactOnIdle.d.ts.map +1 -1
- package/dist/utils/compactOnIdle.js +35 -15
- package/dist/utils/compactOnIdle.js.map +1 -1
- package/dist/utils/fallbackBridge.d.ts +5 -0
- package/dist/utils/fallbackBridge.d.ts.map +1 -1
- package/dist/utils/fallbackBridge.js +19 -3
- package/dist/utils/fallbackBridge.js.map +1 -1
- package/dist/utils/fallbackChain.d.ts +9 -3
- package/dist/utils/fallbackChain.d.ts.map +1 -1
- package/dist/utils/fallbackChain.js +21 -3
- package/dist/utils/fallbackChain.js.map +1 -1
- package/dist/utils/fileSendPlan.d.ts +6 -0
- package/dist/utils/fileSendPlan.d.ts.map +1 -1
- package/dist/utils/fileSendPlan.js +11 -0
- package/dist/utils/fileSendPlan.js.map +1 -1
- package/dist/utils/fileSendService.d.ts +7 -1
- package/dist/utils/fileSendService.d.ts.map +1 -1
- package/dist/utils/fileSendService.js +6 -2
- package/dist/utils/fileSendService.js.map +1 -1
- package/dist/utils/groupFinalizePlan.d.ts +8 -10
- package/dist/utils/groupFinalizePlan.d.ts.map +1 -1
- package/dist/utils/groupFinalizePlan.js +8 -12
- package/dist/utils/groupFinalizePlan.js.map +1 -1
- package/dist/utils/jsonStreamHost.d.ts +13 -0
- package/dist/utils/jsonStreamHost.d.ts.map +1 -1
- package/dist/utils/jsonStreamHost.js +13 -0
- package/dist/utils/jsonStreamHost.js.map +1 -1
- package/dist/utils/messageSendService.d.ts +5 -3
- package/dist/utils/messageSendService.d.ts.map +1 -1
- package/dist/utils/messageSendService.js +12 -5
- package/dist/utils/messageSendService.js.map +1 -1
- package/dist/utils/openCodeCompactPlugin.d.ts +39 -14
- package/dist/utils/openCodeCompactPlugin.d.ts.map +1 -1
- package/dist/utils/openCodeCompactPlugin.js +106 -38
- package/dist/utils/openCodeCompactPlugin.js.map +1 -1
- package/dist/utils/promptDelivery.d.ts +29 -12
- package/dist/utils/promptDelivery.d.ts.map +1 -1
- package/dist/utils/promptDelivery.js +13 -5
- package/dist/utils/promptDelivery.js.map +1 -1
- package/dist/utils/sentKindsSummary.d.ts +10 -0
- package/dist/utils/sentKindsSummary.d.ts.map +1 -0
- package/dist/utils/sentKindsSummary.js +18 -0
- package/dist/utils/sentKindsSummary.js.map +1 -0
- package/dist/utils/telegramError.d.ts +6 -0
- package/dist/utils/telegramError.d.ts.map +1 -1
- package/dist/utils/telegramError.js +12 -0
- package/dist/utils/telegramError.js.map +1 -1
- package/dist/utils/topicView.d.ts +1 -1
- package/dist/utils/topicView.js +1 -1
- package/dist/utils/transcribeAudio.d.ts +10 -0
- package/dist/utils/transcribeAudio.d.ts.map +1 -1
- package/dist/utils/transcribeAudio.js +22 -3
- package/dist/utils/transcribeAudio.js.map +1 -1
- package/dist/utils/transcriptErrorTime.d.ts +8 -0
- package/dist/utils/transcriptErrorTime.d.ts.map +1 -0
- package/dist/utils/transcriptErrorTime.js +56 -0
- package/dist/utils/transcriptErrorTime.js.map +1 -0
- package/dist/utils/wedgeRecovery.d.ts +2 -0
- package/dist/utils/wedgeRecovery.d.ts.map +1 -1
- package/dist/utils/wedgeRecovery.js +1 -1
- package/dist/utils/wedgeRecovery.js.map +1 -1
- package/package.json +11 -3
- package/remoteRun/runner.sh +1242 -0
- 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">
|
|
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
|
|
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
|
-
##
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
##
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
150
|
+
## License
|
|
849
151
|
|
|
850
152
|
MIT
|