charness 3.3.2 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +639 -0
  2. package/DEVELOPMENT.md +76 -0
  3. package/README.md +78 -7
  4. package/SETUP.md +65 -19
  5. package/dist/adapters/claudeCliAdapter.js +2 -2
  6. package/dist/adapters/claudeCliAdapter.js.map +1 -1
  7. package/dist/adapters/claudeConfigDir.d.ts +18 -0
  8. package/dist/adapters/claudeConfigDir.d.ts.map +1 -1
  9. package/dist/adapters/claudeJsonStreamAdapter.d.ts +8 -1
  10. package/dist/adapters/claudeJsonStreamAdapter.d.ts.map +1 -1
  11. package/dist/adapters/claudeJsonStreamAdapter.js +24 -6
  12. package/dist/adapters/claudeJsonStreamAdapter.js.map +1 -1
  13. package/dist/adapters/createAdapter.d.ts +2 -1
  14. package/dist/adapters/createAdapter.d.ts.map +1 -1
  15. package/dist/adapters/createAdapter.js +16 -0
  16. package/dist/adapters/createAdapter.js.map +1 -1
  17. package/dist/adapters/openCodeAdapter.d.ts +11 -0
  18. package/dist/adapters/openCodeAdapter.d.ts.map +1 -1
  19. package/dist/adapters/openCodeAdapter.js +24 -9
  20. package/dist/adapters/openCodeAdapter.js.map +1 -1
  21. package/dist/agentLogin/agentLogin.d.ts +19 -1
  22. package/dist/agentLogin/agentLogin.d.ts.map +1 -1
  23. package/dist/agentLogin/agentLogin.js +64 -18
  24. package/dist/agentLogin/agentLogin.js.map +1 -1
  25. package/dist/apiErrorRetry.d.ts +3 -1
  26. package/dist/apiErrorRetry.d.ts.map +1 -1
  27. package/dist/apiErrorRetry.js +11 -1
  28. package/dist/apiErrorRetry.js.map +1 -1
  29. package/dist/bot.d.ts +0 -3
  30. package/dist/bot.d.ts.map +1 -1
  31. package/dist/bot.js +487 -182
  32. package/dist/bot.js.map +1 -1
  33. package/dist/changelogEntries.d.ts +25 -0
  34. package/dist/changelogEntries.d.ts.map +1 -0
  35. package/dist/changelogEntries.js +66 -0
  36. package/dist/changelogEntries.js.map +1 -0
  37. package/dist/cli/bot.d.ts +6 -2
  38. package/dist/cli/bot.d.ts.map +1 -1
  39. package/dist/cli/bot.js +34 -2
  40. package/dist/cli/bot.js.map +1 -1
  41. package/dist/cli/claudeProfiles.d.ts +1 -1
  42. package/dist/cli/claudeProfiles.d.ts.map +1 -1
  43. package/dist/cli/claudeProfiles.js +11 -6
  44. package/dist/cli/claudeProfiles.js.map +1 -1
  45. package/dist/cli/hot.d.ts +7 -0
  46. package/dist/cli/hot.d.ts.map +1 -1
  47. package/dist/cli/hot.js +53 -3
  48. package/dist/cli/hot.js.map +1 -1
  49. package/dist/cli.d.ts +1 -1
  50. package/dist/cli.d.ts.map +1 -1
  51. package/dist/cli.js +8 -0
  52. package/dist/cli.js.map +1 -1
  53. package/dist/connectors/telegram/bindPicker.d.ts +64 -0
  54. package/dist/connectors/telegram/bindPicker.d.ts.map +1 -0
  55. package/dist/connectors/telegram/bindPicker.js +97 -0
  56. package/dist/connectors/telegram/bindPicker.js.map +1 -0
  57. package/dist/connectors/telegram/commands/claudeAccounts.d.ts +6 -2
  58. package/dist/connectors/telegram/commands/claudeAccounts.d.ts.map +1 -1
  59. package/dist/connectors/telegram/commands/claudeAccounts.js +127 -28
  60. package/dist/connectors/telegram/commands/claudeAccounts.js.map +1 -1
  61. package/dist/connectors/telegram/commands/fallbackChain.d.ts.map +1 -1
  62. package/dist/connectors/telegram/commands/fallbackChain.js +6 -2
  63. package/dist/connectors/telegram/commands/fallbackChain.js.map +1 -1
  64. package/dist/connectors/telegram/commands/selfUpdate.d.ts +16 -0
  65. package/dist/connectors/telegram/commands/selfUpdate.d.ts.map +1 -0
  66. package/dist/connectors/telegram/commands/selfUpdate.js +210 -0
  67. package/dist/connectors/telegram/commands/selfUpdate.js.map +1 -0
  68. package/dist/connectors/telegram/commands/stt.d.ts +1 -0
  69. package/dist/connectors/telegram/commands/stt.d.ts.map +1 -1
  70. package/dist/connectors/telegram/commands/stt.js +5 -0
  71. package/dist/connectors/telegram/commands/stt.js.map +1 -1
  72. package/dist/connectors/telegram/commands/topicCommand.d.ts +75 -0
  73. package/dist/connectors/telegram/commands/topicCommand.d.ts.map +1 -0
  74. package/dist/connectors/telegram/commands/topicCommand.js +391 -0
  75. package/dist/connectors/telegram/commands/topicCommand.js.map +1 -0
  76. package/dist/i18n/de.d.ts.map +1 -1
  77. package/dist/i18n/de.js +136 -3
  78. package/dist/i18n/de.js.map +1 -1
  79. package/dist/i18n/en.d.ts.map +1 -1
  80. package/dist/i18n/en.js +136 -3
  81. package/dist/i18n/en.js.map +1 -1
  82. package/dist/i18n/es.d.ts.map +1 -1
  83. package/dist/i18n/es.js +136 -3
  84. package/dist/i18n/es.js.map +1 -1
  85. package/dist/i18n/fr.d.ts.map +1 -1
  86. package/dist/i18n/fr.js +136 -3
  87. package/dist/i18n/fr.js.map +1 -1
  88. package/dist/i18n/hi.d.ts.map +1 -1
  89. package/dist/i18n/hi.js +136 -3
  90. package/dist/i18n/hi.js.map +1 -1
  91. package/dist/i18n/ja.d.ts.map +1 -1
  92. package/dist/i18n/ja.js +136 -3
  93. package/dist/i18n/ja.js.map +1 -1
  94. package/dist/i18n/ka.d.ts.map +1 -1
  95. package/dist/i18n/ka.js +136 -3
  96. package/dist/i18n/ka.js.map +1 -1
  97. package/dist/i18n/pt.d.ts.map +1 -1
  98. package/dist/i18n/pt.js +136 -3
  99. package/dist/i18n/pt.js.map +1 -1
  100. package/dist/i18n/ru.d.ts.map +1 -1
  101. package/dist/i18n/ru.js +136 -3
  102. package/dist/i18n/ru.js.map +1 -1
  103. package/dist/i18n/uk.d.ts.map +1 -1
  104. package/dist/i18n/uk.js +136 -3
  105. package/dist/i18n/uk.js.map +1 -1
  106. package/dist/i18n/uz.d.ts.map +1 -1
  107. package/dist/i18n/uz.js +136 -3
  108. package/dist/i18n/uz.js.map +1 -1
  109. package/dist/i18n/zh.d.ts.map +1 -1
  110. package/dist/i18n/zh.js +136 -3
  111. package/dist/i18n/zh.js.map +1 -1
  112. package/dist/instanceSettings.d.ts +12 -1
  113. package/dist/instanceSettings.d.ts.map +1 -1
  114. package/dist/instanceSettings.js +40 -4
  115. package/dist/instanceSettings.js.map +1 -1
  116. package/dist/scheduler/mcpSurface.d.ts.map +1 -1
  117. package/dist/scheduler/mcpSurface.js +4 -2
  118. package/dist/scheduler/mcpSurface.js.map +1 -1
  119. package/dist/selfUpdate/bootRollback.d.ts +22 -0
  120. package/dist/selfUpdate/bootRollback.d.ts.map +1 -0
  121. package/dist/selfUpdate/bootRollback.js +79 -0
  122. package/dist/selfUpdate/bootRollback.js.map +1 -0
  123. package/dist/selfUpdate/commandRunner.d.ts +21 -0
  124. package/dist/selfUpdate/commandRunner.d.ts.map +1 -0
  125. package/dist/selfUpdate/commandRunner.js +32 -0
  126. package/dist/selfUpdate/commandRunner.js.map +1 -0
  127. package/dist/selfUpdate/constants.d.ts +26 -0
  128. package/dist/selfUpdate/constants.d.ts.map +1 -0
  129. package/dist/selfUpdate/constants.js +31 -0
  130. package/dist/selfUpdate/constants.js.map +1 -0
  131. package/dist/selfUpdate/gitSource.d.ts +58 -0
  132. package/dist/selfUpdate/gitSource.d.ts.map +1 -0
  133. package/dist/selfUpdate/gitSource.js +183 -0
  134. package/dist/selfUpdate/gitSource.js.map +1 -0
  135. package/dist/selfUpdate/hostLock.d.ts +16 -0
  136. package/dist/selfUpdate/hostLock.d.ts.map +1 -0
  137. package/dist/selfUpdate/hostLock.js +101 -0
  138. package/dist/selfUpdate/hostLock.js.map +1 -0
  139. package/dist/selfUpdate/hotSupervisor.d.ts +47 -0
  140. package/dist/selfUpdate/hotSupervisor.d.ts.map +1 -0
  141. package/dist/selfUpdate/hotSupervisor.js +125 -0
  142. package/dist/selfUpdate/hotSupervisor.js.map +1 -0
  143. package/dist/selfUpdate/installKind.d.ts +45 -0
  144. package/dist/selfUpdate/installKind.d.ts.map +1 -0
  145. package/dist/selfUpdate/installKind.js +99 -0
  146. package/dist/selfUpdate/installKind.js.map +1 -0
  147. package/dist/selfUpdate/launchEnvironment.d.ts +3 -0
  148. package/dist/selfUpdate/launchEnvironment.d.ts.map +1 -0
  149. package/dist/selfUpdate/launchEnvironment.js +16 -0
  150. package/dist/selfUpdate/launchEnvironment.js.map +1 -0
  151. package/dist/selfUpdate/npmSource.d.ts +34 -0
  152. package/dist/selfUpdate/npmSource.d.ts.map +1 -0
  153. package/dist/selfUpdate/npmSource.js +107 -0
  154. package/dist/selfUpdate/npmSource.js.map +1 -0
  155. package/dist/selfUpdate/releaseNotes.d.ts +33 -0
  156. package/dist/selfUpdate/releaseNotes.d.ts.map +1 -0
  157. package/dist/selfUpdate/releaseNotes.js +115 -0
  158. package/dist/selfUpdate/releaseNotes.js.map +1 -0
  159. package/dist/selfUpdate/restart.d.ts +33 -0
  160. package/dist/selfUpdate/restart.d.ts.map +1 -0
  161. package/dist/selfUpdate/restart.js +94 -0
  162. package/dist/selfUpdate/restart.js.map +1 -0
  163. package/dist/selfUpdate/selfCheck.d.ts +20 -0
  164. package/dist/selfUpdate/selfCheck.d.ts.map +1 -0
  165. package/dist/selfUpdate/selfCheck.js +126 -0
  166. package/dist/selfUpdate/selfCheck.js.map +1 -0
  167. package/dist/selfUpdate/semver.d.ts +24 -0
  168. package/dist/selfUpdate/semver.d.ts.map +1 -0
  169. package/dist/selfUpdate/semver.js +165 -0
  170. package/dist/selfUpdate/semver.js.map +1 -0
  171. package/dist/selfUpdate/types.d.ts +51 -0
  172. package/dist/selfUpdate/types.d.ts.map +1 -0
  173. package/dist/selfUpdate/types.js +3 -0
  174. package/dist/selfUpdate/types.js.map +1 -0
  175. package/dist/selfUpdate/updateEngine.d.ts +110 -0
  176. package/dist/selfUpdate/updateEngine.d.ts.map +1 -0
  177. package/dist/selfUpdate/updateEngine.js +727 -0
  178. package/dist/selfUpdate/updateEngine.js.map +1 -0
  179. package/dist/selfUpdate/updateMessages.d.ts +84 -0
  180. package/dist/selfUpdate/updateMessages.d.ts.map +1 -0
  181. package/dist/selfUpdate/updateMessages.js +115 -0
  182. package/dist/selfUpdate/updateMessages.js.map +1 -0
  183. package/dist/selfUpdate/updateModes.d.ts +17 -0
  184. package/dist/selfUpdate/updateModes.d.ts.map +1 -0
  185. package/dist/selfUpdate/updateModes.js +14 -0
  186. package/dist/selfUpdate/updateModes.js.map +1 -0
  187. package/dist/selfUpdate/updatePending.d.ts +76 -0
  188. package/dist/selfUpdate/updatePending.d.ts.map +1 -0
  189. package/dist/selfUpdate/updatePending.js +131 -0
  190. package/dist/selfUpdate/updatePending.js.map +1 -0
  191. package/dist/selfUpdate/updatePolicy.d.ts +115 -0
  192. package/dist/selfUpdate/updatePolicy.d.ts.map +1 -0
  193. package/dist/selfUpdate/updatePolicy.js +83 -0
  194. package/dist/selfUpdate/updatePolicy.js.map +1 -0
  195. package/dist/selfUpdate/updateRuntime.d.ts +22 -0
  196. package/dist/selfUpdate/updateRuntime.d.ts.map +1 -0
  197. package/dist/selfUpdate/updateRuntime.js +148 -0
  198. package/dist/selfUpdate/updateRuntime.js.map +1 -0
  199. package/dist/startupPromptBuffer.d.ts +2 -0
  200. package/dist/startupPromptBuffer.d.ts.map +1 -1
  201. package/dist/startupPromptBuffer.js +4 -0
  202. package/dist/startupPromptBuffer.js.map +1 -1
  203. package/dist/state.d.ts +43 -0
  204. package/dist/state.d.ts.map +1 -1
  205. package/dist/state.js +54 -0
  206. package/dist/state.js.map +1 -1
  207. package/dist/types.d.ts +10 -0
  208. package/dist/types.d.ts.map +1 -1
  209. package/dist/utils/anthropicApiKeyCheck.d.ts +19 -0
  210. package/dist/utils/anthropicApiKeyCheck.d.ts.map +1 -0
  211. package/dist/utils/anthropicApiKeyCheck.js +41 -0
  212. package/dist/utils/anthropicApiKeyCheck.js.map +1 -0
  213. package/dist/utils/claudeAccountPickerPlan.d.ts +6 -1
  214. package/dist/utils/claudeAccountPickerPlan.d.ts.map +1 -1
  215. package/dist/utils/claudeAccountPickerPlan.js +5 -1
  216. package/dist/utils/claudeAccountPickerPlan.js.map +1 -1
  217. package/dist/utils/claudeApiKeyHelper.d.ts +19 -0
  218. package/dist/utils/claudeApiKeyHelper.d.ts.map +1 -0
  219. package/dist/utils/claudeApiKeyHelper.js +32 -0
  220. package/dist/utils/claudeApiKeyHelper.js.map +1 -0
  221. package/dist/utils/claudeApiSpend.d.ts +73 -0
  222. package/dist/utils/claudeApiSpend.d.ts.map +1 -0
  223. package/dist/utils/claudeApiSpend.js +82 -0
  224. package/dist/utils/claudeApiSpend.js.map +1 -0
  225. package/dist/utils/claudeCompactHook.d.ts +11 -6
  226. package/dist/utils/claudeCompactHook.d.ts.map +1 -1
  227. package/dist/utils/claudeCompactHook.js +28 -11
  228. package/dist/utils/claudeCompactHook.js.map +1 -1
  229. package/dist/utils/claudeLimitSwitch.d.ts +2 -1
  230. package/dist/utils/claudeLimitSwitch.d.ts.map +1 -1
  231. package/dist/utils/claudeLimitSwitch.js +3 -2
  232. package/dist/utils/claudeLimitSwitch.js.map +1 -1
  233. package/dist/utils/claudeProfileUsageLines.d.ts +18 -3
  234. package/dist/utils/claudeProfileUsageLines.d.ts.map +1 -1
  235. package/dist/utils/claudeProfileUsageLines.js +23 -2
  236. package/dist/utils/claudeProfileUsageLines.js.map +1 -1
  237. package/dist/utils/claudeProfiles.d.ts +57 -2
  238. package/dist/utils/claudeProfiles.d.ts.map +1 -1
  239. package/dist/utils/claudeProfiles.js +96 -3
  240. package/dist/utils/claudeProfiles.js.map +1 -1
  241. package/dist/utils/claudeStreamJson.d.ts +4 -1
  242. package/dist/utils/claudeStreamJson.d.ts.map +1 -1
  243. package/dist/utils/claudeStreamJson.js +6 -0
  244. package/dist/utils/claudeStreamJson.js.map +1 -1
  245. package/dist/utils/claudeTranscriptContent.d.ts +8 -0
  246. package/dist/utils/claudeTranscriptContent.d.ts.map +1 -1
  247. package/dist/utils/claudeTranscriptContent.js +44 -0
  248. package/dist/utils/claudeTranscriptContent.js.map +1 -1
  249. package/dist/utils/claudeUsage.d.ts +22 -6
  250. package/dist/utils/claudeUsage.d.ts.map +1 -1
  251. package/dist/utils/claudeUsage.js +12 -5
  252. package/dist/utils/claudeUsage.js.map +1 -1
  253. package/dist/utils/claudeUsageRefresher.d.ts +6 -0
  254. package/dist/utils/claudeUsageRefresher.d.ts.map +1 -1
  255. package/dist/utils/claudeUsageRefresher.js +2 -2
  256. package/dist/utils/claudeUsageRefresher.js.map +1 -1
  257. package/dist/utils/fallbackChain.d.ts +15 -8
  258. package/dist/utils/fallbackChain.d.ts.map +1 -1
  259. package/dist/utils/fallbackChain.js +30 -13
  260. package/dist/utils/fallbackChain.js.map +1 -1
  261. package/dist/utils/limitsBoardPlan.d.ts +4 -2
  262. package/dist/utils/limitsBoardPlan.d.ts.map +1 -1
  263. package/dist/utils/limitsBoardPlan.js +12 -5
  264. package/dist/utils/limitsBoardPlan.js.map +1 -1
  265. package/dist/utils/recvPreviewRedaction.d.ts +7 -2
  266. package/dist/utils/recvPreviewRedaction.d.ts.map +1 -1
  267. package/dist/utils/recvPreviewRedaction.js +10 -4
  268. package/dist/utils/recvPreviewRedaction.js.map +1 -1
  269. package/dist/utils/threadStatusReport.d.ts +12 -1
  270. package/dist/utils/threadStatusReport.d.ts.map +1 -1
  271. package/dist/utils/threadStatusReport.js +13 -4
  272. package/dist/utils/threadStatusReport.js.map +1 -1
  273. package/dist/utils/updateScreenPlan.d.ts +38 -0
  274. package/dist/utils/updateScreenPlan.d.ts.map +1 -0
  275. package/dist/utils/updateScreenPlan.js +143 -0
  276. package/dist/utils/updateScreenPlan.js.map +1 -0
  277. package/package.json +3 -1
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.bindPickerCallbacks = exports.bindPageNoopCallback = exports.bindLeaveCurrentCallback = exports.bindCreateFolderCallback = exports.BIND_PAGE_SIZE = void 0;
4
+ exports.buildBindKeyboard = buildBindKeyboard;
5
+ /**
6
+ * The folder picker of `/bind` — and of `/topic`, which shows the same keyboard with its buttons pointing at a
7
+ * wizard instead of at the topic it sits in. A leaf module (no `bot.ts`, no environment): the command modules take it
8
+ * as a plain import.
9
+ */
10
+ const telegraf_1 = require("telegraf");
11
+ const i18n_1 = require("../../i18n");
12
+ const validation_1 = require("../../validation");
13
+ /** Inline-keyboard page size for `/bind`. 20 fits one phone screen comfortably. */
14
+ exports.BIND_PAGE_SIZE = 20;
15
+ /**
16
+ * «Create new folder» button of the `/bind` picker. The callback id deliberately does NOT start with `bind_`, so the
17
+ * `bind_<subdir>` action regex can't mistake it for a folder pick.
18
+ */
19
+ exports.bindCreateFolderCallback = 'bindCreateFolder';
20
+ /**
21
+ * «Leave current dir» button — the folded-in `/unbind`. Like {@link bindCreateFolderCallback}, the callback id
22
+ * deliberately does NOT start with `bind_`. Only rendered in a bound topic.
23
+ */
24
+ exports.bindLeaveCurrentCallback = 'bindLeaveCurrent';
25
+ /** The middle «N/M» pill of the nav row: pure UI, answered without a state change (shared by every picker built here). */
26
+ exports.bindPageNoopCallback = 'bind_page_noop';
27
+ /** `/bind`'s own: the folder name rides in the data, pages are numbered. */
28
+ exports.bindPickerCallbacks = {
29
+ getFolderCallback: (subdir) => `bind_${subdir}`,
30
+ getPageCallback: (page) => `bind_page_${page}`,
31
+ createFolderCallback: exports.bindCreateFolderCallback,
32
+ };
33
+ /**
34
+ * @description Build a `subdir`-suggestion keyboard for `/bind` and friends.
35
+ *
36
+ * Lists are paginated at {@link BIND_PAGE_SIZE} entries: a `WORK_ROOT` with
37
+ * many subfolders (mono-repo workspace, packages dir, etc.) would otherwise
38
+ * push the inline keyboard past Telegram's per-message height limit and
39
+ * lose entries below the fold.
40
+ *
41
+ * The nav row appears only when there's more than one page. The middle
42
+ * `pageLabel` button is a no-op ({@link bindPageNoopCallback}) — it exists
43
+ * solely so the user sees "2/5" without us having to fake a divider with
44
+ * a separate message.
45
+ *
46
+ * Subdirs whose callback_data exceeds 64 bytes are filtered out upstream
47
+ * in `listAvailableSubdirs`. `page` is clamped to a valid index so a stale
48
+ * callback (folders disappeared since the keyboard was sent) just lands
49
+ * on the last available page.
50
+ *
51
+ * When `isBound` the FIRST row is a full-width «leave current dir» button
52
+ * ({@link bindLeaveCurrentCallback}, the folded-in `/unbind`); the «create new folder»
53
+ * button ({@link bindCreateFolderCallback}) follows. Both callbacks deliberately do
54
+ * NOT start with `bind_`, so they can't be mistaken for a folder pick by the
55
+ * `bind_<subdir>` action regex. They ride on every page so they're reachable
56
+ * regardless of pagination.
57
+ *
58
+ * `callbacks` re-targets the buttons (the `/topic` wizard's, which adds a «cancel» row at the bottom); the layout
59
+ * stays the one `/bind` has.
60
+ */
61
+ function buildBindKeyboard(subdirs, page = 0, pageSize = exports.BIND_PAGE_SIZE, isBound = false, callbacks = exports.bindPickerCallbacks) {
62
+ const { slice, currentPage, totalPages } = (0, validation_1.paginateBindList)(subdirs, page, pageSize);
63
+ const firstIndex = currentPage * pageSize;
64
+ const rows = [
65
+ [telegraf_1.Markup.button.callback((0, i18n_1.t)('bind.create_button'), callbacks.createFolderCallback)],
66
+ ];
67
+ // A bound topic gets a full-width «leave current dir» row PREPENDED above the
68
+ // «create new folder» row — this is the folded-in `/unbind`. Its callback
69
+ // deliberately avoids the `bind_` prefix so the `bind_<subdir>` action regex
70
+ // can't catch it. The row is omitted when unbound (nothing to leave).
71
+ if (isBound) {
72
+ rows.unshift([telegraf_1.Markup.button.callback((0, i18n_1.t)('bind.leave_button'), exports.bindLeaveCurrentCallback)]);
73
+ }
74
+ for (let i = 0; i < slice.length; i += 2) {
75
+ const row = [telegraf_1.Markup.button.callback(`📁 ${slice[i]}`, callbacks.getFolderCallback(slice[i], firstIndex + i))];
76
+ if (slice[i + 1]) {
77
+ row.push(telegraf_1.Markup.button.callback(`📁 ${slice[i + 1]}`, callbacks.getFolderCallback(slice[i + 1], firstIndex + i + 1)));
78
+ }
79
+ rows.push(row);
80
+ }
81
+ if (totalPages > 1) {
82
+ const nav = [];
83
+ if (currentPage > 0) {
84
+ nav.push(telegraf_1.Markup.button.callback('⬅️ Prev', callbacks.getPageCallback(currentPage - 1)));
85
+ }
86
+ nav.push(telegraf_1.Markup.button.callback(`${currentPage + 1}/${totalPages}`, exports.bindPageNoopCallback));
87
+ if (currentPage < totalPages - 1) {
88
+ nav.push(telegraf_1.Markup.button.callback('Next ➡️', callbacks.getPageCallback(currentPage + 1)));
89
+ }
90
+ rows.push(nav);
91
+ }
92
+ if (callbacks.cancelButton) {
93
+ rows.push([telegraf_1.Markup.button.callback(callbacks.cancelButton.label, callbacks.cancelButton.callback)]);
94
+ }
95
+ return telegraf_1.Markup.inlineKeyboard(rows);
96
+ }
97
+ //# sourceMappingURL=bindPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bindPicker.js","sourceRoot":"","sources":["../../../src/connectors/telegram/bindPicker.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@ import { type FallbackChainEntry } from '../../../utils/fallbackChain';
5
5
  import type { BackendFamily } from '../../../utils/backendSwitchPlan';
6
6
  import type { OperatorSwitchOutcome } from '../../../utils/operatorSwitchPlan';
7
7
  import { type ClaudeAuthStatus } from '../../../utils/claudeProfiles';
8
+ import type { AnthropicApiKeyCheck } from '../../../utils/anthropicApiKeyCheck';
8
9
  import type { BotCore } from './botCore';
9
10
  /**
10
11
  * How a profile change reached the topic's live session: the adapter's own answer, plus `restartRequired`
@@ -15,7 +16,7 @@ export type ClaudeProfileApplyOutcome = ClaudeConfigDirApplyResult | 'restartReq
15
16
  * What the account picker needs from the bot: the shared core, where profiles live, the CLI status probe,
16
17
  * the profile sign-in driver, and the hooks that move live sessions onto a changed pick.
17
18
  */
18
- export interface ClaudeAccountsPorts extends Pick<BotCore, 'bot' | 'command' | 'getState' | 'replyToThread' | 'editThreadMessage' | 'authoriseContext' | 'withThreadLocale' | 'checkIsGeneral'> {
19
+ export interface ClaudeAccountsPorts extends Pick<BotCore, 'bot' | 'command' | 'getState' | 'replyToThread' | 'editThreadMessage' | 'deleteThreadMessage' | 'authoriseContext' | 'withThreadLocale' | 'checkIsGeneral'> {
19
20
  getDataDir: () => string;
20
21
  /** The instance zone (`/timezone`) the reset instants are shown in. */
21
22
  getInstanceTimezone: () => string;
@@ -58,6 +59,8 @@ export interface ClaudeAccountsPorts extends Pick<BotCore, 'bot' | 'command' | '
58
59
  applyAutoSwitch: (key: SessionKey, isGeneral: boolean, enabled: boolean) => Promise<string>;
59
60
  /** `~/.claude`, where the default login lives; injectable so a test signs out a temp directory, never the real one. */
60
61
  getHomeClaudeDir?: () => string;
62
+ /** Whether the API accepts a pasted key (`utils/anthropicApiKeyCheck.ts`); injectable so a test never reaches the network. */
63
+ verifyAnthropicApiKey: (apiKey: string) => Promise<AnthropicApiKeyCheck>;
61
64
  }
62
65
  /**
63
66
  * @description Build the `/account` feature over its ports: the command, its buttons, the name capture the
@@ -69,8 +72,9 @@ export declare function createClaudeAccounts(ports: ClaudeAccountsPorts): {
69
72
  registerClaudeAccountCallbacks: () => void;
70
73
  checkIsAwaitingProfileName: (key: SessionKey) => boolean;
71
74
  cancelProfileNameCapture: (key: SessionKey) => boolean;
72
- submitProfileName: (key: SessionKey, text: string) => Promise<void>;
75
+ submitProfileName: (key: SessionKey, text: string, messageId?: number) => Promise<void>;
73
76
  pickClaudeAccount: (key: SessionKey, isGeneral: boolean, name: string) => Promise<string | null>;
74
77
  onClaudeProfileAuthStatus: (_key: SessionKey, profile: ClaudeLoginProfile, status: ClaudeAuthStatus | null) => Promise<void>;
78
+ onClaudeProfileApiKey: (_key: SessionKey, profile: ClaudeLoginProfile, apiKey: string) => Promise<string>;
75
79
  };
76
80
  //# sourceMappingURL=claudeAccounts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"claudeAccounts.d.ts","sourceRoot":"","sources":["../../../../src/connectors/telegram/commands/claudeAccounts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claudeAccounts.d.ts","sourceRoot":"","sources":["../../../../src/connectors/telegram/commands/claudeAccounts.ts"],"names":[],"mappings":"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"}
@@ -32,12 +32,12 @@ const providerSnapshotLimit = 50;
32
32
  * auth-status sink the sign-in driver reports to.
33
33
  */
34
34
  function createClaudeAccounts(ports) {
35
- const { bot, command, getState, replyToThread, editThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, getDataDir, getInstanceTimezone, refreshClaudeUsage, readClaudeAuthStatus, startClaudeProfileLogin, applyClaudeProfile, applyClaudeProfileEverywhere, switchTopicByOperator, detectOpenCodeProviders, listOpenCodeModels, getTopicRun, disconnectOpenCodeProvider, startOpenCodeConnect, applyAutoSwitch, getHomeClaudeDir, } = ports;
35
+ const { bot, command, getState, replyToThread, editThreadMessage, deleteThreadMessage, authoriseContext, withThreadLocale, checkIsGeneral, getDataDir, getInstanceTimezone, refreshClaudeUsage, readClaudeAuthStatus, startClaudeProfileLogin, applyClaudeProfile, applyClaudeProfileEverywhere, switchTopicByOperator, detectOpenCodeProviders, listOpenCodeModels, getTopicRun, disconnectOpenCodeProvider, startOpenCodeConnect, applyAutoSwitch, getHomeClaudeDir, verifyAnthropicApiKey, } = ports;
36
36
  const answerWithWholeText = (0, callbackToast_1.createWholeTextAnswerer)(replyToThread);
37
37
  /**
38
- * Topics whose next plain message is an answer to «➕»: the name of a new Claude profile, or the id of the
39
- * OpenCode provider to connect. One capture per topic; armed by the buttons / `add`, disarmed by a slash
40
- * command or `/esc`.
38
+ * Topics whose next plain message is an answer to «➕»: the name of a new Claude profile (a subscription one, or one
39
+ * billed to the API), or the id of the OpenCode provider to connect. One capture per topic; armed by the buttons /
40
+ * `add`, disarmed by a slash command or `/esc`.
41
41
  */
42
42
  const awaitingProfileName = new Map();
43
43
  /** The OpenCode providers each screen MESSAGE listed — its disconnect buttons carry an index into this. */
@@ -91,6 +91,10 @@ function createClaudeAccounts(ports) {
91
91
  function checkIsKnownProfile(name) {
92
92
  return name === claudeProfiles_1.defaultClaudeProfileName || getState().getClaudeProfileRecord(name) !== undefined;
93
93
  }
94
+ /** How a known profile pays, as last recorded (`default` from its hidden record); `null` when never recorded. */
95
+ function readBilling(name) {
96
+ return getState().getClaudeProfileInfo(name)?.billing ?? null;
97
+ }
94
98
  /**
95
99
  * @description Re-read every added profile's auth status (and the default's) and persist what changed, so the
96
100
  * picker shows the CLI's truth, not the state of the last sign-in. One short `claude auth status` per profile,
@@ -120,6 +124,8 @@ function createClaudeAccounts(ports) {
120
124
  await getState().upsertClaudeProfileRecord(name, {
121
125
  // A removed `default` comes back by itself the moment it is signed in again.
122
126
  ...(name === claudeProfiles_1.defaultClaudeProfileName && status.loggedIn ? { isRemoved: undefined } : {}),
127
+ // The CLI's word on how the account pays wins over the kind it was added as, both ways.
128
+ ...(status.billing !== null ? { billing: status.billing } : {}),
123
129
  loggedIn: status.loggedIn,
124
130
  email: status.email ?? undefined,
125
131
  subscriptionType: status.subscriptionType ?? undefined,
@@ -127,19 +133,62 @@ function createClaudeAccounts(ports) {
127
133
  statusCheckedAt: new Date().toISOString(),
128
134
  });
129
135
  }
130
- /** The sign-in driver's report at the end of a profile sign-in: record it, then move live sessions onto it. */
136
+ /**
137
+ * The sign-in driver's report at the end of a profile sign-in: record it, then move live sessions onto it. A Console
138
+ * sign-in that created its key replaces an older pasted key of the same account (the latest sign-in wins); the record
139
+ * then takes the probe the sessions see — a failed Console sign-in over a working pasted key leaves the account
140
+ * signed in.
141
+ */
131
142
  async function onClaudeProfileAuthStatus(_key, profile, status) {
132
- await recordProfileStatus(profile.name, status);
143
+ let recorded = status;
144
+ if (profile.billing === 'api' && profile.configDir !== null) {
145
+ if (status?.loggedIn && status.apiKeySource === claudeProfiles_1.claudeConsoleLoginKeySource)
146
+ (0, claudeProfiles_1.removeClaudeProfileApiKey)(profile.configDir);
147
+ recorded = await readClaudeAuthStatus(profile.configDir) ?? status;
148
+ }
149
+ await recordProfileStatus(profile.name, recorded);
150
+ // A sign-in is the operator acting on the account: a used-up-credits mark ends with it.
151
+ if (recorded?.loggedIn)
152
+ getState().setClaudeProfileCreditExhausted(profile.name, null);
133
153
  // A topic may already have picked the profile before its sign-in: its sleeping session resumes under it by
134
154
  // itself, and a live one is re-spawned now that the account exists.
155
+ if (recorded?.loggedIn)
156
+ await applyClaudeProfileEverywhere();
157
+ }
158
+ /**
159
+ * @description An API key pasted into an API account's sign-in (the driver already deleted the message and stopped
160
+ * the Console login): asked of the API first — a key it refuses is not stored — then kept in the profile's own
161
+ * directory (`0600`, read by the `apiKeyHelper` of that profile's launches only), the account probed and recorded,
162
+ * and live topics moved onto it. Returns the line to post; the key itself is never logged or echoed.
163
+ */
164
+ async function onClaudeProfileApiKey(_key, profile, apiKey) {
165
+ if (profile.configDir === null || !checkIsKnownProfile(profile.name))
166
+ return (0, i18n_1.t)('account.apiKeyNoDirectory', { name: profile.name });
167
+ const check = await verifyAnthropicApiKey(apiKey);
168
+ if (check === 'invalid')
169
+ return (0, i18n_1.t)('account.apiKeyRejected', { name: profile.name });
170
+ try {
171
+ (0, claudeProfiles_1.createClaudeProfileLayout)({ dataDir: getDataDir(), name: profile.name });
172
+ (0, claudeProfiles_1.writeClaudeProfileApiKey)(profile.configDir, apiKey);
173
+ }
174
+ catch (e) {
175
+ console.error(`[account] storing the API key of ${profile.name} failed:`, e instanceof Error ? e.message : e);
176
+ return (0, i18n_1.t)('account.createFailed', { name: profile.name });
177
+ }
178
+ const status = await readClaudeAuthStatus(profile.configDir);
179
+ await getState().upsertClaudeProfileRecord(profile.name, { billing: 'api' });
180
+ await recordProfileStatus(profile.name, status);
181
+ getState().setClaudeProfileCreditExhausted(profile.name, null);
135
182
  if (status?.loggedIn)
136
183
  await applyClaudeProfileEverywhere();
184
+ console.log(`[account] ${profile.name}: an API key was stored (${check === 'valid' ? 'accepted by the API' : 'not verified'})`);
185
+ return (0, i18n_1.t)(check === 'valid' ? 'account.apiKeySaved' : 'account.apiKeySavedUnverified', { name: profile.name });
137
186
  }
138
187
  function formatRow(row) {
139
188
  const marker = row.isActive ? '✓ ' : '• ';
140
- const account = row.loggedIn === false || (row.loggedIn === undefined && row.email === null)
189
+ const account = row.loggedIn === false || (row.loggedIn === undefined && row.email === null && row.billing !== 'api')
141
190
  ? (0, i18n_1.t)('account.row.loggedOut')
142
- : `${row.email ?? '?'}, ${(0, i18n_1.t)(`account.kind.${row.kind}`, { plan: row.subscriptionType ?? '?' })}`;
191
+ : formatAccountDetail(row);
143
192
  const users = row.topicCount > 0 ? (0, i18n_1.t)('account.row.topics', { count: row.topicCount }) : '';
144
193
  return `${marker}${row.name} — ${account}${users}${formatUsageLines(row.name)}`;
145
194
  }
@@ -151,12 +200,21 @@ function createClaudeAccounts(ports) {
151
200
  const lines = (0, claudeProfileUsageLines_1.formatClaudeProfileUsageLines)(getState().getClaudeProfileInfo(name), getInstanceTimezone(), Date.now());
152
201
  return lines.map((line) => `\n${line}`).join('');
153
202
  }
203
+ /**
204
+ * What a signed-in account is: `ops@example.com, personal max`; billed to the API `ops@example.com, 💳 API · pay per
205
+ * use`, or `💳 API key · pay per use` for a pasted key (no email to show).
206
+ */
207
+ function formatAccountDetail(row) {
208
+ if (row.billing === 'api')
209
+ return row.email === null ? (0, i18n_1.t)('account.kind.apiKey') : `${row.email}, ${(0, i18n_1.t)('account.kind.api')}`;
210
+ return `${row.email ?? '?'}, ${(0, i18n_1.t)(`account.kind.${row.kind}`, { plan: row.subscriptionType ?? '?' })}`;
211
+ }
154
212
  function formatRowButton(row) {
155
213
  const marker = row.isActive ? '✓ ' : '';
156
- const detail = row.loggedIn === false || (row.loggedIn === undefined && row.email === null)
214
+ const detail = row.loggedIn === false || (row.loggedIn === undefined && row.email === null && row.billing !== 'api')
157
215
  ? (0, i18n_1.t)('account.row.loggedOut')
158
216
  : row.email ?? row.name;
159
- return `${marker}${row.name} · ${detail}`;
217
+ return `${marker}${row.name} · ${detail}${row.billing === 'api' ? ' 💳' : ''}`;
160
218
  }
161
219
  async function buildPickerRows(key, isGeneral) {
162
220
  // The usage poll runs beside the status probes; a snapshot younger than a minute is kept.
@@ -170,6 +228,7 @@ function createClaudeAccounts(ports) {
170
228
  loggedIn: defaultStatus?.loggedIn,
171
229
  email: defaultStatus?.email ?? null,
172
230
  subscriptionType: defaultStatus?.subscriptionType ?? null,
231
+ billing: defaultStatus?.billing ?? readBilling(claudeProfiles_1.defaultClaudeProfileName),
173
232
  },
174
233
  activeName: resolveActiveName(key, isGeneral),
175
234
  topicCounts,
@@ -236,6 +295,7 @@ function createClaudeAccounts(ports) {
236
295
  telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.addClaudeButton'), claudeAccountPickerPlan_1.accountAddCallback),
237
296
  telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.addProviderButton'), claudeAccountPickerPlan_1.accountAddProviderCallback),
238
297
  ]);
298
+ keyboardRows.push([telegraf_1.Markup.button.callback((0, i18n_1.t)('accounts.addClaudeApiButton'), claudeAccountPickerPlan_1.accountAddApiCallback)]);
239
299
  if (!isGeneral && getState().getClaudeProfileOverride(key) !== undefined) {
240
300
  keyboardRows.push([
241
301
  telegraf_1.Markup.button.callback((0, i18n_1.t)('account.followButton', { name: getState().getClaudeProfileGlobalDefault() }), claudeAccountPickerPlan_1.accountFollowDefaultCallback),
@@ -286,13 +346,19 @@ function createClaudeAccounts(ports) {
286
346
  if (status !== null && !status.loggedIn)
287
347
  return (0, i18n_1.t)('account.notLoggedIn', { name });
288
348
  }
349
+ // An account billed to the API says so whenever it is picked: from now on that topic (or every topic following General) costs money per request.
350
+ const meteredNote = readBilling(name) === 'api' ? `\n${(0, i18n_1.t)(isGeneral ? 'account.pickedApiGeneral' : 'account.pickedApi')}` : '';
289
351
  if (isGeneral) {
352
+ // The operator's pick is the act that ends a used-up-credits mark (in a topic the switch path clears it).
353
+ getState().setClaudeProfileCreditExhausted(name, null);
290
354
  await getState().setClaudeProfileGlobalDefault(name);
291
355
  await applyClaudeProfileEverywhere();
292
- return (0, i18n_1.t)('account.setGlobal', { name });
356
+ return `${(0, i18n_1.t)('account.setGlobal', { name })}${meteredNote}`;
293
357
  }
294
358
  const outcome = await switchTopicByOperator(key, { backend: 'claude', profile: name, model: null });
295
- return outcome.kind === 'asked' ? null : outcome.text;
359
+ if (outcome.kind === 'asked')
360
+ return null;
361
+ return outcome.kind === 'switched' ? `${outcome.text}${meteredNote}` : outcome.text;
296
362
  }
297
363
  /** A topic drops its own pick; its session follows the instance default from now on. */
298
364
  async function applyFollowDefault(key) {
@@ -304,9 +370,10 @@ function createClaudeAccounts(ports) {
304
370
  /**
305
371
  * @description Create a profile and start its sign-in: the directory with its shared links, the registry
306
372
  * record (flushed before the sign-in, so a crash never leaves a signed-in directory the picker cannot see),
307
- * then the relay posts the link. Returns the message to post BEFORE the link, or the refusal.
373
+ * then the relay posts the link. An API account (`billing: 'api'`) is recorded as such and signs in to the Claude
374
+ * Console (`--console`), or takes a pasted API key. Returns the message to post BEFORE the link, or the refusal.
308
375
  */
309
- async function addProfile(key, rawName) {
376
+ async function addProfile(key, rawName, billing = 'subscription') {
310
377
  const name = (0, claudeProfiles_1.normaliseClaudeProfileName)(rawName);
311
378
  if (!(0, claudeProfiles_1.checkIsValidClaudeProfileName)(name))
312
379
  return (0, i18n_1.t)('account.invalidName', { max: claudeProfiles_1.claudeProfileNameMaxLength });
@@ -320,9 +387,9 @@ function createClaudeAccounts(ports) {
320
387
  console.error(`[account] profile layout for ${name} failed:`, e instanceof Error ? e.message : e);
321
388
  return (0, i18n_1.t)('account.createFailed', { name });
322
389
  }
323
- await getState().upsertClaudeProfileRecord(name, { createdAt: new Date().toISOString(), loggedIn: false });
324
- await replyToThread(key, (0, i18n_1.t)('account.created', { name }));
325
- await startClaudeProfileLogin(key, { name, configDir });
390
+ await getState().upsertClaudeProfileRecord(name, { createdAt: new Date().toISOString(), loggedIn: false, ...(billing === 'api' ? { billing } : {}) });
391
+ await replyToThread(key, (0, i18n_1.t)(billing === 'api' ? 'account.createdApi' : 'account.created', { name }));
392
+ await startClaudeProfileLogin(key, { name, configDir, ...(billing === 'api' ? { billing } : {}) });
326
393
  return '';
327
394
  }
328
395
  /** Sign a known profile in (again); `default` is the plain `~/.claude` login. */
@@ -333,7 +400,9 @@ function createClaudeAccounts(ports) {
333
400
  // The directory may be gone (a hand-cleaned DATA_DIR): recreate the layout so the login has a home.
334
401
  (0, claudeProfiles_1.createClaudeProfileLayout)({ dataDir: getDataDir(), name });
335
402
  }
336
- await startClaudeProfileLogin(key, { name, configDir: resolveDir(name) });
403
+ // An API account signs in to the Claude Console again (or takes a pasted key), never the subscription.
404
+ const billing = readBilling(name);
405
+ await startClaudeProfileLogin(key, { name, configDir: resolveDir(name), ...(billing === 'api' ? { billing } : {}) });
337
406
  return '';
338
407
  }
339
408
  /**
@@ -418,19 +487,30 @@ function createClaudeAccounts(ports) {
418
487
  awaitingProfileName.delete((0, sessionKey_1.keyToString)(key));
419
488
  await startOpenCodeConnect(key, providerId);
420
489
  }
421
- /** The captured answer of «➕»: a Claude profile name, or an OpenCode provider id, by what was asked. */
422
- async function submitProfileName(key, text) {
423
- if (awaitingProfileName.get((0, sessionKey_1.keyToString)(key)) === 'providerId') {
490
+ /**
491
+ * The captured answer of «➕»: a Claude profile name (a subscription or an API account), or an OpenCode provider id,
492
+ * by what was asked. An API key pasted where the name was asked is a secret: deleted at once (`messageId`), never
493
+ * echoed, and the name is asked again — the key belongs to the sign-in that follows.
494
+ */
495
+ async function submitProfileName(key, text, messageId) {
496
+ const asked = awaitingProfileName.get((0, sessionKey_1.keyToString)(key));
497
+ if (asked === 'providerId') {
424
498
  await submitProviderId(key, text);
425
499
  return;
426
500
  }
501
+ if ((0, claudeProfiles_1.checkIsAnthropicSecretLike)(text)) {
502
+ if (messageId !== undefined)
503
+ await deleteThreadMessage(key, messageId);
504
+ await replyToThread(key, `${(0, i18n_1.t)('account.apiKeyAsName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
505
+ return;
506
+ }
427
507
  const name = (0, claudeProfiles_1.normaliseClaudeProfileName)(text);
428
508
  if (!(0, claudeProfiles_1.checkIsValidClaudeProfileName)(name)) {
429
509
  await replyToThread(key, `${(0, i18n_1.t)('account.invalidName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
430
510
  return;
431
511
  }
432
512
  awaitingProfileName.delete((0, sessionKey_1.keyToString)(key));
433
- const message = await addProfile(key, name);
513
+ const message = await addProfile(key, name, asked === 'claudeApiName' ? 'api' : 'subscription');
434
514
  if (message)
435
515
  await replyToThread(key, message);
436
516
  }
@@ -480,25 +560,38 @@ function createClaudeAccounts(ports) {
480
560
  }
481
561
  // ── the command ──
482
562
  function registerClaudeAccountCommands() {
483
- // `/accounts` (hidden alias `/account`) — bare: the screen. `add <name>` creates a profile and signs it in; `use <name>` switches
563
+ // `/accounts` (hidden alias `/account`) — bare: the screen. `add <name>` creates a profile and signs it in (`add <name> api`:
564
+ // an account billed to a Claude Console organization's API balance); `use <name>` switches
484
565
  // this topic (General: the instance default); `login <name>` signs a profile in again; `remove <name>`
485
566
  // deletes one. Not gated on a session: the pick is read at the next spawn, so it must be settable first.
486
- command(['account', 'accounts'], async (_ctx, key, parsed) => {
567
+ command(['account', 'accounts'], async (ctx, key, parsed) => {
568
+ // A key typed on the command line (`/accounts add console api sk-ant-…`) is a secret like a pasted one: the message
569
+ // is deleted, the key never echoed nor taken — it belongs to the sign-in that follows.
570
+ if ((0, claudeProfiles_1.checkIsAnthropicSecretLike)(parsed.argsText)) {
571
+ await deleteThreadMessage(key, ctx.message.message_id);
572
+ await replyToThread(key, (0, i18n_1.t)('account.apiKeyInCommand'));
573
+ return;
574
+ }
487
575
  const isGeneral = checkIsGeneral(key);
488
576
  const [verb = '', ...rest] = parsed.args;
489
577
  const name = (0, claudeProfiles_1.normaliseClaudeProfileName)(rest.join(' '));
578
+ // `add <name> api`: the last word names the billing; the name is what precedes it.
579
+ const isApiAdd = verb.toLowerCase() === 'add' && rest.length >= 2 && rest.at(-1)?.toLowerCase() === 'api';
490
580
  switch (verb.toLowerCase()) {
491
581
  case '': {
492
582
  await postPicker(key, isGeneral);
493
583
  return;
494
584
  }
495
585
  case 'add': {
496
- if (!name) {
497
- awaitingProfileName.set((0, sessionKey_1.keyToString)(key), 'claudeName');
498
- await replyToThread(key, `${(0, i18n_1.t)('account.askName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
586
+ if (!name || name === 'api') {
587
+ const asked = name === 'api' ? 'claudeApiName' : 'claudeName';
588
+ awaitingProfileName.set((0, sessionKey_1.keyToString)(key), asked);
589
+ await replyToThread(key, `${(0, i18n_1.t)(asked === 'claudeApiName' ? 'account.askNameApi' : 'account.askName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
499
590
  return;
500
591
  }
501
- const message = await addProfile(key, name);
592
+ const message = isApiAdd
593
+ ? await addProfile(key, (0, claudeProfiles_1.normaliseClaudeProfileName)(rest.slice(0, -1).join(' ')), 'api')
594
+ : await addProfile(key, name);
502
595
  if (message)
503
596
  await replyToThread(key, message);
504
597
  return;
@@ -581,6 +674,11 @@ function createClaudeAccounts(ports) {
581
674
  await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
582
675
  await replyToThread(key, `${(0, i18n_1.t)('account.askName', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
583
676
  }));
677
+ bot.action(claudeAccountPickerPlan_1.accountAddApiCallback, (ctx) => handleTap(ctx, async (key) => {
678
+ awaitingProfileName.set((0, sessionKey_1.keyToString)(key), 'claudeApiName');
679
+ await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
680
+ await replyToThread(key, `${(0, i18n_1.t)('account.askNameApi', { max: claudeProfiles_1.claudeProfileNameMaxLength })}\n${(0, i18n_1.t)('login.esc_hint')}`);
681
+ }));
584
682
  bot.action(claudeAccountPickerPlan_1.accountAddProviderCallback, (ctx) => handleTap(ctx, async (key) => {
585
683
  awaitingProfileName.set((0, sessionKey_1.keyToString)(key), 'providerId');
586
684
  await (0, callbackToast_1.answerCallbackQuerySafely)(ctx);
@@ -740,6 +838,7 @@ function createClaudeAccounts(ports) {
740
838
  submitProfileName,
741
839
  pickClaudeAccount: applyPick,
742
840
  onClaudeProfileAuthStatus,
841
+ onClaudeProfileApiKey,
743
842
  };
744
843
  }
745
844
  //# sourceMappingURL=claudeAccounts.js.map