@within-7/minto 0.3.6 → 0.3.10

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 (238) hide show
  1. package/{cli.js → cli.cjs} +25 -23
  2. package/dist/commands/agents/AgentsCommand.js +459 -655
  3. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  4. package/dist/commands/agents/types.js +1 -0
  5. package/dist/commands/agents/types.js.map +2 -2
  6. package/dist/commands/agents/utils/fileOperations.js +96 -36
  7. package/dist/commands/agents/utils/fileOperations.js.map +3 -3
  8. package/dist/commands/agents/utils/index.js +3 -1
  9. package/dist/commands/agents/utils/index.js.map +2 -2
  10. package/dist/commands/context.js +54 -23
  11. package/dist/commands/context.js.map +2 -2
  12. package/dist/commands/export.js +673 -93
  13. package/dist/commands/export.js.map +2 -2
  14. package/dist/commands/language.js +110 -0
  15. package/dist/commands/language.js.map +7 -0
  16. package/dist/commands/mcp-interactive.js +419 -217
  17. package/dist/commands/mcp-interactive.js.map +2 -2
  18. package/dist/commands/model.js +415 -66
  19. package/dist/commands/model.js.map +2 -2
  20. package/dist/commands/new.js +56 -0
  21. package/dist/commands/new.js.map +7 -0
  22. package/dist/commands/permissions.js +75 -49
  23. package/dist/commands/permissions.js.map +2 -2
  24. package/dist/commands/plugin.js +882 -185
  25. package/dist/commands/plugin.js.map +3 -3
  26. package/dist/commands/resume.js +251 -16
  27. package/dist/commands/resume.js.map +2 -2
  28. package/dist/commands/sandbox.js +168 -70
  29. package/dist/commands/sandbox.js.map +2 -2
  30. package/dist/commands/sessions.js +224 -0
  31. package/dist/commands/sessions.js.map +7 -0
  32. package/dist/commands/setup.js +596 -109
  33. package/dist/commands/setup.js.map +2 -2
  34. package/dist/commands/stats.js +292 -0
  35. package/dist/commands/stats.js.map +7 -0
  36. package/dist/commands/status.js +75 -7
  37. package/dist/commands/status.js.map +2 -2
  38. package/dist/commands/undo.js +154 -180
  39. package/dist/commands/undo.js.map +2 -2
  40. package/dist/commands.js +6 -0
  41. package/dist/commands.js.map +2 -2
  42. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  43. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  44. package/dist/components/Config.js +9 -8
  45. package/dist/components/Config.js.map +2 -2
  46. package/dist/components/HeaderBar.js +2 -1
  47. package/dist/components/HeaderBar.js.map +2 -2
  48. package/dist/components/Help.js +166 -32
  49. package/dist/components/Help.js.map +2 -2
  50. package/dist/components/HotkeyHelpPanel.js +46 -44
  51. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  52. package/dist/components/InfoPanel/InfoPanel.js +123 -0
  53. package/dist/components/InfoPanel/InfoPanel.js.map +7 -0
  54. package/dist/components/InfoPanel/index.js +5 -0
  55. package/dist/components/InfoPanel/index.js.map +7 -0
  56. package/dist/components/InfoPanel/types.js +1 -0
  57. package/dist/components/InfoPanel/types.js.map +7 -0
  58. package/dist/components/Logo.js +5 -2
  59. package/dist/components/Logo.js.map +2 -2
  60. package/dist/components/MCPServerApprovalDialog.js +6 -5
  61. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  62. package/dist/components/MCPServerMultiselectDialog.js +5 -4
  63. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  64. package/dist/components/MessageSelector.js +4 -3
  65. package/dist/components/MessageSelector.js.map +2 -2
  66. package/dist/components/ModelConfig.js +13 -12
  67. package/dist/components/ModelConfig.js.map +2 -2
  68. package/dist/components/ModelListManager.js +4 -3
  69. package/dist/components/ModelListManager.js.map +2 -2
  70. package/dist/components/ModelSelector/BrandTextInput.js +43 -0
  71. package/dist/components/ModelSelector/BrandTextInput.js.map +7 -0
  72. package/dist/components/ModelSelector/ModelSelector.js +419 -501
  73. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  74. package/dist/components/ModelSelector/WizardContainer.js +45 -0
  75. package/dist/components/ModelSelector/WizardContainer.js.map +7 -0
  76. package/dist/components/ModelSelector/index.js +1 -3
  77. package/dist/components/ModelSelector/index.js.map +2 -2
  78. package/dist/components/PromptInput.js +77 -44
  79. package/dist/components/PromptInput.js.map +2 -2
  80. package/dist/components/SensitiveFileWarning.js +12 -8
  81. package/dist/components/SensitiveFileWarning.js.map +2 -2
  82. package/dist/components/SimpleSelector/SimpleSelector.js +154 -0
  83. package/dist/components/SimpleSelector/SimpleSelector.js.map +7 -0
  84. package/dist/components/SimpleSelector/index.js +5 -0
  85. package/dist/components/SimpleSelector/index.js.map +7 -0
  86. package/dist/components/SimpleSelector/types.js +1 -0
  87. package/dist/components/SimpleSelector/types.js.map +7 -0
  88. package/dist/components/StatusOverlayContent.js +21 -0
  89. package/dist/components/StatusOverlayContent.js.map +7 -0
  90. package/dist/components/TabbedListView/ScrollableList.js +117 -0
  91. package/dist/components/TabbedListView/ScrollableList.js.map +7 -0
  92. package/dist/components/TabbedListView/SearchInput.js +23 -0
  93. package/dist/components/TabbedListView/SearchInput.js.map +7 -0
  94. package/dist/components/TabbedListView/TabBar.js +20 -0
  95. package/dist/components/TabbedListView/TabBar.js.map +7 -0
  96. package/dist/components/TabbedListView/TabbedListView.js +246 -0
  97. package/dist/components/TabbedListView/TabbedListView.js.map +7 -0
  98. package/dist/components/TabbedListView/index.js +11 -0
  99. package/dist/components/TabbedListView/index.js.map +7 -0
  100. package/dist/components/TabbedListView/types.js +1 -0
  101. package/dist/components/TabbedListView/types.js.map +7 -0
  102. package/dist/components/TodoChangeBlock.js +6 -5
  103. package/dist/components/TodoChangeBlock.js.map +3 -3
  104. package/dist/components/TodoPanel.js +6 -3
  105. package/dist/components/TodoPanel.js.map +3 -3
  106. package/dist/components/TrustDialog.js +6 -5
  107. package/dist/components/TrustDialog.js.map +2 -2
  108. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js +2 -1
  109. package/dist/components/messages/UserToolResultMessage/UserToolCanceledMessage.js.map +2 -2
  110. package/dist/constants/macros.js +1 -1
  111. package/dist/constants/macros.js.map +1 -1
  112. package/dist/constants/product.js +2 -2
  113. package/dist/constants/product.js.map +1 -1
  114. package/dist/constants/prompts.js +17 -0
  115. package/dist/constants/prompts.js.map +2 -2
  116. package/dist/constants/toolInputExamples.js +5 -1
  117. package/dist/constants/toolInputExamples.js.map +2 -2
  118. package/dist/core/backupHook.js +29 -0
  119. package/dist/core/backupHook.js.map +7 -0
  120. package/dist/core/config/defaults.js +8 -2
  121. package/dist/core/config/defaults.js.map +2 -2
  122. package/dist/core/config/schema.js +14 -2
  123. package/dist/core/config/schema.js.map +2 -2
  124. package/dist/core/costTracker.js +0 -16
  125. package/dist/core/costTracker.js.map +2 -2
  126. package/dist/core/tokenStatsManager.js +5 -0
  127. package/dist/core/tokenStatsManager.js.map +2 -2
  128. package/dist/cost-tracker.js +0 -16
  129. package/dist/cost-tracker.js.map +2 -2
  130. package/dist/entrypoints/bootstrap.js +56 -0
  131. package/dist/entrypoints/bootstrap.js.map +7 -0
  132. package/dist/entrypoints/cli.js +164 -23
  133. package/dist/entrypoints/cli.js.map +3 -3
  134. package/dist/history.js +75 -15
  135. package/dist/history.js.map +2 -2
  136. package/dist/i18n/index.js +2 -2
  137. package/dist/i18n/index.js.map +2 -2
  138. package/dist/i18n/locales/en.js +582 -1
  139. package/dist/i18n/locales/en.js.map +2 -2
  140. package/dist/i18n/locales/zh-CN.js +582 -1
  141. package/dist/i18n/locales/zh-CN.js.map +2 -2
  142. package/dist/i18n/types.js.map +1 -1
  143. package/dist/index.js +1 -1
  144. package/dist/index.js.map +2 -2
  145. package/dist/messages.js +11 -0
  146. package/dist/messages.js.map +2 -2
  147. package/dist/permissions.js.map +2 -2
  148. package/dist/query.js +9 -0
  149. package/dist/query.js.map +2 -2
  150. package/dist/screens/REPL.js +45 -7
  151. package/dist/screens/REPL.js.map +2 -2
  152. package/dist/services/customCommands.js +44 -16
  153. package/dist/services/customCommands.js.map +2 -2
  154. package/dist/services/plugins/lspServers.js +1 -1
  155. package/dist/services/plugins/lspServers.js.map +2 -2
  156. package/dist/services/plugins/pluginRuntime.js +2 -1
  157. package/dist/services/plugins/pluginRuntime.js.map +2 -2
  158. package/dist/services/plugins/pluginValidation.js +10 -3
  159. package/dist/services/plugins/pluginValidation.js.map +2 -2
  160. package/dist/services/plugins/skillMarketplace.js +16 -8
  161. package/dist/services/plugins/skillMarketplace.js.map +2 -2
  162. package/dist/services/systemReminder.js +17 -6
  163. package/dist/services/systemReminder.js.map +2 -2
  164. package/dist/tools/FileEditTool/FileEditTool.js +7 -0
  165. package/dist/tools/FileEditTool/FileEditTool.js.map +2 -2
  166. package/dist/tools/FileWriteTool/FileWriteTool.js +7 -0
  167. package/dist/tools/FileWriteTool/FileWriteTool.js.map +2 -2
  168. package/dist/tools/MultiEditTool/MultiEditTool.js +7 -0
  169. package/dist/tools/MultiEditTool/MultiEditTool.js.map +2 -2
  170. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -0
  171. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  172. package/dist/tools/TaskTool/TaskTool.js +179 -1
  173. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  174. package/dist/tools/TodoWriteTool/prompt.js +21 -0
  175. package/dist/tools/TodoWriteTool/prompt.js.map +2 -2
  176. package/dist/tools/URLFetcherTool/prompt.js +14 -9
  177. package/dist/tools/URLFetcherTool/prompt.js.map +2 -2
  178. package/dist/tools/WebSearchTool/prompt.js +12 -6
  179. package/dist/tools/WebSearchTool/prompt.js.map +2 -2
  180. package/dist/types/PermissionMode.js +30 -1
  181. package/dist/types/PermissionMode.js.map +2 -2
  182. package/dist/types/plugin.js +2 -4
  183. package/dist/types/plugin.js.map +2 -2
  184. package/dist/utils/agentHookExecutor.js +103 -0
  185. package/dist/utils/agentHookExecutor.js.map +7 -0
  186. package/dist/utils/agentLoader.js +272 -32
  187. package/dist/utils/agentLoader.js.map +2 -2
  188. package/dist/utils/agentMemory.js +134 -0
  189. package/dist/utils/agentMemory.js.map +7 -0
  190. package/dist/utils/claudeCodeSync.js +439 -0
  191. package/dist/utils/claudeCodeSync.js.map +7 -0
  192. package/dist/utils/config.js +52 -24
  193. package/dist/utils/config.js.map +2 -2
  194. package/dist/utils/configPaths.js +199 -0
  195. package/dist/utils/configPaths.js.map +7 -0
  196. package/dist/utils/execFileNoThrow.js +2 -1
  197. package/dist/utils/execFileNoThrow.js.map +2 -2
  198. package/dist/utils/historyManager.js +234 -0
  199. package/dist/utils/historyManager.js.map +7 -0
  200. package/dist/utils/marketplaceManager.js +80 -43
  201. package/dist/utils/marketplaceManager.js.map +2 -2
  202. package/dist/utils/messages.js +13 -8
  203. package/dist/utils/messages.js.map +2 -2
  204. package/dist/utils/migration/index.js +37 -0
  205. package/dist/utils/migration/index.js.map +7 -0
  206. package/dist/utils/migration/migrateHistory.js +273 -0
  207. package/dist/utils/migration/migrateHistory.js.map +7 -0
  208. package/dist/utils/migration/migrateTodos.js +323 -0
  209. package/dist/utils/migration/migrateTodos.js.map +7 -0
  210. package/dist/utils/pasteCache.js +309 -0
  211. package/dist/utils/pasteCache.js.map +7 -0
  212. package/dist/utils/pluginInstaller.js +34 -24
  213. package/dist/utils/pluginInstaller.js.map +2 -2
  214. package/dist/utils/pluginLoader.js +54 -28
  215. package/dist/utils/pluginLoader.js.map +2 -2
  216. package/dist/utils/repoFetcher.js +110 -0
  217. package/dist/utils/repoFetcher.js.map +7 -0
  218. package/dist/utils/sessionIndex.js +192 -0
  219. package/dist/utils/sessionIndex.js.map +7 -0
  220. package/dist/utils/sessionTracker.js +170 -0
  221. package/dist/utils/sessionTracker.js.map +7 -0
  222. package/dist/utils/skillLoader.js +103 -5
  223. package/dist/utils/skillLoader.js.map +2 -2
  224. package/dist/utils/stats.js +417 -0
  225. package/dist/utils/stats.js.map +7 -0
  226. package/dist/utils/stringSubstitution.js +106 -0
  227. package/dist/utils/stringSubstitution.js.map +7 -0
  228. package/dist/utils/teamConfig.js +156 -14
  229. package/dist/utils/teamConfig.js.map +2 -2
  230. package/dist/utils/terminal.js +1 -1
  231. package/dist/utils/terminal.js.map +2 -2
  232. package/dist/utils/todoStorage.js +51 -19
  233. package/dist/utils/todoStorage.js.map +2 -2
  234. package/dist/utils/tooling/safeRender.js.map +2 -2
  235. package/dist/version.js +2 -2
  236. package/dist/version.js.map +1 -1
  237. package/package.json +71 -28
  238. package/scripts/{postinstall.js → postinstall.cjs} +1 -1
@@ -14,7 +14,8 @@ const DEFAULT_MODEL_POINTERS = {
14
14
  main: "",
15
15
  task: "",
16
16
  reasoning: "",
17
- quick: ""
17
+ quick: "",
18
+ compact: ""
18
19
  };
19
20
  const DEFAULT_GLOBAL_CONFIG = {
20
21
  numStartups: 0,
@@ -33,6 +34,7 @@ const DEFAULT_GLOBAL_CONFIG = {
33
34
  lastDismissedUpdateVersion: void 0,
34
35
  compressionMode: "business",
35
36
  thinking: false,
37
+ backupEnabled: true,
36
38
  configVersion: 1
37
39
  // Current config version
38
40
  };
@@ -56,7 +58,11 @@ const GLOBAL_CONFIG_KEYS = [
56
58
  "preferredNotifChannel",
57
59
  "shiftEnterKeyBindingInstalled",
58
60
  "maxTokens",
59
- "compressionMode"
61
+ "compressionMode",
62
+ "language",
63
+ "safetyMode",
64
+ "autoSyncClaudeCode",
65
+ "backupEnabled"
60
66
  ];
61
67
  const PROJECT_CONFIG_KEYS = [
62
68
  "dontCrawlDirectory",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/config/defaults.ts"],
4
- "sourcesContent": ["/**\n * Configuration Default Values\n *\n * Centralized default values for all configuration types.\n */\n\nimport type { GlobalConfig, ProjectConfig, ModelPointers } from './schema'\n\n/**\n * Default project configuration\n */\nexport const DEFAULT_PROJECT_CONFIG: ProjectConfig = {\n allowedTools: [],\n context: {},\n history: [],\n dontCrawlDirectory: false,\n enableArchitectTool: false,\n mcpContextUris: [],\n mcpServers: {},\n approvedMcprcServers: [],\n rejectedMcprcServers: [],\n hasTrustDialogAccepted: false,\n}\n\n/**\n * Default model pointers\n */\nexport const DEFAULT_MODEL_POINTERS: ModelPointers = {\n main: '',\n task: '',\n reasoning: '',\n quick: '',\n}\n\n/**\n * Default global configuration\n */\nexport const DEFAULT_GLOBAL_CONFIG: GlobalConfig = {\n numStartups: 0,\n autoUpdaterStatus: 'not_configured',\n theme: 'dark',\n preferredNotifChannel: 'iterm2',\n verbose: false,\n primaryProvider: 'anthropic',\n customApiKeyResponses: {\n approved: [],\n rejected: [],\n },\n stream: true,\n modelProfiles: [],\n modelPointers: DEFAULT_MODEL_POINTERS,\n lastDismissedUpdateVersion: undefined,\n compressionMode: 'business',\n thinking: false,\n configVersion: 1, // Current config version\n}\n\n/**\n * Get default project config, optionally adjusted for home directory\n */\nexport function defaultConfigForProject(\n projectPath: string,\n homeDir: string,\n): ProjectConfig {\n const config = { ...DEFAULT_PROJECT_CONFIG }\n if (projectPath === homeDir) {\n config.dontCrawlDirectory = true\n }\n return config\n}\n\n/**\n * Current configuration version\n * Increment this when making breaking changes to config structure\n */\nexport const CURRENT_CONFIG_VERSION = 1\n\n/**\n * Modifiable global config keys (exposed to CLI)\n */\nexport const GLOBAL_CONFIG_KEYS = [\n 'autoUpdaterStatus',\n 'theme',\n 'hasCompletedOnboarding',\n 'lastOnboardingVersion',\n 'lastReleaseNotesSeen',\n 'verbose',\n 'customApiKeyResponses',\n 'primaryProvider',\n 'preferredNotifChannel',\n 'shiftEnterKeyBindingInstalled',\n 'maxTokens',\n 'compressionMode',\n] as const\n\nexport type GlobalConfigKey = (typeof GLOBAL_CONFIG_KEYS)[number]\n\n/**\n * Modifiable project config keys (exposed to CLI)\n */\nexport const PROJECT_CONFIG_KEYS = [\n 'dontCrawlDirectory',\n 'enableArchitectTool',\n 'hasTrustDialogAccepted',\n 'hasCompletedProjectOnboarding',\n] as const\n\nexport type ProjectConfigKey = (typeof PROJECT_CONFIG_KEYS)[number]\n\n/**\n * Type guards for config keys\n */\nexport function isGlobalConfigKey(key: string): key is GlobalConfigKey {\n return GLOBAL_CONFIG_KEYS.includes(key as GlobalConfigKey)\n}\n\nexport function isProjectConfigKey(key: string): key is ProjectConfigKey {\n return PROJECT_CONFIG_KEYS.includes(key as ProjectConfigKey)\n}\n"],
5
- "mappings": "AAWO,MAAM,yBAAwC;AAAA,EACnD,cAAc,CAAC;AAAA,EACf,SAAS,CAAC;AAAA,EACV,SAAS,CAAC;AAAA,EACV,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,gBAAgB,CAAC;AAAA,EACjB,YAAY,CAAC;AAAA,EACb,sBAAsB,CAAC;AAAA,EACvB,sBAAsB,CAAC;AAAA,EACvB,wBAAwB;AAC1B;AAKO,MAAM,yBAAwC;AAAA,EACnD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AACT;AAKO,MAAM,wBAAsC;AAAA,EACjD,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,OAAO;AAAA,EACP,uBAAuB;AAAA,EACvB,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,uBAAuB;AAAA,IACrB,UAAU,CAAC;AAAA,IACX,UAAU,CAAC;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,EACR,eAAe,CAAC;AAAA,EAChB,eAAe;AAAA,EACf,4BAA4B;AAAA,EAC5B,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,eAAe;AAAA;AACjB;AAKO,SAAS,wBACd,aACA,SACe;AACf,QAAM,SAAS,EAAE,GAAG,uBAAuB;AAC3C,MAAI,gBAAgB,SAAS;AAC3B,WAAO,qBAAqB;AAAA,EAC9B;AACA,SAAO;AACT;AAMO,MAAM,yBAAyB;AAK/B,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,kBAAkB,KAAqC;AACrE,SAAO,mBAAmB,SAAS,GAAsB;AAC3D;AAEO,SAAS,mBAAmB,KAAsC;AACvE,SAAO,oBAAoB,SAAS,GAAuB;AAC7D;",
4
+ "sourcesContent": ["/**\n * Configuration Default Values\n *\n * Centralized default values for all configuration types.\n */\n\nimport type { GlobalConfig, ProjectConfig, ModelPointers } from './schema'\n\n/**\n * Default project configuration\n */\nexport const DEFAULT_PROJECT_CONFIG: ProjectConfig = {\n allowedTools: [],\n context: {},\n history: [],\n dontCrawlDirectory: false,\n enableArchitectTool: false,\n mcpContextUris: [],\n mcpServers: {},\n approvedMcprcServers: [],\n rejectedMcprcServers: [],\n hasTrustDialogAccepted: false,\n}\n\n/**\n * Default model pointers\n */\nexport const DEFAULT_MODEL_POINTERS: ModelPointers = {\n main: '',\n task: '',\n reasoning: '',\n quick: '',\n compact: '',\n}\n\n/**\n * Default global configuration\n */\nexport const DEFAULT_GLOBAL_CONFIG: GlobalConfig = {\n numStartups: 0,\n autoUpdaterStatus: 'not_configured',\n theme: 'dark',\n preferredNotifChannel: 'iterm2',\n verbose: false,\n primaryProvider: 'anthropic',\n customApiKeyResponses: {\n approved: [],\n rejected: [],\n },\n stream: true,\n modelProfiles: [],\n modelPointers: DEFAULT_MODEL_POINTERS,\n lastDismissedUpdateVersion: undefined,\n compressionMode: 'business',\n thinking: false,\n backupEnabled: true,\n configVersion: 1, // Current config version\n}\n\n/**\n * Get default project config, optionally adjusted for home directory\n */\nexport function defaultConfigForProject(\n projectPath: string,\n homeDir: string,\n): ProjectConfig {\n const config = { ...DEFAULT_PROJECT_CONFIG }\n if (projectPath === homeDir) {\n config.dontCrawlDirectory = true\n }\n return config\n}\n\n/**\n * Current configuration version\n * Increment this when making breaking changes to config structure\n */\nexport const CURRENT_CONFIG_VERSION = 1\n\n/**\n * Modifiable global config keys (exposed to CLI)\n */\nexport const GLOBAL_CONFIG_KEYS = [\n 'autoUpdaterStatus',\n 'theme',\n 'hasCompletedOnboarding',\n 'lastOnboardingVersion',\n 'lastReleaseNotesSeen',\n 'verbose',\n 'customApiKeyResponses',\n 'primaryProvider',\n 'preferredNotifChannel',\n 'shiftEnterKeyBindingInstalled',\n 'maxTokens',\n 'compressionMode',\n 'language',\n 'safetyMode',\n 'autoSyncClaudeCode',\n 'backupEnabled',\n] as const\n\nexport type GlobalConfigKey = (typeof GLOBAL_CONFIG_KEYS)[number]\n\n/**\n * Modifiable project config keys (exposed to CLI)\n */\nexport const PROJECT_CONFIG_KEYS = [\n 'dontCrawlDirectory',\n 'enableArchitectTool',\n 'hasTrustDialogAccepted',\n 'hasCompletedProjectOnboarding',\n] as const\n\nexport type ProjectConfigKey = (typeof PROJECT_CONFIG_KEYS)[number]\n\n/**\n * Type guards for config keys\n */\nexport function isGlobalConfigKey(key: string): key is GlobalConfigKey {\n return GLOBAL_CONFIG_KEYS.includes(key as GlobalConfigKey)\n}\n\nexport function isProjectConfigKey(key: string): key is ProjectConfigKey {\n return PROJECT_CONFIG_KEYS.includes(key as ProjectConfigKey)\n}\n"],
5
+ "mappings": "AAWO,MAAM,yBAAwC;AAAA,EACnD,cAAc,CAAC;AAAA,EACf,SAAS,CAAC;AAAA,EACV,SAAS,CAAC;AAAA,EACV,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,gBAAgB,CAAC;AAAA,EACjB,YAAY,CAAC;AAAA,EACb,sBAAsB,CAAC;AAAA,EACvB,sBAAsB,CAAC;AAAA,EACvB,wBAAwB;AAC1B;AAKO,MAAM,yBAAwC;AAAA,EACnD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,WAAW;AAAA,EACX,OAAO;AAAA,EACP,SAAS;AACX;AAKO,MAAM,wBAAsC;AAAA,EACjD,aAAa;AAAA,EACb,mBAAmB;AAAA,EACnB,OAAO;AAAA,EACP,uBAAuB;AAAA,EACvB,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,uBAAuB;AAAA,IACrB,UAAU,CAAC;AAAA,IACX,UAAU,CAAC;AAAA,EACb;AAAA,EACA,QAAQ;AAAA,EACR,eAAe,CAAC;AAAA,EAChB,eAAe;AAAA,EACf,4BAA4B;AAAA,EAC5B,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,eAAe;AAAA,EACf,eAAe;AAAA;AACjB;AAKO,SAAS,wBACd,aACA,SACe;AACf,QAAM,SAAS,EAAE,GAAG,uBAAuB;AAC3C,MAAI,gBAAgB,SAAS;AAC3B,WAAO,qBAAqB;AAAA,EAC9B;AACA,SAAO;AACT;AAMO,MAAM,yBAAyB;AAK/B,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,MAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,kBAAkB,KAAqC;AACrE,SAAO,mBAAmB,SAAS,GAAsB;AAC3D;AAEO,SAAS,mBAAmB,KAAsC;AACvE,SAAO,oBAAoB,SAAS,GAAuB;AAC7D;",
6
6
  "names": []
7
7
  }
@@ -49,6 +49,8 @@ const ProviderTypeSchema = z.enum([
49
49
  "custom-openai"
50
50
  ]);
51
51
  const CompressionModeSchema = z.enum(["business", "code"]);
52
+ const UILanguageSchema = z.enum(["en", "zh-CN"]);
53
+ const SafetyModeSchema = z.enum(["yolo", "smart", "strict", "free"]);
52
54
  const ReasoningEffortSchema = z.enum([
53
55
  "minimal",
54
56
  "low",
@@ -75,13 +77,15 @@ const ModelPointerTypeSchema = z.enum([
75
77
  "main",
76
78
  "task",
77
79
  "reasoning",
78
- "quick"
80
+ "quick",
81
+ "compact"
79
82
  ]);
80
83
  const ModelPointersSchema = z.object({
81
84
  main: z.string(),
82
85
  task: z.string(),
83
86
  reasoning: z.string(),
84
- quick: z.string()
87
+ quick: z.string(),
88
+ compact: z.string()
85
89
  });
86
90
  const AccountInfoSchema = z.object({
87
91
  accountUuid: z.string(),
@@ -138,6 +142,12 @@ const GlobalConfigSchema = z.object({
138
142
  lastDismissedUpdateVersion: z.string().optional(),
139
143
  compressionMode: CompressionModeSchema.optional(),
140
144
  thinking: z.boolean().optional(),
145
+ language: UILanguageSchema.optional(),
146
+ safetyMode: SafetyModeSchema.optional(),
147
+ // Claude Code sync
148
+ autoSyncClaudeCode: z.boolean().optional(),
149
+ // File backup for /undo
150
+ backupEnabled: z.boolean().optional(),
141
151
  // Config version for migrations
142
152
  configVersion: z.number().optional()
143
153
  });
@@ -170,6 +180,8 @@ export {
170
180
  ProjectConfigSchema,
171
181
  ProviderTypeSchema,
172
182
  ReasoningEffortSchema,
183
+ SafetyModeSchema,
184
+ UILanguageSchema,
173
185
  safeValidateGlobalConfig,
174
186
  safeValidateProjectConfig,
175
187
  validateGlobalConfig,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/config/schema.ts"],
4
- "sourcesContent": ["/**\n * Configuration Schema Definitions\n *\n * Defines all configuration types for Minto with Zod validation.\n */\n\nimport { z } from 'zod'\n\n// MCP Server Configurations\nexport const McpStdioServerConfigSchema = z.object({\n type: z.literal('stdio').optional().default('stdio'),\n command: z.string(),\n args: z.array(z.string()),\n env: z.record(z.string()).optional(),\n enabled: z.boolean().optional(),\n})\n\nexport const McpSSEServerConfigSchema = z.object({\n type: z.literal('sse'),\n url: z.string(),\n enabled: z.boolean().optional(),\n})\n\nexport const McpServerConfigSchema = z.union([\n McpStdioServerConfigSchema,\n McpSSEServerConfigSchema,\n])\n\n// Auto Updater Status\nexport const AutoUpdaterStatusSchema = z.enum([\n 'disabled',\n 'enabled',\n 'no_permissions',\n 'not_configured',\n])\n\n// Notification Channel\nexport const NotificationChannelSchema = z.enum([\n 'iterm2',\n 'terminal_bell',\n 'iterm2_with_bell',\n 'notifications_disabled',\n])\n\n// Provider Types\nexport const ProviderTypeSchema = z.enum([\n 'anthropic',\n 'openai',\n 'mistral',\n 'deepseek',\n 'kimi',\n 'qwen',\n 'glm',\n 'minimax',\n 'baidu-qianfan',\n 'siliconflow',\n 'bigdream',\n 'opendev',\n 'xai',\n 'groq',\n 'gemini',\n 'ollama',\n 'azure',\n 'custom',\n 'custom-openai',\n])\n\n// Compression Mode\nexport const CompressionModeSchema = z.enum(['business', 'code'])\n\n// Reasoning Effort\nexport const ReasoningEffortSchema = z.enum([\n 'minimal',\n 'low',\n 'medium',\n 'high',\n])\n\n// Model Profile\nexport const ModelProfileSchema = z.object({\n name: z.string(),\n provider: ProviderTypeSchema,\n modelName: z.string(),\n baseURL: z.string().optional(),\n apiKey: z.string(),\n maxTokens: z.number(),\n contextLength: z.number(),\n reasoningEffort: ReasoningEffortSchema.optional(),\n isActive: z.boolean(),\n createdAt: z.number(),\n lastUsed: z.number().optional(),\n isGPT5: z.boolean().optional(),\n validationStatus: z\n .enum(['valid', 'needs_repair', 'auto_repaired'])\n .optional(),\n lastValidation: z.number().optional(),\n})\n\n// Model Pointer Type\nexport const ModelPointerTypeSchema = z.enum([\n 'main',\n 'task',\n 'reasoning',\n 'quick',\n])\n\n// Model Pointers\nexport const ModelPointersSchema = z.object({\n main: z.string(),\n task: z.string(),\n reasoning: z.string(),\n quick: z.string(),\n})\n\n// Account Info\nexport const AccountInfoSchema = z.object({\n accountUuid: z.string(),\n emailAddress: z.string(),\n organizationUuid: z.string().optional(),\n})\n\n// Project Configuration\nexport const ProjectConfigSchema = z.object({\n allowedTools: z.array(z.string()),\n context: z.record(z.string()),\n contextFiles: z.array(z.string()).optional(),\n history: z.array(z.string()),\n dontCrawlDirectory: z.boolean().optional(),\n enableArchitectTool: z.boolean().optional(),\n mcpContextUris: z.array(z.string()),\n mcpServers: z.record(McpServerConfigSchema).optional(),\n approvedMcprcServers: z.array(z.string()).optional(),\n rejectedMcprcServers: z.array(z.string()).optional(),\n lastAPIDuration: z.number().optional(),\n lastCost: z.number().optional(),\n lastDuration: z.number().optional(),\n lastSessionId: z.string().optional(),\n exampleFiles: z.array(z.string()).optional(),\n exampleFilesGeneratedAt: z.number().optional(),\n hasTrustDialogAccepted: z.boolean().optional(),\n hasCompletedProjectOnboarding: z.boolean().optional(),\n})\n\n// Global Configuration\nexport const GlobalConfigSchema = z.object({\n projects: z.record(ProjectConfigSchema).optional(),\n numStartups: z.number(),\n autoUpdaterStatus: AutoUpdaterStatusSchema.optional(),\n userID: z.string().optional(),\n theme: z.string(),\n hasCompletedOnboarding: z.boolean().optional(),\n lastOnboardingVersion: z.string().optional(),\n lastReleaseNotesSeen: z.string().optional(),\n mcpServers: z.record(McpServerConfigSchema).optional(),\n preferredNotifChannel: NotificationChannelSchema,\n verbose: z.boolean(),\n customApiKeyResponses: z\n .object({\n approved: z.array(z.string()).optional(),\n rejected: z.array(z.string()).optional(),\n })\n .optional(),\n primaryProvider: ProviderTypeSchema.optional(),\n maxTokens: z.number().optional(),\n hasAcknowledgedCostThreshold: z.boolean().optional(),\n oauthAccount: AccountInfoSchema.optional(),\n iterm2KeyBindingInstalled: z.boolean().optional(),\n shiftEnterKeyBindingInstalled: z.boolean().optional(),\n proxy: z.string().optional(),\n stream: z.boolean().optional(),\n modelProfiles: z.array(ModelProfileSchema).optional(),\n modelPointers: ModelPointersSchema.optional(),\n defaultModelName: z.string().optional(),\n lastDismissedUpdateVersion: z.string().optional(),\n compressionMode: CompressionModeSchema.optional(),\n thinking: z.boolean().optional(),\n // Config version for migrations\n configVersion: z.number().optional(),\n})\n\n// Type exports for TypeScript usage\nexport type McpStdioServerConfig = z.infer<typeof McpStdioServerConfigSchema>\nexport type McpSSEServerConfig = z.infer<typeof McpSSEServerConfigSchema>\nexport type McpServerConfig = z.infer<typeof McpServerConfigSchema>\nexport type AutoUpdaterStatus = z.infer<typeof AutoUpdaterStatusSchema>\nexport type NotificationChannel = z.infer<typeof NotificationChannelSchema>\nexport type ProviderType = z.infer<typeof ProviderTypeSchema>\nexport type CompressionMode = z.infer<typeof CompressionModeSchema>\nexport type ReasoningEffort = z.infer<typeof ReasoningEffortSchema>\nexport type ModelProfile = z.infer<typeof ModelProfileSchema>\nexport type ModelPointerType = z.infer<typeof ModelPointerTypeSchema>\nexport type ModelPointers = z.infer<typeof ModelPointersSchema>\nexport type AccountInfo = z.infer<typeof AccountInfoSchema>\nexport type ProjectConfig = z.infer<typeof ProjectConfigSchema>\nexport type GlobalConfig = z.infer<typeof GlobalConfigSchema>\n\n// Validation utilities\nexport function validateGlobalConfig(config: unknown): GlobalConfig {\n return GlobalConfigSchema.parse(config)\n}\n\nexport function validateProjectConfig(config: unknown): ProjectConfig {\n return ProjectConfigSchema.parse(config)\n}\n\nexport function safeValidateGlobalConfig(config: unknown): {\n success: boolean\n data?: GlobalConfig\n error?: z.ZodError\n} {\n const result = GlobalConfigSchema.safeParse(config)\n return result.success\n ? { success: true, data: result.data }\n : { success: false, error: result.error }\n}\n\nexport function safeValidateProjectConfig(config: unknown): {\n success: boolean\n data?: ProjectConfig\n error?: z.ZodError\n} {\n const result = ProjectConfigSchema.safeParse(config)\n return result.success\n ? { success: true, data: result.data }\n : { success: false, error: result.error }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Configuration Schema Definitions\n *\n * Defines all configuration types for Minto with Zod validation.\n */\n\nimport { z } from 'zod'\n\n// MCP Server Configurations\nexport const McpStdioServerConfigSchema = z.object({\n type: z.literal('stdio').optional().default('stdio'),\n command: z.string(),\n args: z.array(z.string()),\n env: z.record(z.string()).optional(),\n enabled: z.boolean().optional(),\n})\n\nexport const McpSSEServerConfigSchema = z.object({\n type: z.literal('sse'),\n url: z.string(),\n enabled: z.boolean().optional(),\n})\n\nexport const McpServerConfigSchema = z.union([\n McpStdioServerConfigSchema,\n McpSSEServerConfigSchema,\n])\n\n// Auto Updater Status\nexport const AutoUpdaterStatusSchema = z.enum([\n 'disabled',\n 'enabled',\n 'no_permissions',\n 'not_configured',\n])\n\n// Notification Channel\nexport const NotificationChannelSchema = z.enum([\n 'iterm2',\n 'terminal_bell',\n 'iterm2_with_bell',\n 'notifications_disabled',\n])\n\n// Provider Types\nexport const ProviderTypeSchema = z.enum([\n 'anthropic',\n 'openai',\n 'mistral',\n 'deepseek',\n 'kimi',\n 'qwen',\n 'glm',\n 'minimax',\n 'baidu-qianfan',\n 'siliconflow',\n 'bigdream',\n 'opendev',\n 'xai',\n 'groq',\n 'gemini',\n 'ollama',\n 'azure',\n 'custom',\n 'custom-openai',\n])\n\n// Compression Mode\nexport const CompressionModeSchema = z.enum(['business', 'code'])\n\n// UI Language\nexport const UILanguageSchema = z.enum(['en', 'zh-CN'])\n\n// Safety Mode\nexport const SafetyModeSchema = z.enum(['yolo', 'smart', 'strict', 'free'])\n\n// Reasoning Effort\nexport const ReasoningEffortSchema = z.enum([\n 'minimal',\n 'low',\n 'medium',\n 'high',\n])\n\n// Model Profile\nexport const ModelProfileSchema = z.object({\n name: z.string(),\n provider: ProviderTypeSchema,\n modelName: z.string(),\n baseURL: z.string().optional(),\n apiKey: z.string(),\n maxTokens: z.number(),\n contextLength: z.number(),\n reasoningEffort: ReasoningEffortSchema.optional(),\n isActive: z.boolean(),\n createdAt: z.number(),\n lastUsed: z.number().optional(),\n isGPT5: z.boolean().optional(),\n validationStatus: z\n .enum(['valid', 'needs_repair', 'auto_repaired'])\n .optional(),\n lastValidation: z.number().optional(),\n})\n\n// Model Pointer Type\nexport const ModelPointerTypeSchema = z.enum([\n 'main',\n 'task',\n 'reasoning',\n 'quick',\n 'compact',\n])\n\n// Model Pointers\nexport const ModelPointersSchema = z.object({\n main: z.string(),\n task: z.string(),\n reasoning: z.string(),\n quick: z.string(),\n compact: z.string(),\n})\n\n// Account Info\nexport const AccountInfoSchema = z.object({\n accountUuid: z.string(),\n emailAddress: z.string(),\n organizationUuid: z.string().optional(),\n})\n\n// Project Configuration\nexport const ProjectConfigSchema = z.object({\n allowedTools: z.array(z.string()),\n context: z.record(z.string()),\n contextFiles: z.array(z.string()).optional(),\n history: z.array(z.string()),\n dontCrawlDirectory: z.boolean().optional(),\n enableArchitectTool: z.boolean().optional(),\n mcpContextUris: z.array(z.string()),\n mcpServers: z.record(McpServerConfigSchema).optional(),\n approvedMcprcServers: z.array(z.string()).optional(),\n rejectedMcprcServers: z.array(z.string()).optional(),\n lastAPIDuration: z.number().optional(),\n lastCost: z.number().optional(),\n lastDuration: z.number().optional(),\n lastSessionId: z.string().optional(),\n exampleFiles: z.array(z.string()).optional(),\n exampleFilesGeneratedAt: z.number().optional(),\n hasTrustDialogAccepted: z.boolean().optional(),\n hasCompletedProjectOnboarding: z.boolean().optional(),\n})\n\n// Global Configuration\nexport const GlobalConfigSchema = z.object({\n projects: z.record(ProjectConfigSchema).optional(),\n numStartups: z.number(),\n autoUpdaterStatus: AutoUpdaterStatusSchema.optional(),\n userID: z.string().optional(),\n theme: z.string(),\n hasCompletedOnboarding: z.boolean().optional(),\n lastOnboardingVersion: z.string().optional(),\n lastReleaseNotesSeen: z.string().optional(),\n mcpServers: z.record(McpServerConfigSchema).optional(),\n preferredNotifChannel: NotificationChannelSchema,\n verbose: z.boolean(),\n customApiKeyResponses: z\n .object({\n approved: z.array(z.string()).optional(),\n rejected: z.array(z.string()).optional(),\n })\n .optional(),\n primaryProvider: ProviderTypeSchema.optional(),\n maxTokens: z.number().optional(),\n hasAcknowledgedCostThreshold: z.boolean().optional(),\n oauthAccount: AccountInfoSchema.optional(),\n iterm2KeyBindingInstalled: z.boolean().optional(),\n shiftEnterKeyBindingInstalled: z.boolean().optional(),\n proxy: z.string().optional(),\n stream: z.boolean().optional(),\n modelProfiles: z.array(ModelProfileSchema).optional(),\n modelPointers: ModelPointersSchema.optional(),\n defaultModelName: z.string().optional(),\n lastDismissedUpdateVersion: z.string().optional(),\n compressionMode: CompressionModeSchema.optional(),\n thinking: z.boolean().optional(),\n language: UILanguageSchema.optional(),\n safetyMode: SafetyModeSchema.optional(),\n // Claude Code sync\n autoSyncClaudeCode: z.boolean().optional(),\n // File backup for /undo\n backupEnabled: z.boolean().optional(),\n // Config version for migrations\n configVersion: z.number().optional(),\n})\n\n// Type exports for TypeScript usage\nexport type McpStdioServerConfig = z.infer<typeof McpStdioServerConfigSchema>\nexport type McpSSEServerConfig = z.infer<typeof McpSSEServerConfigSchema>\nexport type McpServerConfig = z.infer<typeof McpServerConfigSchema>\nexport type AutoUpdaterStatus = z.infer<typeof AutoUpdaterStatusSchema>\nexport type NotificationChannel = z.infer<typeof NotificationChannelSchema>\nexport type ProviderType = z.infer<typeof ProviderTypeSchema>\nexport type CompressionMode = z.infer<typeof CompressionModeSchema>\nexport type UILanguage = z.infer<typeof UILanguageSchema>\nexport type SafetyMode = z.infer<typeof SafetyModeSchema>\nexport type ReasoningEffort = z.infer<typeof ReasoningEffortSchema>\nexport type ModelProfile = z.infer<typeof ModelProfileSchema>\nexport type ModelPointerType = z.infer<typeof ModelPointerTypeSchema>\nexport type ModelPointers = z.infer<typeof ModelPointersSchema>\nexport type AccountInfo = z.infer<typeof AccountInfoSchema>\nexport type ProjectConfig = z.infer<typeof ProjectConfigSchema>\nexport type GlobalConfig = z.infer<typeof GlobalConfigSchema>\n\n// Validation utilities\nexport function validateGlobalConfig(config: unknown): GlobalConfig {\n return GlobalConfigSchema.parse(config)\n}\n\nexport function validateProjectConfig(config: unknown): ProjectConfig {\n return ProjectConfigSchema.parse(config)\n}\n\nexport function safeValidateGlobalConfig(config: unknown): {\n success: boolean\n data?: GlobalConfig\n error?: z.ZodError\n} {\n const result = GlobalConfigSchema.safeParse(config)\n return result.success\n ? { success: true, data: result.data }\n : { success: false, error: result.error }\n}\n\nexport function safeValidateProjectConfig(config: unknown): {\n success: boolean\n data?: ProjectConfig\n error?: z.ZodError\n} {\n const result = ProjectConfigSchema.safeParse(config)\n return result.success\n ? { success: true, data: result.data }\n : { success: false, error: result.error }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,11 +2,6 @@ import chalk from "chalk";
2
2
  import { useEffect } from "react";
3
3
  import signalExit from "signal-exit";
4
4
  import { formatDuration } from "../utils/format.js";
5
- import {
6
- getCurrentProjectConfig,
7
- saveCurrentProjectConfig
8
- } from "../utils/config.js";
9
- import { SESSION_ID } from "../utils/log.js";
10
5
  import { tokenStatsManager } from "./tokenStatsManager.js";
11
6
  const STATE = {
12
7
  totalCost: 0,
@@ -86,17 +81,6 @@ function useCostSummary() {
86
81
  signalExit(
87
82
  () => {
88
83
  process.stdout.write("\n" + formatTotalCost() + "\n");
89
- try {
90
- const projectConfig = getCurrentProjectConfig();
91
- saveCurrentProjectConfig({
92
- ...projectConfig,
93
- lastCost: STATE.totalCost,
94
- lastAPIDuration: STATE.totalAPIDuration,
95
- lastDuration: getTotalDuration(),
96
- lastSessionId: SESSION_ID
97
- });
98
- } catch {
99
- }
100
84
  },
101
85
  { alwaysLast: true }
102
86
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/costTracker.ts"],
4
- "sourcesContent": ["/**\n * Cost Tracker\n *\n * Tracks API costs and usage metrics for the session.\n * This is the core implementation; the original cost-tracker.ts\n * re-exports from this module for backwards compatibility.\n *\n * NOTE: Token statistics are now managed by TokenStatsManager.\n * This module delegates getTokenCounts() to the unified manager.\n */\n\nimport chalk from 'chalk'\nimport { useEffect } from 'react'\nimport signalExit from 'signal-exit'\nimport { formatDuration } from '../utils/format'\nimport {\n getCurrentProjectConfig,\n saveCurrentProjectConfig,\n} from '../utils/config'\nimport { SESSION_ID } from '../utils/log'\nimport { tokenStatsManager } from './tokenStatsManager'\n\n/**\n * Cost tracking state\n *\n * NOTE: Token statistics are now managed by TokenStatsManager.\n * This state only tracks cost, duration, and request count.\n */\ninterface CostState {\n /** Total cost in dollars */\n totalCost: number\n\n /** Total API call duration in milliseconds */\n totalAPIDuration: number\n\n /** Session start timestamp */\n startTime: number\n\n /** Request count */\n requestCount: number\n}\n\n// Global state (token tracking delegated to TokenStatsManager)\nconst STATE: CostState = {\n totalCost: 0,\n totalAPIDuration: 0,\n startTime: Date.now(),\n requestCount: 0,\n}\n\n/**\n * Add to total cost\n */\nexport function addToTotalCost(cost: number, duration: number): void {\n STATE.totalCost += cost\n STATE.totalAPIDuration += duration\n STATE.requestCount += 1\n}\n\n/**\n * Add token usage\n *\n * @deprecated Use tokenStatsManager.recordUsage() instead.\n * This function is kept for backward compatibility but no longer updates\n * the global token statistics. Token tracking is now handled by TokenStatsManager.\n */\nexport function addTokenUsage(\n _inputTokens: number,\n _outputTokens: number,\n _cacheCreationTokens?: number,\n _cacheReadTokens?: number,\n): void {\n // DEPRECATED: This function is a no-op.\n // Token tracking is now handled by TokenStatsManager in claude.ts via recordTokenUsage().\n}\n\n/**\n * Get total cost\n */\nexport function getTotalCost(): number {\n return STATE.totalCost\n}\n\n/**\n * Get total duration (wall clock time)\n */\nexport function getTotalDuration(): number {\n return Date.now() - STATE.startTime\n}\n\n/**\n * Get total API duration\n */\nexport function getTotalAPIDuration(): number {\n return STATE.totalAPIDuration\n}\n\n/**\n * Get token counts\n *\n * Delegates to TokenStatsManager for unified token statistics.\n */\nexport function getTokenCounts(): {\n input: number\n output: number\n cacheCreation: number\n cacheRead: number\n total: number\n} {\n // Delegate to TokenStatsManager (single source of truth)\n const globalStats = tokenStatsManager.getGlobalStats()\n return {\n input: globalStats.totalInputTokens,\n output: globalStats.totalOutputTokens,\n cacheCreation: globalStats.totalCacheCreationTokens,\n cacheRead: globalStats.totalCacheReadTokens,\n total: globalStats.grandTotalTokens,\n }\n}\n\n/**\n * Get request count\n */\nexport function getRequestCount(): number {\n return STATE.requestCount\n}\n\n/**\n * Get full cost summary\n */\nexport function getCostSummary(): {\n cost: number\n apiDuration: number\n wallDuration: number\n tokens: ReturnType<typeof getTokenCounts>\n requests: number\n} {\n return {\n cost: STATE.totalCost,\n apiDuration: STATE.totalAPIDuration,\n wallDuration: getTotalDuration(),\n tokens: getTokenCounts(),\n requests: STATE.requestCount,\n }\n}\n\n/**\n * Format cost for display\n */\nfunction formatCost(cost: number): string {\n return `$${cost > 0.5 ? round(cost, 100).toFixed(2) : cost.toFixed(4)}`\n}\n\n/**\n * Round to precision\n */\nfunction round(number: number, precision: number): number {\n return Math.round(number * precision) / precision\n}\n\n/**\n * Format total cost for display\n */\nexport function formatTotalCost(): string {\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}`,\n )\n}\n\n/**\n * Format detailed cost summary\n */\nexport function formatDetailedCost(): string {\n const tokens = getTokenCounts()\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}\nTokens: ${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out\nCache: ${tokens.cacheCreation.toLocaleString()} created / ${tokens.cacheRead.toLocaleString()} read\nRequests: ${STATE.requestCount}`,\n )\n}\n\n// Flag to ensure we only register once and never unregister\nlet exitHandlerRegistered = false\n\n/**\n * React hook for cost summary on exit\n *\n * CRITICAL FIX: Use signal-exit with alwaysLast: true\n *\n * The problem was that Ink also uses signal-exit (with alwaysLast: false)\n * to clean up on exit. During Ink's cleanup, it may:\n * 1. Call onRender() one last time\n * 2. Use ansiEscapes.eraseLines() to clear previous output\n * 3. Or even call clearTerminal if output height >= terminal rows\n *\n * By using signal-exit with alwaysLast: true, we ensure our handler\n * runs AFTER Ink's cleanup, so our statistics are the last thing printed.\n */\nexport function useCostSummary(): void {\n useEffect(() => {\n // Only register ONCE, and NEVER unregister\n if (exitHandlerRegistered) {\n return\n }\n exitHandlerRegistered = true\n\n // Use signal-exit with alwaysLast: true to run AFTER Ink's cleanup\n signalExit(\n () => {\n // Write statistics to stdout\n process.stdout.write('\\n' + formatTotalCost() + '\\n')\n\n // Save last cost and duration to project config\n try {\n const projectConfig = getCurrentProjectConfig()\n saveCurrentProjectConfig({\n ...projectConfig,\n lastCost: STATE.totalCost,\n lastAPIDuration: STATE.totalAPIDuration,\n lastDuration: getTotalDuration(),\n lastSessionId: SESSION_ID,\n })\n } catch {\n // Ignore errors during exit - config save is best-effort\n }\n },\n { alwaysLast: true },\n )\n\n // NO cleanup - never unregister the exit handler\n }, [])\n}\n\n/**\n * Reset state (for testing only)\n */\nexport function resetStateForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetStateForTests can only be called in tests')\n }\n STATE.startTime = Date.now()\n STATE.totalCost = 0\n STATE.totalAPIDuration = 0\n STATE.requestCount = 0\n // Token stats are managed by TokenStatsManager - reset there if needed\n tokenStatsManager.reset()\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Cost Tracker\n *\n * Tracks API costs and usage metrics for the session.\n * This is the core implementation; the original cost-tracker.ts\n * re-exports from this module for backwards compatibility.\n *\n * NOTE: Token statistics are now managed by TokenStatsManager.\n * This module delegates getTokenCounts() to the unified manager.\n */\n\nimport chalk from 'chalk'\nimport { useEffect } from 'react'\nimport signalExit from 'signal-exit'\nimport { formatDuration } from '../utils/format'\nimport { tokenStatsManager } from './tokenStatsManager'\n\n/**\n * Cost tracking state\n *\n * NOTE: Token statistics are now managed by TokenStatsManager.\n * This state only tracks cost, duration, and request count.\n */\ninterface CostState {\n /** Total cost in dollars */\n totalCost: number\n\n /** Total API call duration in milliseconds */\n totalAPIDuration: number\n\n /** Session start timestamp */\n startTime: number\n\n /** Request count */\n requestCount: number\n}\n\n// Global state (token tracking delegated to TokenStatsManager)\nconst STATE: CostState = {\n totalCost: 0,\n totalAPIDuration: 0,\n startTime: Date.now(),\n requestCount: 0,\n}\n\n/**\n * Add to total cost\n */\nexport function addToTotalCost(cost: number, duration: number): void {\n STATE.totalCost += cost\n STATE.totalAPIDuration += duration\n STATE.requestCount += 1\n}\n\n/**\n * Add token usage\n *\n * @deprecated Use tokenStatsManager.recordUsage() instead.\n * This function is kept for backward compatibility but no longer updates\n * the global token statistics. Token tracking is now handled by TokenStatsManager.\n */\nexport function addTokenUsage(\n _inputTokens: number,\n _outputTokens: number,\n _cacheCreationTokens?: number,\n _cacheReadTokens?: number,\n): void {\n // DEPRECATED: This function is a no-op.\n // Token tracking is now handled by TokenStatsManager in claude.ts via recordTokenUsage().\n}\n\n/**\n * Get total cost\n */\nexport function getTotalCost(): number {\n return STATE.totalCost\n}\n\n/**\n * Get total duration (wall clock time)\n */\nexport function getTotalDuration(): number {\n return Date.now() - STATE.startTime\n}\n\n/**\n * Get total API duration\n */\nexport function getTotalAPIDuration(): number {\n return STATE.totalAPIDuration\n}\n\n/**\n * Get token counts\n *\n * Delegates to TokenStatsManager for unified token statistics.\n */\nexport function getTokenCounts(): {\n input: number\n output: number\n cacheCreation: number\n cacheRead: number\n total: number\n} {\n // Delegate to TokenStatsManager (single source of truth)\n const globalStats = tokenStatsManager.getGlobalStats()\n return {\n input: globalStats.totalInputTokens,\n output: globalStats.totalOutputTokens,\n cacheCreation: globalStats.totalCacheCreationTokens,\n cacheRead: globalStats.totalCacheReadTokens,\n total: globalStats.grandTotalTokens,\n }\n}\n\n/**\n * Get request count\n */\nexport function getRequestCount(): number {\n return STATE.requestCount\n}\n\n/**\n * Get full cost summary\n */\nexport function getCostSummary(): {\n cost: number\n apiDuration: number\n wallDuration: number\n tokens: ReturnType<typeof getTokenCounts>\n requests: number\n} {\n return {\n cost: STATE.totalCost,\n apiDuration: STATE.totalAPIDuration,\n wallDuration: getTotalDuration(),\n tokens: getTokenCounts(),\n requests: STATE.requestCount,\n }\n}\n\n/**\n * Format cost for display\n */\nfunction formatCost(cost: number): string {\n return `$${cost > 0.5 ? round(cost, 100).toFixed(2) : cost.toFixed(4)}`\n}\n\n/**\n * Round to precision\n */\nfunction round(number: number, precision: number): number {\n return Math.round(number * precision) / precision\n}\n\n/**\n * Format total cost for display\n */\nexport function formatTotalCost(): string {\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}`,\n )\n}\n\n/**\n * Format detailed cost summary\n */\nexport function formatDetailedCost(): string {\n const tokens = getTokenCounts()\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}\nTokens: ${tokens.input.toLocaleString()} in / ${tokens.output.toLocaleString()} out\nCache: ${tokens.cacheCreation.toLocaleString()} created / ${tokens.cacheRead.toLocaleString()} read\nRequests: ${STATE.requestCount}`,\n )\n}\n\n// Flag to ensure we only register once and never unregister\nlet exitHandlerRegistered = false\n\n/**\n * React hook for cost summary on exit\n *\n * CRITICAL FIX: Use signal-exit with alwaysLast: true\n *\n * The problem was that Ink also uses signal-exit (with alwaysLast: false)\n * to clean up on exit. During Ink's cleanup, it may:\n * 1. Call onRender() one last time\n * 2. Use ansiEscapes.eraseLines() to clear previous output\n * 3. Or even call clearTerminal if output height >= terminal rows\n *\n * By using signal-exit with alwaysLast: true, we ensure our handler\n * runs AFTER Ink's cleanup, so our statistics are the last thing printed.\n */\nexport function useCostSummary(): void {\n useEffect(() => {\n // Only register ONCE, and NEVER unregister\n if (exitHandlerRegistered) {\n return\n }\n exitHandlerRegistered = true\n\n // Use signal-exit with alwaysLast: true to run AFTER Ink's cleanup\n signalExit(\n () => {\n // Write statistics to stdout\n process.stdout.write('\\n' + formatTotalCost() + '\\n')\n },\n { alwaysLast: true },\n )\n\n // NO cleanup - never unregister the exit handler\n }, [])\n}\n\n/**\n * Reset state (for testing only)\n */\nexport function resetStateForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetStateForTests can only be called in tests')\n }\n STATE.startTime = Date.now()\n STATE.totalCost = 0\n STATE.totalAPIDuration = 0\n STATE.requestCount = 0\n // Token stats are managed by TokenStatsManager - reset there if needed\n tokenStatsManager.reset()\n}\n"],
5
+ "mappings": "AAWA,OAAO,WAAW;AAClB,SAAS,iBAAiB;AAC1B,OAAO,gBAAgB;AACvB,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAuBlC,MAAM,QAAmB;AAAA,EACvB,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW,KAAK,IAAI;AAAA,EACpB,cAAc;AAChB;AAKO,SAAS,eAAe,MAAc,UAAwB;AACnE,QAAM,aAAa;AACnB,QAAM,oBAAoB;AAC1B,QAAM,gBAAgB;AACxB;AASO,SAAS,cACd,cACA,eACA,sBACA,kBACM;AAGR;AAKO,SAAS,eAAuB;AACrC,SAAO,MAAM;AACf;AAKO,SAAS,mBAA2B;AACzC,SAAO,KAAK,IAAI,IAAI,MAAM;AAC5B;AAKO,SAAS,sBAA8B;AAC5C,SAAO,MAAM;AACf;AAOO,SAAS,iBAMd;AAEA,QAAM,cAAc,kBAAkB,eAAe;AACrD,SAAO;AAAA,IACL,OAAO,YAAY;AAAA,IACnB,QAAQ,YAAY;AAAA,IACpB,eAAe,YAAY;AAAA,IAC3B,WAAW,YAAY;AAAA,IACvB,OAAO,YAAY;AAAA,EACrB;AACF;AAKO,SAAS,kBAA0B;AACxC,SAAO,MAAM;AACf;AAKO,SAAS,iBAMd;AACA,SAAO;AAAA,IACL,MAAM,MAAM;AAAA,IACZ,aAAa,MAAM;AAAA,IACnB,cAAc,iBAAiB;AAAA,IAC/B,QAAQ,eAAe;AAAA,IACvB,UAAU,MAAM;AAAA,EAClB;AACF;AAKA,SAAS,WAAW,MAAsB;AACxC,SAAO,IAAI,OAAO,MAAM,MAAM,MAAM,GAAG,EAAE,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AACvE;AAKA,SAAS,MAAM,QAAgB,WAA2B;AACxD,SAAO,KAAK,MAAM,SAAS,SAAS,IAAI;AAC1C;AAKO,SAAS,kBAA0B;AACxC,SAAO,MAAM;AAAA,IACX,eAAe,WAAW,MAAM,SAAS,CAAC;AAAA,wBACtB,eAAe,MAAM,gBAAgB,CAAC;AAAA,yBACrC,eAAe,iBAAiB,CAAC,CAAC;AAAA,EACzD;AACF;AAKO,SAAS,qBAA6B;AAC3C,QAAM,SAAS,eAAe;AAC9B,SAAO,MAAM;AAAA,IACX,eAAe,WAAW,MAAM,SAAS,CAAC;AAAA,wBACtB,eAAe,MAAM,gBAAgB,CAAC;AAAA,yBACrC,eAAe,iBAAiB,CAAC,CAAC;AAAA,UACjD,OAAO,MAAM,eAAe,CAAC,SAAS,OAAO,OAAO,eAAe,CAAC;AAAA,SACrE,OAAO,cAAc,eAAe,CAAC,cAAc,OAAO,UAAU,eAAe,CAAC;AAAA,YACjF,MAAM,YAAY;AAAA,EAC5B;AACF;AAGA,IAAI,wBAAwB;AAgBrB,SAAS,iBAAuB;AACrC,YAAU,MAAM;AAEd,QAAI,uBAAuB;AACzB;AAAA,IACF;AACA,4BAAwB;AAGxB;AAAA,MACE,MAAM;AAEJ,gBAAQ,OAAO,MAAM,OAAO,gBAAgB,IAAI,IAAI;AAAA,MACtD;AAAA,MACA,EAAE,YAAY,KAAK;AAAA,IACrB;AAAA,EAGF,GAAG,CAAC,CAAC;AACP;AAKO,SAAS,qBAA2B;AACzC,MAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,YAAY;AAClB,QAAM,mBAAmB;AACzB,QAAM,eAAe;AAErB,oBAAkB,MAAM;AAC1B;",
6
6
  "names": []
7
7
  }
@@ -311,6 +311,11 @@ class TokenStatsManagerImpl extends EventEmitter {
311
311
  const tokenStatsManager = TokenStatsManagerImpl.getInstance();
312
312
  function recordTokenUsage(usage, costUSD, model, context) {
313
313
  tokenStatsManager.recordUsage(usage, costUSD, model, context);
314
+ try {
315
+ const { recordMessage } = require("@utils/stats");
316
+ recordMessage(model, usage.inputTokens, usage.outputTokens, costUSD);
317
+ } catch {
318
+ }
314
319
  }
315
320
  function getGlobalTokenStats() {
316
321
  return tokenStatsManager.getGlobalStats();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/tokenStatsManager.ts"],
4
- "sourcesContent": ["/**\n * Token Statistics Manager\n *\n * Centralized token statistics tracking for the entire application.\n * This is the single source of truth for all token usage data.\n *\n * Architecture:\n * - Singleton pattern for global access\n * - Event-driven updates for real-time UI\n * - Hierarchical aggregation (global \u2192 agent \u2192 request)\n * - Backward compatible with existing costTracker\n *\n * Usage:\n * ```typescript\n * import { tokenStatsManager, recordTokenUsage } from '@core/tokenStatsManager'\n *\n * // Record usage from API response\n * recordTokenUsage(\n * { inputTokens: 100, outputTokens: 50 },\n * 0.0015,\n * 'gpt-4',\n * { agentId: 'agent-123' }\n * )\n *\n * // Get global stats\n * const stats = tokenStatsManager.getGlobalStats()\n *\n * // Subscribe to updates\n * const unsubscribe = tokenStatsManager.onStatsUpdate((event) => {\n * console.log('Stats updated:', event)\n * })\n * ```\n */\n\nimport { EventEmitter } from 'events'\nimport type {\n TokenUsageRecord,\n AggregatedTokenStats,\n TokenStatsScope,\n TokenStatsEvent,\n TokenTrackingContext,\n RawTokenUsage,\n ModelTokenStats,\n} from './tokenStats'\nimport { DEFAULT_ESTIMATION_CONFIG } from './tokenStats'\n\n/**\n * TokenStatsManager - Centralized token statistics tracking\n */\nclass TokenStatsManagerImpl extends EventEmitter {\n private static instance: TokenStatsManagerImpl | null = null\n\n // Global aggregated stats\n private globalStats: AggregatedTokenStats = this.createEmptyStats()\n\n // Per-agent stats (agentId \u2192 stats)\n private agentStats: Map<string, AggregatedTokenStats> = new Map()\n\n // Per-request records (requestId \u2192 record) - for debugging, with LRU eviction\n private requestRecords: Map<string, TokenUsageRecord> = new Map()\n\n // Configuration\n private static readonly MAX_REQUEST_RECORDS = 1000\n\n private constructor() {\n super()\n this.setMaxListeners(100)\n }\n\n /**\n * Get the singleton instance\n */\n static getInstance(): TokenStatsManagerImpl {\n if (!TokenStatsManagerImpl.instance) {\n TokenStatsManagerImpl.instance = new TokenStatsManagerImpl()\n }\n return TokenStatsManagerImpl.instance\n }\n\n /**\n * Record token usage from an API response\n *\n * This is the PRIMARY entry point - called from claude.ts\n * after every API response.\n *\n * @param usage - Raw token usage from API\n * @param costUSD - Calculated cost in USD\n * @param model - Model name used\n * @param context - Optional tracking context (agentId, toolUseId)\n */\n recordUsage(\n usage: RawTokenUsage,\n costUSD: number,\n model: string,\n context?: TokenTrackingContext,\n ): void {\n const record: TokenUsageRecord = {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheCreationTokens: usage.cacheCreationTokens ?? 0,\n cacheReadTokens: usage.cacheReadTokens ?? 0,\n totalTokens: usage.inputTokens + usage.outputTokens,\n estimatedCostUSD: costUSD,\n source: 'api',\n timestamp: Date.now(),\n model,\n }\n\n // Update global stats\n this.aggregateInto(this.globalStats, record)\n\n // Update agent stats if in agent context\n if (context?.agentId) {\n let stats = this.agentStats.get(context.agentId)\n if (!stats) {\n stats = this.createEmptyStats()\n this.agentStats.set(context.agentId, stats)\n }\n this.aggregateInto(stats, record)\n }\n\n // Store request record (with LRU eviction)\n const requestId =\n context?.toolUseId ??\n `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n this.requestRecords.set(requestId, record)\n if (this.requestRecords.size > TokenStatsManagerImpl.MAX_REQUEST_RECORDS) {\n const oldestKey = this.requestRecords.keys().next().value\n if (oldestKey) this.requestRecords.delete(oldestKey)\n }\n\n // Emit event for UI updates\n const scope: TokenStatsScope = context?.agentId\n ? { type: 'agent', agentId: context.agentId }\n : { type: 'global' }\n\n this.emitEvent({\n eventType: 'usage_recorded',\n scope,\n usage: record,\n aggregated: context?.agentId\n ? this.agentStats.get(context.agentId)!\n : this.globalStats,\n globalStats: this.globalStats,\n })\n }\n\n /**\n * Estimate token count from text\n *\n * Uses a simple character-based estimation. This is a fallback\n * when the API doesn't return token counts.\n *\n * @param text - Text to estimate tokens for\n * @returns Estimated token count\n */\n estimateTokens(text: string): number {\n return Math.ceil(text.length / DEFAULT_ESTIMATION_CONFIG.charsPerToken)\n }\n\n /**\n * Record estimated usage when API doesn't return token counts\n *\n * This is used as a fallback for APIs that don't return usage data.\n *\n * @param inputText - Input text (prompt)\n * @param outputText - Output text (completion)\n * @param model - Model name\n * @param context - Optional tracking context\n */\n recordEstimatedUsage(\n inputText: string,\n outputText: string,\n model: string,\n context?: TokenTrackingContext,\n ): void {\n const inputTokens = this.estimateTokens(inputText)\n const outputTokens = this.estimateTokens(outputText)\n\n const costUSD =\n (inputTokens / 1_000_000) *\n DEFAULT_ESTIMATION_CONFIG.defaultInputCostPerMillion +\n (outputTokens / 1_000_000) *\n DEFAULT_ESTIMATION_CONFIG.defaultOutputCostPerMillion\n\n const record: TokenUsageRecord = {\n inputTokens,\n outputTokens,\n cacheCreationTokens: 0,\n cacheReadTokens: 0,\n totalTokens: inputTokens + outputTokens,\n estimatedCostUSD: costUSD,\n source: 'estimated',\n timestamp: Date.now(),\n model,\n }\n\n // Update global stats\n this.aggregateInto(this.globalStats, record)\n\n // Update agent stats if in context\n if (context?.agentId) {\n let stats = this.agentStats.get(context.agentId)\n if (!stats) {\n stats = this.createEmptyStats()\n this.agentStats.set(context.agentId, stats)\n }\n this.aggregateInto(stats, record)\n }\n\n // Emit event\n this.emitEvent({\n eventType: 'usage_recorded',\n scope: { type: 'global' },\n usage: record,\n aggregated: this.globalStats,\n globalStats: this.globalStats,\n })\n }\n\n // ===== Getters =====\n\n /**\n * Get global aggregated statistics\n */\n getGlobalStats(): AggregatedTokenStats {\n return this.cloneStats(this.globalStats)\n }\n\n /**\n * Get statistics for a specific agent\n *\n * @param agentId - Agent ID to get stats for\n * @returns Agent stats or null if not found\n */\n getAgentStats(agentId: string): AggregatedTokenStats | null {\n const stats = this.agentStats.get(agentId)\n return stats ? this.cloneStats(stats) : null\n }\n\n /**\n * Get all agent statistics\n */\n getAllAgentStats(): Map<string, AggregatedTokenStats> {\n const result = new Map<string, AggregatedTokenStats>()\n for (const [agentId, stats] of this.agentStats) {\n result.set(agentId, this.cloneStats(stats))\n }\n return result\n }\n\n /**\n * Get a specific request record\n */\n getRequestRecord(requestId: string): TokenUsageRecord | null {\n return this.requestRecords.get(requestId) ?? null\n }\n\n // ===== Backward Compatibility Layer =====\n\n /**\n * Get total tokens (for costTracker.getTokenCounts compatibility)\n *\n * This method provides backward compatibility with the existing\n * costTracker.getTokenCounts() interface.\n */\n getTokenCounts(): {\n input: number\n output: number\n cacheCreation: number\n cacheRead: number\n total: number\n } {\n return {\n input: this.globalStats.totalInputTokens,\n output: this.globalStats.totalOutputTokens,\n cacheCreation: this.globalStats.totalCacheCreationTokens,\n cacheRead: this.globalStats.totalCacheReadTokens,\n total: this.globalStats.grandTotalTokens,\n }\n }\n\n // ===== Event Subscription =====\n\n /**\n * Subscribe to all stats updates\n *\n * @param callback - Called when any stats change\n * @returns Unsubscribe function\n */\n onStatsUpdate(callback: (event: TokenStatsEvent) => void): () => void {\n this.on('stats_update', callback)\n return () => this.off('stats_update', callback)\n }\n\n /**\n * Subscribe to updates for a specific agent\n *\n * @param agentId - Agent ID to subscribe to\n * @param callback - Called when agent stats change\n * @returns Unsubscribe function\n */\n onAgentStatsUpdate(\n agentId: string,\n callback: (stats: AggregatedTokenStats) => void,\n ): () => void {\n const handler = (event: TokenStatsEvent) => {\n if (event.scope.type === 'agent' && event.scope.agentId === agentId) {\n callback(event.aggregated)\n }\n }\n this.on('stats_update', handler)\n return () => this.off('stats_update', handler)\n }\n\n /**\n * Subscribe to global stats updates only\n *\n * @param callback - Called when global stats change\n * @returns Unsubscribe function\n */\n onGlobalStatsUpdate(\n callback: (stats: AggregatedTokenStats) => void,\n ): () => void {\n const handler = (event: TokenStatsEvent) => {\n callback(event.globalStats)\n }\n this.on('stats_update', handler)\n return () => this.off('stats_update', handler)\n }\n\n // ===== Agent Lifecycle =====\n\n /**\n * Mark an agent's scope as completed\n *\n * This emits a scope_completed event and optionally\n * cleans up the agent's stats from memory.\n *\n * @param agentId - Agent ID to complete\n * @param cleanup - Whether to remove from memory (default: false)\n */\n completeAgentScope(agentId: string, cleanup: boolean = false): void {\n const stats = this.agentStats.get(agentId)\n if (!stats) return\n\n this.emitEvent({\n eventType: 'scope_completed',\n scope: { type: 'agent', agentId },\n aggregated: stats,\n globalStats: this.globalStats,\n })\n\n if (cleanup) {\n this.agentStats.delete(agentId)\n }\n }\n\n // ===== Internal Methods =====\n\n private createEmptyStats(): AggregatedTokenStats {\n return {\n totalInputTokens: 0,\n totalOutputTokens: 0,\n totalCacheCreationTokens: 0,\n totalCacheReadTokens: 0,\n grandTotalTokens: 0,\n totalCostUSD: 0,\n requestCount: 0,\n byModel: new Map(),\n }\n }\n\n private aggregateInto(\n stats: AggregatedTokenStats,\n record: TokenUsageRecord,\n ): void {\n stats.totalInputTokens += record.inputTokens\n stats.totalOutputTokens += record.outputTokens\n stats.totalCacheCreationTokens += record.cacheCreationTokens\n stats.totalCacheReadTokens += record.cacheReadTokens\n stats.grandTotalTokens += record.totalTokens\n stats.totalCostUSD += record.estimatedCostUSD\n stats.requestCount += 1\n\n if (!stats.firstRequestTime) {\n stats.firstRequestTime = record.timestamp\n }\n stats.lastRequestTime = record.timestamp\n\n // Update per-model breakdown\n const modelStats = stats.byModel.get(record.model)\n if (modelStats) {\n modelStats.inputTokens += record.inputTokens\n modelStats.outputTokens += record.outputTokens\n modelStats.totalTokens += record.totalTokens\n modelStats.estimatedCostUSD += record.estimatedCostUSD\n modelStats.requestCount += 1\n } else {\n stats.byModel.set(record.model, {\n model: record.model,\n inputTokens: record.inputTokens,\n outputTokens: record.outputTokens,\n totalTokens: record.totalTokens,\n estimatedCostUSD: record.estimatedCostUSD,\n requestCount: 1,\n })\n }\n }\n\n private cloneStats(stats: AggregatedTokenStats): AggregatedTokenStats {\n return {\n ...stats,\n byModel: new Map(stats.byModel),\n }\n }\n\n private emitEvent(event: TokenStatsEvent): void {\n this.emit('stats_update', event)\n }\n\n /**\n * Reset all stats (for testing only)\n */\n resetForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetForTests can only be called in tests')\n }\n this.reset()\n }\n\n /**\n * Reset all statistics\n *\n * Called by costTracker.resetStateForTests() for backward compatibility.\n */\n reset(): void {\n this.globalStats = this.createEmptyStats()\n this.agentStats.clear()\n this.requestRecords.clear()\n }\n}\n\n// Export singleton instance\nexport const tokenStatsManager = TokenStatsManagerImpl.getInstance()\n\n// ===== Convenience Functions =====\n\n/**\n * Record token usage from an API response\n *\n * This is the main function to call from claude.ts after each API response.\n */\nexport function recordTokenUsage(\n usage: RawTokenUsage,\n costUSD: number,\n model: string,\n context?: TokenTrackingContext,\n): void {\n tokenStatsManager.recordUsage(usage, costUSD, model, context)\n}\n\n/**\n * Get global token statistics\n */\nexport function getGlobalTokenStats(): AggregatedTokenStats {\n return tokenStatsManager.getGlobalStats()\n}\n\n/**\n * Get token statistics for a specific agent\n */\nexport function getAgentTokenStats(\n agentId: string,\n): AggregatedTokenStats | null {\n return tokenStatsManager.getAgentStats(agentId)\n}\n\n/**\n * Estimate token count from text\n */\nexport function estimateTokenCount(text: string): number {\n return tokenStatsManager.estimateTokens(text)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Token Statistics Manager\n *\n * Centralized token statistics tracking for the entire application.\n * This is the single source of truth for all token usage data.\n *\n * Architecture:\n * - Singleton pattern for global access\n * - Event-driven updates for real-time UI\n * - Hierarchical aggregation (global \u2192 agent \u2192 request)\n * - Backward compatible with existing costTracker\n *\n * Usage:\n * ```typescript\n * import { tokenStatsManager, recordTokenUsage } from '@core/tokenStatsManager'\n *\n * // Record usage from API response\n * recordTokenUsage(\n * { inputTokens: 100, outputTokens: 50 },\n * 0.0015,\n * 'gpt-4',\n * { agentId: 'agent-123' }\n * )\n *\n * // Get global stats\n * const stats = tokenStatsManager.getGlobalStats()\n *\n * // Subscribe to updates\n * const unsubscribe = tokenStatsManager.onStatsUpdate((event) => {\n * console.log('Stats updated:', event)\n * })\n * ```\n */\n\nimport { EventEmitter } from 'events'\nimport type {\n TokenUsageRecord,\n AggregatedTokenStats,\n TokenStatsScope,\n TokenStatsEvent,\n TokenTrackingContext,\n RawTokenUsage,\n ModelTokenStats,\n} from './tokenStats'\nimport { DEFAULT_ESTIMATION_CONFIG } from './tokenStats'\n\n/**\n * TokenStatsManager - Centralized token statistics tracking\n */\nclass TokenStatsManagerImpl extends EventEmitter {\n private static instance: TokenStatsManagerImpl | null = null\n\n // Global aggregated stats\n private globalStats: AggregatedTokenStats = this.createEmptyStats()\n\n // Per-agent stats (agentId \u2192 stats)\n private agentStats: Map<string, AggregatedTokenStats> = new Map()\n\n // Per-request records (requestId \u2192 record) - for debugging, with LRU eviction\n private requestRecords: Map<string, TokenUsageRecord> = new Map()\n\n // Configuration\n private static readonly MAX_REQUEST_RECORDS = 1000\n\n private constructor() {\n super()\n this.setMaxListeners(100)\n }\n\n /**\n * Get the singleton instance\n */\n static getInstance(): TokenStatsManagerImpl {\n if (!TokenStatsManagerImpl.instance) {\n TokenStatsManagerImpl.instance = new TokenStatsManagerImpl()\n }\n return TokenStatsManagerImpl.instance\n }\n\n /**\n * Record token usage from an API response\n *\n * This is the PRIMARY entry point - called from claude.ts\n * after every API response.\n *\n * @param usage - Raw token usage from API\n * @param costUSD - Calculated cost in USD\n * @param model - Model name used\n * @param context - Optional tracking context (agentId, toolUseId)\n */\n recordUsage(\n usage: RawTokenUsage,\n costUSD: number,\n model: string,\n context?: TokenTrackingContext,\n ): void {\n const record: TokenUsageRecord = {\n inputTokens: usage.inputTokens,\n outputTokens: usage.outputTokens,\n cacheCreationTokens: usage.cacheCreationTokens ?? 0,\n cacheReadTokens: usage.cacheReadTokens ?? 0,\n totalTokens: usage.inputTokens + usage.outputTokens,\n estimatedCostUSD: costUSD,\n source: 'api',\n timestamp: Date.now(),\n model,\n }\n\n // Update global stats\n this.aggregateInto(this.globalStats, record)\n\n // Update agent stats if in agent context\n if (context?.agentId) {\n let stats = this.agentStats.get(context.agentId)\n if (!stats) {\n stats = this.createEmptyStats()\n this.agentStats.set(context.agentId, stats)\n }\n this.aggregateInto(stats, record)\n }\n\n // Store request record (with LRU eviction)\n const requestId =\n context?.toolUseId ??\n `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`\n this.requestRecords.set(requestId, record)\n if (this.requestRecords.size > TokenStatsManagerImpl.MAX_REQUEST_RECORDS) {\n const oldestKey = this.requestRecords.keys().next().value\n if (oldestKey) this.requestRecords.delete(oldestKey)\n }\n\n // Emit event for UI updates\n const scope: TokenStatsScope = context?.agentId\n ? { type: 'agent', agentId: context.agentId }\n : { type: 'global' }\n\n this.emitEvent({\n eventType: 'usage_recorded',\n scope,\n usage: record,\n aggregated: context?.agentId\n ? this.agentStats.get(context.agentId)!\n : this.globalStats,\n globalStats: this.globalStats,\n })\n }\n\n /**\n * Estimate token count from text\n *\n * Uses a simple character-based estimation. This is a fallback\n * when the API doesn't return token counts.\n *\n * @param text - Text to estimate tokens for\n * @returns Estimated token count\n */\n estimateTokens(text: string): number {\n return Math.ceil(text.length / DEFAULT_ESTIMATION_CONFIG.charsPerToken)\n }\n\n /**\n * Record estimated usage when API doesn't return token counts\n *\n * This is used as a fallback for APIs that don't return usage data.\n *\n * @param inputText - Input text (prompt)\n * @param outputText - Output text (completion)\n * @param model - Model name\n * @param context - Optional tracking context\n */\n recordEstimatedUsage(\n inputText: string,\n outputText: string,\n model: string,\n context?: TokenTrackingContext,\n ): void {\n const inputTokens = this.estimateTokens(inputText)\n const outputTokens = this.estimateTokens(outputText)\n\n const costUSD =\n (inputTokens / 1_000_000) *\n DEFAULT_ESTIMATION_CONFIG.defaultInputCostPerMillion +\n (outputTokens / 1_000_000) *\n DEFAULT_ESTIMATION_CONFIG.defaultOutputCostPerMillion\n\n const record: TokenUsageRecord = {\n inputTokens,\n outputTokens,\n cacheCreationTokens: 0,\n cacheReadTokens: 0,\n totalTokens: inputTokens + outputTokens,\n estimatedCostUSD: costUSD,\n source: 'estimated',\n timestamp: Date.now(),\n model,\n }\n\n // Update global stats\n this.aggregateInto(this.globalStats, record)\n\n // Update agent stats if in context\n if (context?.agentId) {\n let stats = this.agentStats.get(context.agentId)\n if (!stats) {\n stats = this.createEmptyStats()\n this.agentStats.set(context.agentId, stats)\n }\n this.aggregateInto(stats, record)\n }\n\n // Emit event\n this.emitEvent({\n eventType: 'usage_recorded',\n scope: { type: 'global' },\n usage: record,\n aggregated: this.globalStats,\n globalStats: this.globalStats,\n })\n }\n\n // ===== Getters =====\n\n /**\n * Get global aggregated statistics\n */\n getGlobalStats(): AggregatedTokenStats {\n return this.cloneStats(this.globalStats)\n }\n\n /**\n * Get statistics for a specific agent\n *\n * @param agentId - Agent ID to get stats for\n * @returns Agent stats or null if not found\n */\n getAgentStats(agentId: string): AggregatedTokenStats | null {\n const stats = this.agentStats.get(agentId)\n return stats ? this.cloneStats(stats) : null\n }\n\n /**\n * Get all agent statistics\n */\n getAllAgentStats(): Map<string, AggregatedTokenStats> {\n const result = new Map<string, AggregatedTokenStats>()\n for (const [agentId, stats] of this.agentStats) {\n result.set(agentId, this.cloneStats(stats))\n }\n return result\n }\n\n /**\n * Get a specific request record\n */\n getRequestRecord(requestId: string): TokenUsageRecord | null {\n return this.requestRecords.get(requestId) ?? null\n }\n\n // ===== Backward Compatibility Layer =====\n\n /**\n * Get total tokens (for costTracker.getTokenCounts compatibility)\n *\n * This method provides backward compatibility with the existing\n * costTracker.getTokenCounts() interface.\n */\n getTokenCounts(): {\n input: number\n output: number\n cacheCreation: number\n cacheRead: number\n total: number\n } {\n return {\n input: this.globalStats.totalInputTokens,\n output: this.globalStats.totalOutputTokens,\n cacheCreation: this.globalStats.totalCacheCreationTokens,\n cacheRead: this.globalStats.totalCacheReadTokens,\n total: this.globalStats.grandTotalTokens,\n }\n }\n\n // ===== Event Subscription =====\n\n /**\n * Subscribe to all stats updates\n *\n * @param callback - Called when any stats change\n * @returns Unsubscribe function\n */\n onStatsUpdate(callback: (event: TokenStatsEvent) => void): () => void {\n this.on('stats_update', callback)\n return () => this.off('stats_update', callback)\n }\n\n /**\n * Subscribe to updates for a specific agent\n *\n * @param agentId - Agent ID to subscribe to\n * @param callback - Called when agent stats change\n * @returns Unsubscribe function\n */\n onAgentStatsUpdate(\n agentId: string,\n callback: (stats: AggregatedTokenStats) => void,\n ): () => void {\n const handler = (event: TokenStatsEvent) => {\n if (event.scope.type === 'agent' && event.scope.agentId === agentId) {\n callback(event.aggregated)\n }\n }\n this.on('stats_update', handler)\n return () => this.off('stats_update', handler)\n }\n\n /**\n * Subscribe to global stats updates only\n *\n * @param callback - Called when global stats change\n * @returns Unsubscribe function\n */\n onGlobalStatsUpdate(\n callback: (stats: AggregatedTokenStats) => void,\n ): () => void {\n const handler = (event: TokenStatsEvent) => {\n callback(event.globalStats)\n }\n this.on('stats_update', handler)\n return () => this.off('stats_update', handler)\n }\n\n // ===== Agent Lifecycle =====\n\n /**\n * Mark an agent's scope as completed\n *\n * This emits a scope_completed event and optionally\n * cleans up the agent's stats from memory.\n *\n * @param agentId - Agent ID to complete\n * @param cleanup - Whether to remove from memory (default: false)\n */\n completeAgentScope(agentId: string, cleanup: boolean = false): void {\n const stats = this.agentStats.get(agentId)\n if (!stats) return\n\n this.emitEvent({\n eventType: 'scope_completed',\n scope: { type: 'agent', agentId },\n aggregated: stats,\n globalStats: this.globalStats,\n })\n\n if (cleanup) {\n this.agentStats.delete(agentId)\n }\n }\n\n // ===== Internal Methods =====\n\n private createEmptyStats(): AggregatedTokenStats {\n return {\n totalInputTokens: 0,\n totalOutputTokens: 0,\n totalCacheCreationTokens: 0,\n totalCacheReadTokens: 0,\n grandTotalTokens: 0,\n totalCostUSD: 0,\n requestCount: 0,\n byModel: new Map(),\n }\n }\n\n private aggregateInto(\n stats: AggregatedTokenStats,\n record: TokenUsageRecord,\n ): void {\n stats.totalInputTokens += record.inputTokens\n stats.totalOutputTokens += record.outputTokens\n stats.totalCacheCreationTokens += record.cacheCreationTokens\n stats.totalCacheReadTokens += record.cacheReadTokens\n stats.grandTotalTokens += record.totalTokens\n stats.totalCostUSD += record.estimatedCostUSD\n stats.requestCount += 1\n\n if (!stats.firstRequestTime) {\n stats.firstRequestTime = record.timestamp\n }\n stats.lastRequestTime = record.timestamp\n\n // Update per-model breakdown\n const modelStats = stats.byModel.get(record.model)\n if (modelStats) {\n modelStats.inputTokens += record.inputTokens\n modelStats.outputTokens += record.outputTokens\n modelStats.totalTokens += record.totalTokens\n modelStats.estimatedCostUSD += record.estimatedCostUSD\n modelStats.requestCount += 1\n } else {\n stats.byModel.set(record.model, {\n model: record.model,\n inputTokens: record.inputTokens,\n outputTokens: record.outputTokens,\n totalTokens: record.totalTokens,\n estimatedCostUSD: record.estimatedCostUSD,\n requestCount: 1,\n })\n }\n }\n\n private cloneStats(stats: AggregatedTokenStats): AggregatedTokenStats {\n return {\n ...stats,\n byModel: new Map(stats.byModel),\n }\n }\n\n private emitEvent(event: TokenStatsEvent): void {\n this.emit('stats_update', event)\n }\n\n /**\n * Reset all stats (for testing only)\n */\n resetForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetForTests can only be called in tests')\n }\n this.reset()\n }\n\n /**\n * Reset all statistics\n *\n * Called by costTracker.resetStateForTests() for backward compatibility.\n */\n reset(): void {\n this.globalStats = this.createEmptyStats()\n this.agentStats.clear()\n this.requestRecords.clear()\n }\n}\n\n// Export singleton instance\nexport const tokenStatsManager = TokenStatsManagerImpl.getInstance()\n\n// ===== Convenience Functions =====\n\n/**\n * Record token usage from an API response\n *\n * This is the main function to call from claude.ts after each API response.\n * Also updates the persistent stats manager for cross-session tracking.\n */\nexport function recordTokenUsage(\n usage: RawTokenUsage,\n costUSD: number,\n model: string,\n context?: TokenTrackingContext,\n): void {\n tokenStatsManager.recordUsage(usage, costUSD, model, context)\n\n // Also record to persistent stats manager (fire-and-forget)\n try {\n const { recordMessage } = require('@utils/stats')\n recordMessage(model, usage.inputTokens, usage.outputTokens, costUSD)\n } catch {\n // Stats manager not available, ignore\n }\n}\n\n/**\n * Get global token statistics\n */\nexport function getGlobalTokenStats(): AggregatedTokenStats {\n return tokenStatsManager.getGlobalStats()\n}\n\n/**\n * Get token statistics for a specific agent\n */\nexport function getAgentTokenStats(\n agentId: string,\n): AggregatedTokenStats | null {\n return tokenStatsManager.getAgentStats(agentId)\n}\n\n/**\n * Estimate token count from text\n */\nexport function estimateTokenCount(text: string): number {\n return tokenStatsManager.estimateTokens(text)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,11 +2,6 @@ import chalk from "chalk";
2
2
  import { useEffect } from "react";
3
3
  import signalExit from "signal-exit";
4
4
  import { formatDuration } from "./utils/format.js";
5
- import {
6
- getCurrentProjectConfig,
7
- saveCurrentProjectConfig
8
- } from "./utils/config.js";
9
- import { SESSION_ID } from "./utils/log.js";
10
5
  const STATE = {
11
6
  totalCost: 0,
12
7
  totalAPIDuration: 0,
@@ -45,17 +40,6 @@ function useCostSummary() {
45
40
  signalExit(
46
41
  () => {
47
42
  process.stdout.write("\n" + formatTotalCost() + "\n");
48
- try {
49
- const projectConfig = getCurrentProjectConfig();
50
- saveCurrentProjectConfig({
51
- ...projectConfig,
52
- lastCost: STATE.totalCost,
53
- lastAPIDuration: STATE.totalAPIDuration,
54
- lastDuration: getTotalDuration(),
55
- lastSessionId: SESSION_ID
56
- });
57
- } catch {
58
- }
59
43
  },
60
44
  { alwaysLast: true }
61
45
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/cost-tracker.ts"],
4
- "sourcesContent": ["import chalk from 'chalk'\nimport { useEffect } from 'react'\nimport signalExit from 'signal-exit'\nimport { formatDuration } from './utils/format'\nimport {\n getCurrentProjectConfig,\n saveCurrentProjectConfig,\n} from '@utils/config'\nimport { SESSION_ID } from './utils/log'\n\n// DO NOT ADD MORE STATE HERE OR BORIS WILL CURSE YOU\nconst STATE: {\n totalCost: number\n totalAPIDuration: number\n startTime: number\n} = {\n totalCost: 0,\n totalAPIDuration: 0,\n startTime: Date.now(),\n}\n\nexport function addToTotalCost(cost: number, duration: number): void {\n STATE.totalCost += cost\n STATE.totalAPIDuration += duration\n}\n\nexport function getTotalCost(): number {\n return STATE.totalCost\n}\n\nexport function getTotalDuration(): number {\n return Date.now() - STATE.startTime\n}\n\nexport function getTotalAPIDuration(): number {\n return STATE.totalAPIDuration\n}\n\nfunction formatCost(cost: number): string {\n return `$${cost > 0.5 ? round(cost, 100).toFixed(2) : cost.toFixed(4)}`\n}\n\nexport function formatTotalCost(): string {\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}`,\n )\n}\n\n/**\n * useCostSummary - Register exit handler to display cost summary\n *\n * CRITICAL FIX: Use signal-exit with alwaysLast: true\n *\n * The problem was that Ink also uses signal-exit (with alwaysLast: false)\n * to clean up on exit. During Ink's cleanup, it may:\n * 1. Call onRender() one last time\n * 2. Use ansiEscapes.eraseLines() to clear previous output\n * 3. Or even call clearTerminal if output height >= terminal rows\n *\n * By using signal-exit with alwaysLast: true, we ensure our handler\n * runs AFTER Ink's cleanup, so our statistics are the last thing printed.\n */\n// Flag to ensure we only register once and never unregister\nlet exitHandlerRegistered = false\n\nexport function useCostSummary(): void {\n useEffect(() => {\n // Only register ONCE, and NEVER unregister\n if (exitHandlerRegistered) {\n return\n }\n exitHandlerRegistered = true\n\n // Use signal-exit with alwaysLast: true to run AFTER Ink's cleanup\n // This ensures our statistics are printed after Ink finishes its final render\n signalExit(\n () => {\n // Write statistics to stdout\n // Using stdout.write directly to avoid any Ink interception\n process.stdout.write('\\n' + formatTotalCost() + '\\n')\n\n // Save last cost and duration to project config\n try {\n const projectConfig = getCurrentProjectConfig()\n saveCurrentProjectConfig({\n ...projectConfig,\n lastCost: STATE.totalCost,\n lastAPIDuration: STATE.totalAPIDuration,\n lastDuration: getTotalDuration(),\n lastSessionId: SESSION_ID,\n })\n } catch {\n // Ignore errors during exit - config save is best-effort\n }\n },\n { alwaysLast: true },\n )\n\n // NO cleanup function - we NEVER want to unregister this handler\n }, [])\n}\n\nfunction round(number: number, precision: number): number {\n return Math.round(number * precision) / precision\n}\n\n// Only used in tests\nexport function resetStateForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetStateForTests can only be called in tests')\n }\n STATE.startTime = Date.now()\n STATE.totalCost = 0\n STATE.totalAPIDuration = 0\n}\n"],
5
- "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,iBAAiB;AAC1B,OAAO,gBAAgB;AACvB,SAAS,sBAAsB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,kBAAkB;AAG3B,MAAM,QAIF;AAAA,EACF,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW,KAAK,IAAI;AACtB;AAEO,SAAS,eAAe,MAAc,UAAwB;AACnE,QAAM,aAAa;AACnB,QAAM,oBAAoB;AAC5B;AAEO,SAAS,eAAuB;AACrC,SAAO,MAAM;AACf;AAEO,SAAS,mBAA2B;AACzC,SAAO,KAAK,IAAI,IAAI,MAAM;AAC5B;AAEO,SAAS,sBAA8B;AAC5C,SAAO,MAAM;AACf;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,IAAI,OAAO,MAAM,MAAM,MAAM,GAAG,EAAE,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AACvE;AAEO,SAAS,kBAA0B;AACxC,SAAO,MAAM;AAAA,IACX,eAAe,WAAW,MAAM,SAAS,CAAC;AAAA,wBACtB,eAAe,MAAM,gBAAgB,CAAC;AAAA,yBACrC,eAAe,iBAAiB,CAAC,CAAC;AAAA,EACzD;AACF;AAiBA,IAAI,wBAAwB;AAErB,SAAS,iBAAuB;AACrC,YAAU,MAAM;AAEd,QAAI,uBAAuB;AACzB;AAAA,IACF;AACA,4BAAwB;AAIxB;AAAA,MACE,MAAM;AAGJ,gBAAQ,OAAO,MAAM,OAAO,gBAAgB,IAAI,IAAI;AAGpD,YAAI;AACF,gBAAM,gBAAgB,wBAAwB;AAC9C,mCAAyB;AAAA,YACvB,GAAG;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,iBAAiB,MAAM;AAAA,YACvB,cAAc,iBAAiB;AAAA,YAC/B,eAAe;AAAA,UACjB,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAAA,MACA,EAAE,YAAY,KAAK;AAAA,IACrB;AAAA,EAGF,GAAG,CAAC,CAAC;AACP;AAEA,SAAS,MAAM,QAAgB,WAA2B;AACxD,SAAO,KAAK,MAAM,SAAS,SAAS,IAAI;AAC1C;AAGO,SAAS,qBAA2B;AACzC,MAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,YAAY;AAClB,QAAM,mBAAmB;AAC3B;",
4
+ "sourcesContent": ["import chalk from 'chalk'\nimport { useEffect } from 'react'\nimport signalExit from 'signal-exit'\nimport { formatDuration } from './utils/format'\n\n// DO NOT ADD MORE STATE HERE OR BORIS WILL CURSE YOU\nconst STATE: {\n totalCost: number\n totalAPIDuration: number\n startTime: number\n} = {\n totalCost: 0,\n totalAPIDuration: 0,\n startTime: Date.now(),\n}\n\nexport function addToTotalCost(cost: number, duration: number): void {\n STATE.totalCost += cost\n STATE.totalAPIDuration += duration\n}\n\nexport function getTotalCost(): number {\n return STATE.totalCost\n}\n\nexport function getTotalDuration(): number {\n return Date.now() - STATE.startTime\n}\n\nexport function getTotalAPIDuration(): number {\n return STATE.totalAPIDuration\n}\n\nfunction formatCost(cost: number): string {\n return `$${cost > 0.5 ? round(cost, 100).toFixed(2) : cost.toFixed(4)}`\n}\n\nexport function formatTotalCost(): string {\n return chalk.grey(\n `Total cost: ${formatCost(STATE.totalCost)}\nTotal duration (API): ${formatDuration(STATE.totalAPIDuration)}\nTotal duration (wall): ${formatDuration(getTotalDuration())}`,\n )\n}\n\n/**\n * useCostSummary - Register exit handler to display cost summary\n *\n * CRITICAL FIX: Use signal-exit with alwaysLast: true\n *\n * The problem was that Ink also uses signal-exit (with alwaysLast: false)\n * to clean up on exit. During Ink's cleanup, it may:\n * 1. Call onRender() one last time\n * 2. Use ansiEscapes.eraseLines() to clear previous output\n * 3. Or even call clearTerminal if output height >= terminal rows\n *\n * By using signal-exit with alwaysLast: true, we ensure our handler\n * runs AFTER Ink's cleanup, so our statistics are the last thing printed.\n */\n// Flag to ensure we only register once and never unregister\nlet exitHandlerRegistered = false\n\nexport function useCostSummary(): void {\n useEffect(() => {\n // Only register ONCE, and NEVER unregister\n if (exitHandlerRegistered) {\n return\n }\n exitHandlerRegistered = true\n\n // Use signal-exit with alwaysLast: true to run AFTER Ink's cleanup\n // This ensures our statistics are printed after Ink finishes its final render\n signalExit(\n () => {\n // Write statistics to stdout\n // Using stdout.write directly to avoid any Ink interception\n process.stdout.write('\\n' + formatTotalCost() + '\\n')\n },\n { alwaysLast: true },\n )\n\n // NO cleanup function - we NEVER want to unregister this handler\n }, [])\n}\n\nfunction round(number: number, precision: number): number {\n return Math.round(number * precision) / precision\n}\n\n// Only used in tests\nexport function resetStateForTests(): void {\n if (process.env.NODE_ENV !== 'test') {\n throw new Error('resetStateForTests can only be called in tests')\n }\n STATE.startTime = Date.now()\n STATE.totalCost = 0\n STATE.totalAPIDuration = 0\n}\n"],
5
+ "mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,iBAAiB;AAC1B,OAAO,gBAAgB;AACvB,SAAS,sBAAsB;AAG/B,MAAM,QAIF;AAAA,EACF,WAAW;AAAA,EACX,kBAAkB;AAAA,EAClB,WAAW,KAAK,IAAI;AACtB;AAEO,SAAS,eAAe,MAAc,UAAwB;AACnE,QAAM,aAAa;AACnB,QAAM,oBAAoB;AAC5B;AAEO,SAAS,eAAuB;AACrC,SAAO,MAAM;AACf;AAEO,SAAS,mBAA2B;AACzC,SAAO,KAAK,IAAI,IAAI,MAAM;AAC5B;AAEO,SAAS,sBAA8B;AAC5C,SAAO,MAAM;AACf;AAEA,SAAS,WAAW,MAAsB;AACxC,SAAO,IAAI,OAAO,MAAM,MAAM,MAAM,GAAG,EAAE,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;AACvE;AAEO,SAAS,kBAA0B;AACxC,SAAO,MAAM;AAAA,IACX,eAAe,WAAW,MAAM,SAAS,CAAC;AAAA,wBACtB,eAAe,MAAM,gBAAgB,CAAC;AAAA,yBACrC,eAAe,iBAAiB,CAAC,CAAC;AAAA,EACzD;AACF;AAiBA,IAAI,wBAAwB;AAErB,SAAS,iBAAuB;AACrC,YAAU,MAAM;AAEd,QAAI,uBAAuB;AACzB;AAAA,IACF;AACA,4BAAwB;AAIxB;AAAA,MACE,MAAM;AAGJ,gBAAQ,OAAO,MAAM,OAAO,gBAAgB,IAAI,IAAI;AAAA,MACtD;AAAA,MACA,EAAE,YAAY,KAAK;AAAA,IACrB;AAAA,EAGF,GAAG,CAAC,CAAC;AACP;AAEA,SAAS,MAAM,QAAgB,WAA2B;AACxD,SAAO,KAAK,MAAM,SAAS,SAAS,IAAI;AAC1C;AAGO,SAAS,qBAA2B;AACzC,MAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,YAAY;AAClB,QAAM,mBAAmB;AAC3B;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env -S node --no-warnings=ExperimentalWarning --enable-source-maps
2
+ import { EventEmitter } from "events";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
+ process.env.DEV = "false";
7
+ process.env["DEV"] = "false";
8
+ EventEmitter.defaultMaxListeners = 20;
9
+ try {
10
+ if (!process.env.YOGA_WASM_PATH) {
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const candidates = [
14
+ join(__dirname, "./yoga.wasm"),
15
+ // dist/entrypoints/yoga.wasm
16
+ join(__dirname, "../yoga.wasm"),
17
+ // dist/yoga.wasm
18
+ join(__dirname, "../../yoga.wasm")
19
+ // project root (dev mode)
20
+ ];
21
+ for (const candidate of candidates) {
22
+ if (existsSync(candidate)) {
23
+ process.env.YOGA_WASM_PATH = candidate;
24
+ break;
25
+ }
26
+ }
27
+ }
28
+ } catch {
29
+ }
30
+ async function main() {
31
+ try {
32
+ await import("./cli.js");
33
+ } catch (error) {
34
+ const err = error;
35
+ const message = err.message || "";
36
+ if (message.includes("react-devtools-core")) {
37
+ console.error("\n\u274C Failed to start: react-devtools-core not found");
38
+ console.error(
39
+ " This may be caused by DEV environment variable being set."
40
+ );
41
+ console.error(" Try running: unset DEV && minto\n");
42
+ } else if (message.includes("yoga") || message.includes("YOGA")) {
43
+ console.error("\n\u274C Failed to start: yoga-layout initialization failed");
44
+ console.error(" The yoga.wasm file may be missing or corrupted.");
45
+ console.error(" Try reinstalling: npm install -g @within-7/minto\n");
46
+ } else {
47
+ console.error("\u274C Failed to start:", message || err);
48
+ if (err.stack) {
49
+ console.error(err.stack);
50
+ }
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
55
+ main();
56
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/entrypoints/bootstrap.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env -S node --no-warnings=ExperimentalWarning --enable-source-maps\n\n/**\n * Bootstrap entry point for Minto CLI\n *\n * CRITICAL: This file MUST remain minimal with NO static imports of application code.\n *\n * This bootstrap exists because ESM static imports execute BEFORE the importing\n * module's code. We need certain initializations to happen BEFORE any application\n * modules are loaded:\n *\n * 1. DEV='false' - Ink checks process.env['DEV'] at module load time and tries to\n * import 'react-devtools-core' if DEV='true'. This package isn't bundled.\n *\n * 2. EventEmitter.defaultMaxListeners - Must be set before any modules create\n * EventEmitters to prevent \"MaxListenersExceededWarning\" during startup.\n *\n * 3. YOGA_WASM_PATH - Must be set before Ink initializes yoga-layout for proper\n * terminal layout rendering.\n *\n * Solution: Use dynamic import() to load cli.tsx AFTER all initializations.\n * Dynamic import() is evaluated at runtime, not at module load time.\n *\n * DO NOT add any static imports here except for Node.js built-ins that don't\n * chain to application code (directly or transitively).\n */\n\nimport { EventEmitter } from 'events'\nimport { fileURLToPath } from 'node:url'\nimport { dirname, join } from 'node:path'\nimport { existsSync } from 'node:fs'\n\n// ============================================================================\n// 1. Disable Ink's development mode\n// ============================================================================\n// Prevents Ink from trying to load react-devtools-core which isn't bundled.\n// Must use both syntaxes because Bun's --define only replaces dot syntax.\nprocess.env.DEV = 'false'\nprocess.env['DEV'] = 'false'\n\n// ============================================================================\n// 2. Increase EventEmitter max listeners\n// ============================================================================\n// Components like ModelConfig, ModelSelector use multiple useInput hooks which\n// add listeners. Default is 10, we increase to 20 to accommodate deep hierarchies.\n// Must be set before any modules create EventEmitters.\nEventEmitter.defaultMaxListeners = 20\n\n// ============================================================================\n// 3. Set YOGA_WASM_PATH for Ink layout engine\n// ============================================================================\n// Ink uses yoga-layout for terminal UI layout. The WASM file path must be set\n// before Ink initializes. Resolve relative to this file for both dev and dist.\ntry {\n if (!process.env.YOGA_WASM_PATH) {\n const __filename = fileURLToPath(import.meta.url)\n const __dirname = dirname(__filename)\n // Check multiple possible locations\n const candidates = [\n join(__dirname, './yoga.wasm'), // dist/entrypoints/yoga.wasm\n join(__dirname, '../yoga.wasm'), // dist/yoga.wasm\n join(__dirname, '../../yoga.wasm'), // project root (dev mode)\n ]\n for (const candidate of candidates) {\n if (existsSync(candidate)) {\n process.env.YOGA_WASM_PATH = candidate\n break\n }\n }\n }\n} catch {\n // Ignore errors - yoga will use fallback or error at runtime\n}\n\n// ============================================================================\n// Main: Dynamically import the CLI\n// ============================================================================\n// This ensures all static imports in cli.tsx see the initialized environment.\nasync function main(): Promise<void> {\n try {\n await import('./cli.js')\n } catch (error) {\n // Provide helpful error messages for common failures\n const err = error as Error\n const message = err.message || ''\n\n if (message.includes('react-devtools-core')) {\n console.error('\\n\u274C Failed to start: react-devtools-core not found')\n console.error(\n ' This may be caused by DEV environment variable being set.',\n )\n console.error(' Try running: unset DEV && minto\\n')\n } else if (message.includes('yoga') || message.includes('YOGA')) {\n console.error('\\n\u274C Failed to start: yoga-layout initialization failed')\n console.error(' The yoga.wasm file may be missing or corrupted.')\n console.error(' Try reinstalling: npm install -g @within-7/minto\\n')\n } else {\n console.error('\u274C Failed to start:', message || err)\n if (err.stack) {\n console.error(err.stack)\n }\n }\n process.exit(1)\n }\n}\n\nmain()\n"],
5
+ "mappings": ";AA2BA,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAC9B,SAAS,kBAAkB;AAO3B,QAAQ,IAAI,MAAM;AAClB,QAAQ,IAAI,KAAK,IAAI;AAQrB,aAAa,sBAAsB;AAOnC,IAAI;AACF,MAAI,CAAC,QAAQ,IAAI,gBAAgB;AAC/B,UAAM,aAAa,cAAc,YAAY,GAAG;AAChD,UAAM,YAAY,QAAQ,UAAU;AAEpC,UAAM,aAAa;AAAA,MACjB,KAAK,WAAW,aAAa;AAAA;AAAA,MAC7B,KAAK,WAAW,cAAc;AAAA;AAAA,MAC9B,KAAK,WAAW,iBAAiB;AAAA;AAAA,IACnC;AACA,eAAW,aAAa,YAAY;AAClC,UAAI,WAAW,SAAS,GAAG;AACzB,gBAAQ,IAAI,iBAAiB;AAC7B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF,QAAQ;AAER;AAMA,eAAe,OAAsB;AACnC,MAAI;AACF,UAAM,OAAO,UAAU;AAAA,EACzB,SAAS,OAAO;AAEd,UAAM,MAAM;AACZ,UAAM,UAAU,IAAI,WAAW;AAE/B,QAAI,QAAQ,SAAS,qBAAqB,GAAG;AAC3C,cAAQ,MAAM,yDAAoD;AAClE,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ,MAAM,sCAAsC;AAAA,IACtD,WAAW,QAAQ,SAAS,MAAM,KAAK,QAAQ,SAAS,MAAM,GAAG;AAC/D,cAAQ,MAAM,6DAAwD;AACtE,cAAQ,MAAM,oDAAoD;AAClE,cAAQ,MAAM,uDAAuD;AAAA,IACvE,OAAO;AACL,cAAQ,MAAM,2BAAsB,WAAW,GAAG;AAClD,UAAI,IAAI,OAAO;AACb,gBAAQ,MAAM,IAAI,KAAK;AAAA,MACzB;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK;",
6
+ "names": []
7
+ }