@within-7/minto 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/commands/agents/AgentsCommand.js +22 -24
  2. package/dist/commands/agents/AgentsCommand.js.map +2 -2
  3. package/dist/commands/context.js +2 -1
  4. package/dist/commands/context.js.map +2 -2
  5. package/dist/commands/export.js +2 -1
  6. package/dist/commands/export.js.map +2 -2
  7. package/dist/commands/mcp-interactive.js +7 -6
  8. package/dist/commands/mcp-interactive.js.map +2 -2
  9. package/dist/commands/model.js +3 -2
  10. package/dist/commands/model.js.map +2 -2
  11. package/dist/commands/permissions.js +4 -3
  12. package/dist/commands/permissions.js.map +2 -2
  13. package/dist/commands/plugin/AddMarketplaceForm.js +3 -2
  14. package/dist/commands/plugin/AddMarketplaceForm.js.map +2 -2
  15. package/dist/commands/plugin/ConfirmDialog.js +2 -1
  16. package/dist/commands/plugin/ConfirmDialog.js.map +2 -2
  17. package/dist/commands/plugin/ErrorView.js +2 -1
  18. package/dist/commands/plugin/ErrorView.js.map +2 -2
  19. package/dist/commands/plugin/InstalledPluginsByMarketplace.js +5 -4
  20. package/dist/commands/plugin/InstalledPluginsByMarketplace.js.map +2 -2
  21. package/dist/commands/plugin/InstalledPluginsManager.js +5 -4
  22. package/dist/commands/plugin/InstalledPluginsManager.js.map +2 -2
  23. package/dist/commands/plugin/MainMenu.js +2 -1
  24. package/dist/commands/plugin/MainMenu.js.map +2 -2
  25. package/dist/commands/plugin/MarketplaceManager.js +5 -4
  26. package/dist/commands/plugin/MarketplaceManager.js.map +2 -2
  27. package/dist/commands/plugin/MarketplaceSelector.js +4 -3
  28. package/dist/commands/plugin/MarketplaceSelector.js.map +2 -2
  29. package/dist/commands/plugin/PlaceholderScreen.js +3 -2
  30. package/dist/commands/plugin/PlaceholderScreen.js.map +2 -2
  31. package/dist/commands/plugin/PluginBrowser.js +6 -5
  32. package/dist/commands/plugin/PluginBrowser.js.map +2 -2
  33. package/dist/commands/plugin/PluginDetailsInstall.js +5 -4
  34. package/dist/commands/plugin/PluginDetailsInstall.js.map +2 -2
  35. package/dist/commands/plugin/PluginDetailsManage.js +4 -3
  36. package/dist/commands/plugin/PluginDetailsManage.js.map +2 -2
  37. package/dist/commands/plugin.js +16 -15
  38. package/dist/commands/plugin.js.map +2 -2
  39. package/dist/commands/sandbox.js +4 -3
  40. package/dist/commands/sandbox.js.map +2 -2
  41. package/dist/commands/setup.js +2 -1
  42. package/dist/commands/setup.js.map +2 -2
  43. package/dist/commands/status.js +2 -1
  44. package/dist/commands/status.js.map +2 -2
  45. package/dist/commands/undo.js +245 -0
  46. package/dist/commands/undo.js.map +7 -0
  47. package/dist/commands.js +2 -0
  48. package/dist/commands.js.map +2 -2
  49. package/dist/components/AgentThinkingBlock.js +1 -1
  50. package/dist/components/AgentThinkingBlock.js.map +2 -2
  51. package/dist/components/AsciiLogo.js +7 -8
  52. package/dist/components/AsciiLogo.js.map +2 -2
  53. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js +3 -2
  54. package/dist/components/AskUserQuestionDialog/AskUserQuestionDialog.js.map +2 -2
  55. package/dist/components/AskUserQuestionDialog/QuestionView.js +2 -1
  56. package/dist/components/AskUserQuestionDialog/QuestionView.js.map +2 -2
  57. package/dist/components/CollapsibleHint.js +2 -1
  58. package/dist/components/CollapsibleHint.js.map +2 -2
  59. package/dist/components/Config.js +3 -2
  60. package/dist/components/Config.js.map +2 -2
  61. package/dist/components/ConsoleOAuthFlow.js +2 -1
  62. package/dist/components/ConsoleOAuthFlow.js.map +2 -2
  63. package/dist/components/Cost.js +2 -1
  64. package/dist/components/Cost.js.map +2 -2
  65. package/dist/components/HeaderBar.js +13 -8
  66. package/dist/components/HeaderBar.js.map +2 -2
  67. package/dist/components/HistorySearchOverlay.js +4 -3
  68. package/dist/components/HistorySearchOverlay.js.map +2 -2
  69. package/dist/components/HotkeyHelpPanel.js +8 -11
  70. package/dist/components/HotkeyHelpPanel.js.map +2 -2
  71. package/dist/components/InvalidConfigDialog.js +2 -1
  72. package/dist/components/InvalidConfigDialog.js.map +2 -2
  73. package/dist/components/Logo.js +23 -67
  74. package/dist/components/Logo.js.map +2 -2
  75. package/dist/components/MCPServerApprovalDialog.js +2 -1
  76. package/dist/components/MCPServerApprovalDialog.js.map +2 -2
  77. package/dist/components/MCPServerDialogCopy.js +2 -1
  78. package/dist/components/MCPServerDialogCopy.js.map +2 -2
  79. package/dist/components/MCPServerMultiselectDialog.js +2 -1
  80. package/dist/components/MCPServerMultiselectDialog.js.map +2 -2
  81. package/dist/components/MessageSelector.js +4 -3
  82. package/dist/components/MessageSelector.js.map +2 -2
  83. package/dist/components/ModeIndicator.js +2 -1
  84. package/dist/components/ModeIndicator.js.map +2 -2
  85. package/dist/components/ModelConfig.js +4 -3
  86. package/dist/components/ModelConfig.js.map +2 -2
  87. package/dist/components/ModelListManager.js +4 -3
  88. package/dist/components/ModelListManager.js.map +2 -2
  89. package/dist/components/ModelSelector/ModelSelector.js +26 -13
  90. package/dist/components/ModelSelector/ModelSelector.js.map +2 -2
  91. package/dist/components/Onboarding.js +3 -2
  92. package/dist/components/Onboarding.js.map +2 -2
  93. package/dist/components/OperationSummary.js +130 -0
  94. package/dist/components/OperationSummary.js.map +7 -0
  95. package/dist/components/PromptInput.js +88 -75
  96. package/dist/components/PromptInput.js.map +2 -2
  97. package/dist/components/SensitiveFileWarning.js +31 -0
  98. package/dist/components/SensitiveFileWarning.js.map +7 -0
  99. package/dist/components/Spinner.js +71 -22
  100. package/dist/components/Spinner.js.map +2 -2
  101. package/dist/components/StructuredDiff.js +6 -8
  102. package/dist/components/StructuredDiff.js.map +2 -2
  103. package/dist/components/SubagentBlock.js +4 -2
  104. package/dist/components/SubagentBlock.js.map +2 -2
  105. package/dist/components/SubagentProgress.js +7 -4
  106. package/dist/components/SubagentProgress.js.map +2 -2
  107. package/dist/components/TaskCard.js +14 -11
  108. package/dist/components/TaskCard.js.map +2 -2
  109. package/dist/components/TextInput.js +9 -1
  110. package/dist/components/TextInput.js.map +2 -2
  111. package/dist/components/TodoPanel.js +44 -26
  112. package/dist/components/TodoPanel.js.map +2 -2
  113. package/dist/components/ToolUseLoader.js +2 -2
  114. package/dist/components/ToolUseLoader.js.map +2 -2
  115. package/dist/components/TreeConnector.js +4 -3
  116. package/dist/components/TreeConnector.js.map +2 -2
  117. package/dist/components/TrustDialog.js +2 -1
  118. package/dist/components/TrustDialog.js.map +2 -2
  119. package/dist/components/binary-feedback/BinaryFeedbackView.js +2 -1
  120. package/dist/components/binary-feedback/BinaryFeedbackView.js.map +2 -2
  121. package/dist/components/messages/AssistantTextMessage.js +17 -9
  122. package/dist/components/messages/AssistantTextMessage.js.map +2 -2
  123. package/dist/components/messages/AssistantToolUseMessage.js +8 -4
  124. package/dist/components/messages/AssistantToolUseMessage.js.map +2 -2
  125. package/dist/components/messages/GroupRenderer.js +2 -1
  126. package/dist/components/messages/GroupRenderer.js.map +2 -2
  127. package/dist/components/messages/NestedTasksPreview.js +13 -1
  128. package/dist/components/messages/NestedTasksPreview.js.map +2 -2
  129. package/dist/components/messages/ParallelTasksGroupView.js +4 -3
  130. package/dist/components/messages/ParallelTasksGroupView.js.map +2 -2
  131. package/dist/components/messages/TaskInModuleView.js +35 -15
  132. package/dist/components/messages/TaskInModuleView.js.map +2 -2
  133. package/dist/components/messages/TaskOutputContent.js +9 -6
  134. package/dist/components/messages/TaskOutputContent.js.map +2 -2
  135. package/dist/components/messages/UserPromptMessage.js +2 -2
  136. package/dist/components/messages/UserPromptMessage.js.map +2 -2
  137. package/dist/constants/colors.js +90 -72
  138. package/dist/constants/colors.js.map +2 -2
  139. package/dist/constants/toolInputExamples.js +84 -0
  140. package/dist/constants/toolInputExamples.js.map +7 -0
  141. package/dist/core/backupManager.js +321 -0
  142. package/dist/core/backupManager.js.map +7 -0
  143. package/dist/core/costTracker.js +9 -18
  144. package/dist/core/costTracker.js.map +2 -2
  145. package/dist/core/gitAutoCommit.js +287 -0
  146. package/dist/core/gitAutoCommit.js.map +7 -0
  147. package/dist/core/index.js +3 -0
  148. package/dist/core/index.js.map +2 -2
  149. package/dist/core/operationTracker.js +212 -0
  150. package/dist/core/operationTracker.js.map +7 -0
  151. package/dist/core/permissions/rules/allowedToolsRule.js +1 -1
  152. package/dist/core/permissions/rules/allowedToolsRule.js.map +2 -2
  153. package/dist/core/permissions/rules/autoEscalationRule.js +5 -0
  154. package/dist/core/permissions/rules/autoEscalationRule.js.map +2 -2
  155. package/dist/core/permissions/rules/projectBoundaryRule.js +5 -0
  156. package/dist/core/permissions/rules/projectBoundaryRule.js.map +2 -2
  157. package/dist/core/permissions/rules/sensitivePathsRule.js +5 -0
  158. package/dist/core/permissions/rules/sensitivePathsRule.js.map +2 -2
  159. package/dist/core/tokenStats.js +9 -0
  160. package/dist/core/tokenStats.js.map +7 -0
  161. package/dist/core/tokenStatsManager.js +331 -0
  162. package/dist/core/tokenStatsManager.js.map +7 -0
  163. package/dist/entrypoints/cli.js +115 -87
  164. package/dist/entrypoints/cli.js.map +2 -2
  165. package/dist/hooks/useAgentTokenStats.js +72 -0
  166. package/dist/hooks/useAgentTokenStats.js.map +7 -0
  167. package/dist/hooks/useAgentTranscripts.js +30 -6
  168. package/dist/hooks/useAgentTranscripts.js.map +2 -2
  169. package/dist/hooks/useLogMessages.js +12 -1
  170. package/dist/hooks/useLogMessages.js.map +2 -2
  171. package/dist/i18n/locales/en.js +6 -5
  172. package/dist/i18n/locales/en.js.map +2 -2
  173. package/dist/i18n/locales/zh-CN.js +6 -5
  174. package/dist/i18n/locales/zh-CN.js.map +2 -2
  175. package/dist/i18n/types.js.map +1 -1
  176. package/dist/permissions.js +28 -1
  177. package/dist/permissions.js.map +2 -2
  178. package/dist/query.js +78 -4
  179. package/dist/query.js.map +3 -3
  180. package/dist/screens/REPL.js +23 -3
  181. package/dist/screens/REPL.js.map +2 -2
  182. package/dist/services/claude.js +54 -3
  183. package/dist/services/claude.js.map +2 -2
  184. package/dist/services/intelligentCompactor.js +1 -1
  185. package/dist/services/intelligentCompactor.js.map +2 -2
  186. package/dist/services/mcpClient.js +81 -25
  187. package/dist/services/mcpClient.js.map +2 -2
  188. package/dist/services/sandbox/filesystemBoundary.js +58 -17
  189. package/dist/services/sandbox/filesystemBoundary.js.map +2 -2
  190. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js +3 -2
  191. package/dist/tools/AskExpertModelTool/AskExpertModelTool.js.map +2 -2
  192. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js +2 -1
  193. package/dist/tools/AskUserQuestionTool/AskUserQuestionTool.js.map +2 -2
  194. package/dist/tools/BashTool/BashTool.js +22 -3
  195. package/dist/tools/BashTool/BashTool.js.map +2 -2
  196. package/dist/tools/BashTool/prompt.js +178 -34
  197. package/dist/tools/BashTool/prompt.js.map +2 -2
  198. package/dist/tools/FileEditTool/prompt.js +6 -3
  199. package/dist/tools/FileEditTool/prompt.js.map +2 -2
  200. package/dist/tools/FileWriteTool/prompt.js +4 -2
  201. package/dist/tools/FileWriteTool/prompt.js.map +2 -2
  202. package/dist/tools/MultiEditTool/prompt.js +5 -3
  203. package/dist/tools/MultiEditTool/prompt.js.map +2 -2
  204. package/dist/tools/NotebookEditTool/NotebookEditTool.js +2 -1
  205. package/dist/tools/NotebookEditTool/NotebookEditTool.js.map +2 -2
  206. package/dist/tools/PlanModeTool/EnterPlanModeTool.js +3 -2
  207. package/dist/tools/PlanModeTool/EnterPlanModeTool.js.map +2 -2
  208. package/dist/tools/PlanModeTool/ExitPlanModeTool.js +3 -2
  209. package/dist/tools/PlanModeTool/ExitPlanModeTool.js.map +2 -2
  210. package/dist/tools/PlanModeTool/prompt.js +1 -1
  211. package/dist/tools/PlanModeTool/prompt.js.map +1 -1
  212. package/dist/tools/SkillTool/SkillTool.js +4 -3
  213. package/dist/tools/SkillTool/SkillTool.js.map +2 -2
  214. package/dist/tools/SkillTool/prompt.js +1 -1
  215. package/dist/tools/SkillTool/prompt.js.map +1 -1
  216. package/dist/tools/TaskOutputTool/TaskOutputTool.js +3 -2
  217. package/dist/tools/TaskOutputTool/TaskOutputTool.js.map +2 -2
  218. package/dist/tools/TaskTool/TaskTool.js +8 -0
  219. package/dist/tools/TaskTool/TaskTool.js.map +2 -2
  220. package/dist/utils/CircuitBreaker.js +242 -0
  221. package/dist/utils/CircuitBreaker.js.map +7 -0
  222. package/dist/utils/ask.js +2 -0
  223. package/dist/utils/ask.js.map +2 -2
  224. package/dist/utils/config.js +47 -5
  225. package/dist/utils/config.js.map +2 -2
  226. package/dist/utils/credentials/CredentialStore.js +1 -0
  227. package/dist/utils/credentials/CredentialStore.js.map +7 -0
  228. package/dist/utils/credentials/EncryptedFileStore.js +157 -0
  229. package/dist/utils/credentials/EncryptedFileStore.js.map +7 -0
  230. package/dist/utils/credentials/index.js +37 -0
  231. package/dist/utils/credentials/index.js.map +7 -0
  232. package/dist/utils/credentials/migration.js +82 -0
  233. package/dist/utils/credentials/migration.js.map +7 -0
  234. package/dist/utils/markdown.js +13 -1
  235. package/dist/utils/markdown.js.map +2 -2
  236. package/dist/utils/permissions/filesystem.js +5 -1
  237. package/dist/utils/permissions/filesystem.js.map +2 -2
  238. package/dist/utils/safePath.js +132 -0
  239. package/dist/utils/safePath.js.map +7 -0
  240. package/dist/utils/sensitiveFiles.js +125 -0
  241. package/dist/utils/sensitiveFiles.js.map +7 -0
  242. package/dist/utils/taskDisplayUtils.js +9 -9
  243. package/dist/utils/taskDisplayUtils.js.map +2 -2
  244. package/dist/utils/theme.js +6 -6
  245. package/dist/utils/theme.js.map +1 -1
  246. package/dist/utils/toolRiskClassification.js +207 -0
  247. package/dist/utils/toolRiskClassification.js.map +7 -0
  248. package/dist/utils/tooling/safeRender.js +5 -4
  249. package/dist/utils/tooling/safeRender.js.map +2 -2
  250. package/dist/version.js +2 -2
  251. package/dist/version.js.map +1 -1
  252. package/package.json +9 -7
  253. package/dist/hooks/useCancelRequest.js +0 -31
  254. package/dist/hooks/useCancelRequest.js.map +0 -7
@@ -0,0 +1,331 @@
1
+ import { EventEmitter } from "events";
2
+ import { DEFAULT_ESTIMATION_CONFIG } from "./tokenStats.js";
3
+ class TokenStatsManagerImpl extends EventEmitter {
4
+ static instance = null;
5
+ // Global aggregated stats
6
+ globalStats = this.createEmptyStats();
7
+ // Per-agent stats (agentId → stats)
8
+ agentStats = /* @__PURE__ */ new Map();
9
+ // Per-request records (requestId → record) - for debugging, with LRU eviction
10
+ requestRecords = /* @__PURE__ */ new Map();
11
+ // Configuration
12
+ static MAX_REQUEST_RECORDS = 1e3;
13
+ constructor() {
14
+ super();
15
+ this.setMaxListeners(100);
16
+ }
17
+ /**
18
+ * Get the singleton instance
19
+ */
20
+ static getInstance() {
21
+ if (!TokenStatsManagerImpl.instance) {
22
+ TokenStatsManagerImpl.instance = new TokenStatsManagerImpl();
23
+ }
24
+ return TokenStatsManagerImpl.instance;
25
+ }
26
+ /**
27
+ * Record token usage from an API response
28
+ *
29
+ * This is the PRIMARY entry point - called from claude.ts
30
+ * after every API response.
31
+ *
32
+ * @param usage - Raw token usage from API
33
+ * @param costUSD - Calculated cost in USD
34
+ * @param model - Model name used
35
+ * @param context - Optional tracking context (agentId, toolUseId)
36
+ */
37
+ recordUsage(usage, costUSD, model, context) {
38
+ const record = {
39
+ inputTokens: usage.inputTokens,
40
+ outputTokens: usage.outputTokens,
41
+ cacheCreationTokens: usage.cacheCreationTokens ?? 0,
42
+ cacheReadTokens: usage.cacheReadTokens ?? 0,
43
+ totalTokens: usage.inputTokens + usage.outputTokens,
44
+ estimatedCostUSD: costUSD,
45
+ source: "api",
46
+ timestamp: Date.now(),
47
+ model
48
+ };
49
+ this.aggregateInto(this.globalStats, record);
50
+ if (context?.agentId) {
51
+ let stats = this.agentStats.get(context.agentId);
52
+ if (!stats) {
53
+ stats = this.createEmptyStats();
54
+ this.agentStats.set(context.agentId, stats);
55
+ }
56
+ this.aggregateInto(stats, record);
57
+ }
58
+ const requestId = context?.toolUseId ?? `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
59
+ this.requestRecords.set(requestId, record);
60
+ if (this.requestRecords.size > TokenStatsManagerImpl.MAX_REQUEST_RECORDS) {
61
+ const oldestKey = this.requestRecords.keys().next().value;
62
+ if (oldestKey) this.requestRecords.delete(oldestKey);
63
+ }
64
+ const scope = context?.agentId ? { type: "agent", agentId: context.agentId } : { type: "global" };
65
+ this.emitEvent({
66
+ eventType: "usage_recorded",
67
+ scope,
68
+ usage: record,
69
+ aggregated: context?.agentId ? this.agentStats.get(context.agentId) : this.globalStats,
70
+ globalStats: this.globalStats
71
+ });
72
+ }
73
+ /**
74
+ * Estimate token count from text
75
+ *
76
+ * Uses a simple character-based estimation. This is a fallback
77
+ * when the API doesn't return token counts.
78
+ *
79
+ * @param text - Text to estimate tokens for
80
+ * @returns Estimated token count
81
+ */
82
+ estimateTokens(text) {
83
+ return Math.ceil(text.length / DEFAULT_ESTIMATION_CONFIG.charsPerToken);
84
+ }
85
+ /**
86
+ * Record estimated usage when API doesn't return token counts
87
+ *
88
+ * This is used as a fallback for APIs that don't return usage data.
89
+ *
90
+ * @param inputText - Input text (prompt)
91
+ * @param outputText - Output text (completion)
92
+ * @param model - Model name
93
+ * @param context - Optional tracking context
94
+ */
95
+ recordEstimatedUsage(inputText, outputText, model, context) {
96
+ const inputTokens = this.estimateTokens(inputText);
97
+ const outputTokens = this.estimateTokens(outputText);
98
+ const costUSD = inputTokens / 1e6 * DEFAULT_ESTIMATION_CONFIG.defaultInputCostPerMillion + outputTokens / 1e6 * DEFAULT_ESTIMATION_CONFIG.defaultOutputCostPerMillion;
99
+ const record = {
100
+ inputTokens,
101
+ outputTokens,
102
+ cacheCreationTokens: 0,
103
+ cacheReadTokens: 0,
104
+ totalTokens: inputTokens + outputTokens,
105
+ estimatedCostUSD: costUSD,
106
+ source: "estimated",
107
+ timestamp: Date.now(),
108
+ model
109
+ };
110
+ this.aggregateInto(this.globalStats, record);
111
+ if (context?.agentId) {
112
+ let stats = this.agentStats.get(context.agentId);
113
+ if (!stats) {
114
+ stats = this.createEmptyStats();
115
+ this.agentStats.set(context.agentId, stats);
116
+ }
117
+ this.aggregateInto(stats, record);
118
+ }
119
+ this.emitEvent({
120
+ eventType: "usage_recorded",
121
+ scope: { type: "global" },
122
+ usage: record,
123
+ aggregated: this.globalStats,
124
+ globalStats: this.globalStats
125
+ });
126
+ }
127
+ // ===== Getters =====
128
+ /**
129
+ * Get global aggregated statistics
130
+ */
131
+ getGlobalStats() {
132
+ return this.cloneStats(this.globalStats);
133
+ }
134
+ /**
135
+ * Get statistics for a specific agent
136
+ *
137
+ * @param agentId - Agent ID to get stats for
138
+ * @returns Agent stats or null if not found
139
+ */
140
+ getAgentStats(agentId) {
141
+ const stats = this.agentStats.get(agentId);
142
+ return stats ? this.cloneStats(stats) : null;
143
+ }
144
+ /**
145
+ * Get all agent statistics
146
+ */
147
+ getAllAgentStats() {
148
+ const result = /* @__PURE__ */ new Map();
149
+ for (const [agentId, stats] of this.agentStats) {
150
+ result.set(agentId, this.cloneStats(stats));
151
+ }
152
+ return result;
153
+ }
154
+ /**
155
+ * Get a specific request record
156
+ */
157
+ getRequestRecord(requestId) {
158
+ return this.requestRecords.get(requestId) ?? null;
159
+ }
160
+ // ===== Backward Compatibility Layer =====
161
+ /**
162
+ * Get total tokens (for costTracker.getTokenCounts compatibility)
163
+ *
164
+ * This method provides backward compatibility with the existing
165
+ * costTracker.getTokenCounts() interface.
166
+ */
167
+ getTokenCounts() {
168
+ return {
169
+ input: this.globalStats.totalInputTokens,
170
+ output: this.globalStats.totalOutputTokens,
171
+ cacheCreation: this.globalStats.totalCacheCreationTokens,
172
+ cacheRead: this.globalStats.totalCacheReadTokens,
173
+ total: this.globalStats.grandTotalTokens
174
+ };
175
+ }
176
+ // ===== Event Subscription =====
177
+ /**
178
+ * Subscribe to all stats updates
179
+ *
180
+ * @param callback - Called when any stats change
181
+ * @returns Unsubscribe function
182
+ */
183
+ onStatsUpdate(callback) {
184
+ this.on("stats_update", callback);
185
+ return () => this.off("stats_update", callback);
186
+ }
187
+ /**
188
+ * Subscribe to updates for a specific agent
189
+ *
190
+ * @param agentId - Agent ID to subscribe to
191
+ * @param callback - Called when agent stats change
192
+ * @returns Unsubscribe function
193
+ */
194
+ onAgentStatsUpdate(agentId, callback) {
195
+ const handler = (event) => {
196
+ if (event.scope.type === "agent" && event.scope.agentId === agentId) {
197
+ callback(event.aggregated);
198
+ }
199
+ };
200
+ this.on("stats_update", handler);
201
+ return () => this.off("stats_update", handler);
202
+ }
203
+ /**
204
+ * Subscribe to global stats updates only
205
+ *
206
+ * @param callback - Called when global stats change
207
+ * @returns Unsubscribe function
208
+ */
209
+ onGlobalStatsUpdate(callback) {
210
+ const handler = (event) => {
211
+ callback(event.globalStats);
212
+ };
213
+ this.on("stats_update", handler);
214
+ return () => this.off("stats_update", handler);
215
+ }
216
+ // ===== Agent Lifecycle =====
217
+ /**
218
+ * Mark an agent's scope as completed
219
+ *
220
+ * This emits a scope_completed event and optionally
221
+ * cleans up the agent's stats from memory.
222
+ *
223
+ * @param agentId - Agent ID to complete
224
+ * @param cleanup - Whether to remove from memory (default: false)
225
+ */
226
+ completeAgentScope(agentId, cleanup = false) {
227
+ const stats = this.agentStats.get(agentId);
228
+ if (!stats) return;
229
+ this.emitEvent({
230
+ eventType: "scope_completed",
231
+ scope: { type: "agent", agentId },
232
+ aggregated: stats,
233
+ globalStats: this.globalStats
234
+ });
235
+ if (cleanup) {
236
+ this.agentStats.delete(agentId);
237
+ }
238
+ }
239
+ // ===== Internal Methods =====
240
+ createEmptyStats() {
241
+ return {
242
+ totalInputTokens: 0,
243
+ totalOutputTokens: 0,
244
+ totalCacheCreationTokens: 0,
245
+ totalCacheReadTokens: 0,
246
+ grandTotalTokens: 0,
247
+ totalCostUSD: 0,
248
+ requestCount: 0,
249
+ byModel: /* @__PURE__ */ new Map()
250
+ };
251
+ }
252
+ aggregateInto(stats, record) {
253
+ stats.totalInputTokens += record.inputTokens;
254
+ stats.totalOutputTokens += record.outputTokens;
255
+ stats.totalCacheCreationTokens += record.cacheCreationTokens;
256
+ stats.totalCacheReadTokens += record.cacheReadTokens;
257
+ stats.grandTotalTokens += record.totalTokens;
258
+ stats.totalCostUSD += record.estimatedCostUSD;
259
+ stats.requestCount += 1;
260
+ if (!stats.firstRequestTime) {
261
+ stats.firstRequestTime = record.timestamp;
262
+ }
263
+ stats.lastRequestTime = record.timestamp;
264
+ const modelStats = stats.byModel.get(record.model);
265
+ if (modelStats) {
266
+ modelStats.inputTokens += record.inputTokens;
267
+ modelStats.outputTokens += record.outputTokens;
268
+ modelStats.totalTokens += record.totalTokens;
269
+ modelStats.estimatedCostUSD += record.estimatedCostUSD;
270
+ modelStats.requestCount += 1;
271
+ } else {
272
+ stats.byModel.set(record.model, {
273
+ model: record.model,
274
+ inputTokens: record.inputTokens,
275
+ outputTokens: record.outputTokens,
276
+ totalTokens: record.totalTokens,
277
+ estimatedCostUSD: record.estimatedCostUSD,
278
+ requestCount: 1
279
+ });
280
+ }
281
+ }
282
+ cloneStats(stats) {
283
+ return {
284
+ ...stats,
285
+ byModel: new Map(stats.byModel)
286
+ };
287
+ }
288
+ emitEvent(event) {
289
+ this.emit("stats_update", event);
290
+ }
291
+ /**
292
+ * Reset all stats (for testing only)
293
+ */
294
+ resetForTests() {
295
+ if (process.env.NODE_ENV !== "test") {
296
+ throw new Error("resetForTests can only be called in tests");
297
+ }
298
+ this.reset();
299
+ }
300
+ /**
301
+ * Reset all statistics
302
+ *
303
+ * Called by costTracker.resetStateForTests() for backward compatibility.
304
+ */
305
+ reset() {
306
+ this.globalStats = this.createEmptyStats();
307
+ this.agentStats.clear();
308
+ this.requestRecords.clear();
309
+ }
310
+ }
311
+ const tokenStatsManager = TokenStatsManagerImpl.getInstance();
312
+ function recordTokenUsage(usage, costUSD, model, context) {
313
+ tokenStatsManager.recordUsage(usage, costUSD, model, context);
314
+ }
315
+ function getGlobalTokenStats() {
316
+ return tokenStatsManager.getGlobalStats();
317
+ }
318
+ function getAgentTokenStats(agentId) {
319
+ return tokenStatsManager.getAgentStats(agentId);
320
+ }
321
+ function estimateTokenCount(text) {
322
+ return tokenStatsManager.estimateTokens(text);
323
+ }
324
+ export {
325
+ estimateTokenCount,
326
+ getAgentTokenStats,
327
+ getGlobalTokenStats,
328
+ recordTokenUsage,
329
+ tokenStatsManager
330
+ };
331
+ //# sourceMappingURL=tokenStatsManager.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 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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",
6
+ "names": []
7
+ }
@@ -52,7 +52,9 @@ import { dateToFilename, logError, parseLogFilename } from "../utils/log.js";
52
52
  import { initDebugLogger } from "../utils/debugLogger.js";
53
53
  import { Onboarding } from "../components/Onboarding.js";
54
54
  import { TrustDialog } from "../components/TrustDialog.js";
55
- import { checkHasTrustDialogAccepted } from "../utils/config.js";
55
+ import {
56
+ checkHasTrustDialogAccepted
57
+ } from "../utils/config.js";
56
58
  import { isDefaultSlowAndCapableModel } from "../utils/model.js";
57
59
  import { LogList } from "../screens/LogList.js";
58
60
  import { ResumeConversation } from "../screens/ResumeConversation.js";
@@ -98,6 +100,7 @@ import { showInvalidConfigDialog } from "../components/InvalidConfigDialog.js";
98
100
  import { ConfigParseError } from "../utils/errors.js";
99
101
  import { grantReadPermissionForOriginalDir } from "../utils/permissions/filesystem.js";
100
102
  import { MACRO } from "../constants/macros.js";
103
+ import { SEMANTIC_COLORS } from "../constants/colors.js";
101
104
  function completeOnboarding() {
102
105
  const config = getGlobalConfig();
103
106
  saveGlobalConfig({
@@ -366,8 +369,12 @@ ${commandList}`
366
369
  "Print response and exit (useful for pipes)",
367
370
  () => true
368
371
  ).option(
369
- "--safe",
370
- "Enable strict permission checking mode (default is permissive)",
372
+ "--smart",
373
+ "Enable Smart safety mode (dangerous operations need confirmation)",
374
+ () => true
375
+ ).option(
376
+ "--strict",
377
+ "Enable Strict safety mode (all write operations need confirmation)",
371
378
  () => true
372
379
  ).option(
373
380
  "-n, --new",
@@ -388,7 +395,8 @@ ${commandList}`
388
395
  verbose,
389
396
  enableArchitect,
390
397
  print,
391
- safe,
398
+ smart,
399
+ strict,
392
400
  new: startNew,
393
401
  resume: selectResume,
394
402
  lang
@@ -396,8 +404,11 @@ ${commandList}`
396
404
  if (lang && isLanguageSupported(lang)) {
397
405
  setLanguage(lang);
398
406
  }
399
- await showSetupScreens(safe, print, rawModeSupported);
400
- await setup(cwd2, safe);
407
+ const envSafetyMode = process.env.MINTO_SAFETY_MODE;
408
+ const safetyMode = envSafetyMode ? envSafetyMode : strict ? "strict" : smart ? "smart" : "yolo";
409
+ const safeMode = safetyMode !== "yolo";
410
+ await showSetupScreens(safeMode, print, rawModeSupported);
411
+ await setup(cwd2, safeMode);
401
412
  assertMinVersion();
402
413
  const [tools, mcpClients] = await Promise.all([
403
414
  getTools(
@@ -421,7 +432,8 @@ ${commandList}`
421
432
  prompt: inputPrompt,
422
433
  cwd: cwd2,
423
434
  tools,
424
- safeMode: safe
435
+ safeMode,
436
+ safetyMode
425
437
  });
426
438
  console.log(response);
427
439
  process.exit(0);
@@ -496,6 +508,11 @@ ${commandList}`
496
508
  {
497
509
  const { render } = await import("ink");
498
510
  const { REPL } = await import("../screens/REPL.js");
511
+ if (process.stdout.isTTY) {
512
+ await new Promise((resolve) => {
513
+ process.stdout.write("\x1B[?25h\n", () => resolve());
514
+ });
515
+ }
499
516
  render(
500
517
  /* @__PURE__ */ React.createElement(
501
518
  REPL,
@@ -509,7 +526,8 @@ ${commandList}`
509
526
  shouldShowPromptInput: true,
510
527
  verbose,
511
528
  tools,
512
- safeMode: safe,
529
+ safeMode,
530
+ safetyMode,
513
531
  mcpClients,
514
532
  isDefaultModel,
515
533
  initialUpdateVersion: updateInfo.version,
@@ -980,7 +998,7 @@ ${commandList}`
980
998
  onSubmit: handleConfirm
981
999
  }
982
1000
  ))
983
- ), /* @__PURE__ */ React2.createElement(Box, { marginTop: 0, marginLeft: 3 }, /* @__PURE__ */ React2.createElement(Text, { dimColor: true }, "Space to select \xB7 Enter to confirm \xB7 Esc to cancel")), isFinished && /* @__PURE__ */ React2.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text, { color: theme.success }, "Successfully imported", " ", importResults.filter((r) => r.success).length, " MCP server to local config.")));
1001
+ ), /* @__PURE__ */ React2.createElement(Box, { marginTop: 0, marginLeft: 3 }, /* @__PURE__ */ React2.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Space to select \xB7 Enter to confirm \xB7 Esc to cancel")), isFinished && /* @__PURE__ */ React2.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text, { color: theme.success }, "Successfully imported", " ", importResults.filter((r) => r.success).length, " MCP server to local config.")));
984
1002
  }
985
1003
  const { unmount } = render(/* @__PURE__ */ React2.createElement(ClaudeDesktopImport, null));
986
1004
  setTimeout(() => {
@@ -1065,98 +1083,108 @@ ${commandList}`
1065
1083
  "[identifier]",
1066
1084
  "A number (0, 1, 2, etc.) or file path to resume a specific conversation"
1067
1085
  ).option("-c, --cwd <cwd>", "The current working directory", String, cwd()).option("-e, --enable-architect", "Enable the Architect tool", () => true).option("-v, --verbose", "Do not truncate message output", () => true).option(
1068
- "--safe",
1069
- "Enable strict permission checking mode (default is permissive)",
1086
+ "--smart",
1087
+ "Enable Smart safety mode (dangerous operations need confirmation)",
1070
1088
  () => true
1071
- ).action(async (identifier, { cwd: cwd2, enableArchitect, safe, verbose }) => {
1072
- await setup(cwd2, safe);
1073
- assertMinVersion();
1074
- const [tools, commands2, logs, mcpClients] = await Promise.all([
1075
- getTools(
1076
- enableArchitect ?? getCurrentProjectConfig().enableArchitectTool
1077
- ),
1078
- getCommands(),
1079
- loadLogList(CACHE_PATHS.messages()),
1080
- getClients()
1081
- ]);
1082
- if (identifier !== void 0) {
1083
- const number = Math.abs(parseInt(identifier));
1084
- const isNumber = !isNaN(number);
1085
- let messages, date, forkNumber;
1086
- try {
1087
- if (isNumber) {
1088
- const log = logs[number];
1089
- if (!log) {
1090
- console.error("No conversation found at index", number);
1091
- process.exit(1);
1089
+ ).option(
1090
+ "--strict",
1091
+ "Enable Strict safety mode (all write operations need confirmation)",
1092
+ () => true
1093
+ ).action(
1094
+ async (identifier, { cwd: cwd2, enableArchitect, smart, strict, verbose }) => {
1095
+ const envSafetyMode = process.env.MINTO_SAFETY_MODE;
1096
+ const safetyMode = envSafetyMode ? envSafetyMode : strict ? "strict" : smart ? "smart" : "yolo";
1097
+ const safeMode = safetyMode !== "yolo";
1098
+ await setup(cwd2, safeMode);
1099
+ assertMinVersion();
1100
+ const [tools, commands2, logs, mcpClients] = await Promise.all([
1101
+ getTools(
1102
+ enableArchitect ?? getCurrentProjectConfig().enableArchitectTool
1103
+ ),
1104
+ getCommands(),
1105
+ loadLogList(CACHE_PATHS.messages()),
1106
+ getClients()
1107
+ ]);
1108
+ if (identifier !== void 0) {
1109
+ const number = Math.abs(parseInt(identifier));
1110
+ const isNumber = !isNaN(number);
1111
+ let messages, date, forkNumber;
1112
+ try {
1113
+ if (isNumber) {
1114
+ const log = logs[number];
1115
+ if (!log) {
1116
+ console.error("No conversation found at index", number);
1117
+ process.exit(1);
1118
+ }
1119
+ messages = await loadMessagesFromLog(log.fullPath, tools);
1120
+ ({ date, forkNumber } = log);
1121
+ } else {
1122
+ if (!existsSync(identifier)) {
1123
+ console.error("File does not exist:", identifier);
1124
+ process.exit(1);
1125
+ }
1126
+ messages = await loadMessagesFromLog(identifier, tools);
1127
+ const pathSegments = identifier.split("/");
1128
+ const filename = pathSegments[pathSegments.length - 1] ?? "unknown";
1129
+ ({ date, forkNumber } = parseLogFilename(filename));
1092
1130
  }
1093
- messages = await loadMessagesFromLog(log.fullPath, tools);
1094
- ({ date, forkNumber } = log);
1095
- } else {
1096
- if (!existsSync(identifier)) {
1097
- console.error("File does not exist:", identifier);
1098
- process.exit(1);
1131
+ const fork = getNextAvailableLogForkNumber(date, forkNumber ?? 1, 0);
1132
+ const isDefaultModel = await isDefaultSlowAndCapableModel();
1133
+ const forkRawModeCheck = validateRawModeSupport(process.stdin);
1134
+ if (!forkRawModeCheck.supported) {
1135
+ requireRawModeOrExit(process.stdin, true);
1099
1136
  }
1100
- messages = await loadMessagesFromLog(identifier, tools);
1101
- const pathSegments = identifier.split("/");
1102
- const filename = pathSegments[pathSegments.length - 1] ?? "unknown";
1103
- ({ date, forkNumber } = parseLogFilename(filename));
1104
- }
1105
- const fork = getNextAvailableLogForkNumber(date, forkNumber ?? 1, 0);
1106
- const isDefaultModel = await isDefaultSlowAndCapableModel();
1107
- const forkRawModeCheck = validateRawModeSupport(process.stdin);
1108
- if (!forkRawModeCheck.supported) {
1109
- requireRawModeOrExit(process.stdin, true);
1137
+ {
1138
+ const { render } = await import("ink");
1139
+ const { REPL } = await import("../screens/REPL.js");
1140
+ render(
1141
+ /* @__PURE__ */ React.createElement(
1142
+ REPL,
1143
+ {
1144
+ initialPrompt: "",
1145
+ messageLogName: date,
1146
+ initialForkNumber: fork,
1147
+ shouldShowPromptInput: true,
1148
+ verbose,
1149
+ commands: commands2,
1150
+ tools,
1151
+ safeMode,
1152
+ safetyMode,
1153
+ initialMessages: messages,
1154
+ mcpClients,
1155
+ isDefaultModel,
1156
+ fallbackMode: false
1157
+ }
1158
+ ),
1159
+ { exitOnCtrlC: false }
1160
+ );
1161
+ }
1162
+ } catch (error) {
1163
+ logError(`Failed to load conversation: ${error}`);
1164
+ process.exit(1);
1110
1165
  }
1111
- {
1166
+ } else {
1167
+ const context2 = {};
1168
+ (async () => {
1112
1169
  const { render } = await import("ink");
1113
- const { REPL } = await import("../screens/REPL.js");
1114
- render(
1170
+ const { unmount } = render(
1115
1171
  /* @__PURE__ */ React.createElement(
1116
- REPL,
1172
+ ResumeConversation,
1117
1173
  {
1118
- initialPrompt: "",
1119
- messageLogName: date,
1120
- initialForkNumber: fork,
1121
- shouldShowPromptInput: true,
1122
- verbose,
1174
+ context: context2,
1123
1175
  commands: commands2,
1176
+ logs,
1124
1177
  tools,
1125
- safeMode: safe,
1126
- initialMessages: messages,
1127
- mcpClients,
1128
- isDefaultModel,
1129
- fallbackMode: false
1178
+ verbose
1130
1179
  }
1131
1180
  ),
1132
- { exitOnCtrlC: false }
1181
+ renderContextWithExitOnCtrlC
1133
1182
  );
1134
- }
1135
- } catch (error) {
1136
- logError(`Failed to load conversation: ${error}`);
1137
- process.exit(1);
1183
+ context2.unmount = unmount;
1184
+ })();
1138
1185
  }
1139
- } else {
1140
- const context2 = {};
1141
- (async () => {
1142
- const { render } = await import("ink");
1143
- const { unmount } = render(
1144
- /* @__PURE__ */ React.createElement(
1145
- ResumeConversation,
1146
- {
1147
- context: context2,
1148
- commands: commands2,
1149
- logs,
1150
- tools,
1151
- verbose
1152
- }
1153
- ),
1154
- renderContextWithExitOnCtrlC
1155
- );
1156
- context2.unmount = unmount;
1157
- })();
1158
1186
  }
1159
- });
1187
+ );
1160
1188
  program.command("error").description(
1161
1189
  "View error logs. Optionally provide a number (0, -1, -2, etc.) to display a specific log."
1162
1190
  ).argument(