@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
@@ -1,33 +1,268 @@
1
- import * as React from "react";
2
- import { ResumeConversation } from "../screens/ResumeConversation.js";
1
+ import React, { useState, useMemo } from "react";
3
2
  import { render } from "ink";
3
+ import path from "path";
4
+ import { t } from "../i18n/index.js";
5
+ import { getCwd } from "../utils/state.js";
4
6
  import { CACHE_PATHS, loadLogList } from "../utils/log.js";
7
+ import { deserializeMessages } from "../utils/conversationRecovery.js";
8
+ import { getNextAvailableLogForkNumber, logError } from "../utils/log.js";
9
+ import { isDefaultSlowAndCapableModel } from "../utils/model.js";
10
+ import { prepareTerminalForREPL } from "../utils/terminal.js";
11
+ import { REPL } from "../screens/REPL.js";
12
+ import {
13
+ TabbedListView
14
+ } from "../components/TabbedListView/index.js";
15
+ function formatDate(date) {
16
+ const now = /* @__PURE__ */ new Date();
17
+ const hours = date.getHours().toString().padStart(2, "0");
18
+ const minutes = date.getMinutes().toString().padStart(2, "0");
19
+ const timeStr = `${hours}:${minutes}`;
20
+ const isToday = date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear();
21
+ if (isToday) {
22
+ return `${t("commands.sessions.today")} ${timeStr}`;
23
+ }
24
+ const yesterday = new Date(now);
25
+ yesterday.setDate(yesterday.getDate() - 1);
26
+ const isYesterday = date.getDate() === yesterday.getDate() && date.getMonth() === yesterday.getMonth() && date.getFullYear() === yesterday.getFullYear();
27
+ if (isYesterday) {
28
+ return `${t("commands.sessions.yesterday")} ${timeStr}`;
29
+ }
30
+ const month = (date.getMonth() + 1).toString().padStart(2, "0");
31
+ const day = date.getDate().toString().padStart(2, "0");
32
+ return `${month}-${day} ${timeStr}`;
33
+ }
34
+ function truncate(str, maxLength) {
35
+ if (str.length <= maxLength) return str;
36
+ return str.slice(0, maxLength - 3) + "...";
37
+ }
38
+ function wasCleared(log) {
39
+ for (let i = log.messages.length - 1; i >= 0; i--) {
40
+ const msg = log.messages[i];
41
+ if (msg.type === "user") {
42
+ const content = typeof msg.message?.content === "string" ? msg.message.content : Array.isArray(msg.message?.content) ? msg.message.content.filter((c) => c.type === "text").map((c) => c.text).join("") : "";
43
+ return content.includes("<command-name>clear</command-name>");
44
+ }
45
+ }
46
+ return false;
47
+ }
48
+ function deduplicateLogForks(logs) {
49
+ const groupedByDate = /* @__PURE__ */ new Map();
50
+ for (const log of logs) {
51
+ const existing = groupedByDate.get(log.date) || [];
52
+ existing.push(log);
53
+ groupedByDate.set(log.date, existing);
54
+ }
55
+ const deduped = [];
56
+ for (const [, group] of groupedByDate) {
57
+ const sorted = group.sort((a, b) => {
58
+ const forkDiff = (b.forkNumber ?? 0) - (a.forkNumber ?? 0);
59
+ if (forkDiff !== 0) return forkDiff;
60
+ return b.modified.getTime() - a.modified.getTime();
61
+ });
62
+ if (sorted[0]) {
63
+ deduped.push(sorted[0]);
64
+ }
65
+ }
66
+ return deduped.sort((a, b) => b.modified.getTime() - a.modified.getTime());
67
+ }
68
+ function getLogProjectPath(log) {
69
+ const firstMessage = log.messages[0];
70
+ return firstMessage?.cwd || null;
71
+ }
72
+ function getProjectName(projectPath) {
73
+ return path.basename(projectPath) || projectPath;
74
+ }
75
+ function extractFirstPrompt(log) {
76
+ for (const msg of log.messages) {
77
+ if (msg.type === "user") {
78
+ let content = "";
79
+ if (typeof msg.message?.content === "string") {
80
+ content = msg.message.content;
81
+ } else if (Array.isArray(msg.message?.content)) {
82
+ content = msg.message.content.filter((c) => c.type === "text").map((c) => c.text).join("");
83
+ }
84
+ const trimmed = content.trim();
85
+ if (trimmed.startsWith("<system-reminder>") || trimmed.startsWith("<command-name>") || trimmed.startsWith("<") && trimmed.includes("</")) {
86
+ continue;
87
+ }
88
+ const firstLine = content.split("\n")[0]?.trim() || "";
89
+ if (firstLine) {
90
+ return firstLine;
91
+ }
92
+ }
93
+ }
94
+ return log.firstPrompt || "No prompt";
95
+ }
96
+ function countToolCalls(log) {
97
+ let count = 0;
98
+ for (const msg of log.messages) {
99
+ if (msg.type === "assistant" && Array.isArray(msg.message?.content)) {
100
+ for (const block of msg.message.content) {
101
+ if (block.type === "tool_use") {
102
+ count++;
103
+ }
104
+ }
105
+ }
106
+ }
107
+ return count;
108
+ }
109
+ function logToListItem(log) {
110
+ const dateStr = formatDate(log.modified);
111
+ const projectPath = getLogProjectPath(log) || "";
112
+ const projectName = projectPath ? truncate(getProjectName(projectPath), 15) : "";
113
+ const msgCount = log.messageCount;
114
+ const toolCount = countToolCalls(log);
115
+ const firstPrompt = extractFirstPrompt(log);
116
+ return {
117
+ id: log.fullPath,
118
+ // Use fullPath as unique id (date can be duplicated for forks)
119
+ label: truncate(firstPrompt, 45),
120
+ description: projectName || void 0,
121
+ metadata: `${dateStr} \xB7 ${msgCount} msgs \xB7 ${toolCount} tools`,
122
+ category: projectName || "Unknown",
123
+ data: log
124
+ };
125
+ }
126
+ function ResumeDisplay({
127
+ recentLogs,
128
+ projectLogs,
129
+ allLogs,
130
+ tools,
131
+ commands,
132
+ verbose,
133
+ onClose,
134
+ unmount
135
+ }) {
136
+ const [activeTab, setActiveTab] = useState("recent");
137
+ const [searchQuery, setSearchQuery] = useState("");
138
+ const tabs = useMemo(
139
+ () => [
140
+ {
141
+ id: "recent",
142
+ label: t("commands.sessions.title"),
143
+ badge: recentLogs.length
144
+ },
145
+ {
146
+ id: "project",
147
+ label: t("commands.sessions.headerProject"),
148
+ badge: projectLogs.length
149
+ },
150
+ {
151
+ id: "all",
152
+ label: t("common.all"),
153
+ badge: allLogs.length
154
+ }
155
+ ],
156
+ [recentLogs.length, projectLogs.length, allLogs.length]
157
+ );
158
+ const items = useMemo(() => {
159
+ let logs;
160
+ switch (activeTab) {
161
+ case "project":
162
+ logs = projectLogs;
163
+ break;
164
+ case "all":
165
+ logs = allLogs;
166
+ break;
167
+ case "recent":
168
+ default:
169
+ logs = recentLogs;
170
+ }
171
+ return logs.map(logToListItem);
172
+ }, [activeTab, recentLogs, projectLogs, allLogs]);
173
+ const handleSelect = async (item) => {
174
+ const log = item.data;
175
+ try {
176
+ onClose();
177
+ const isDefaultModel = await isDefaultSlowAndCapableModel();
178
+ unmount();
179
+ await prepareTerminalForREPL();
180
+ render(
181
+ /* @__PURE__ */ React.createElement(
182
+ REPL,
183
+ {
184
+ messageLogName: log.date,
185
+ initialPrompt: "",
186
+ shouldShowPromptInput: true,
187
+ verbose,
188
+ commands,
189
+ tools,
190
+ initialMessages: deserializeMessages(log.messages, tools),
191
+ initialForkNumber: getNextAvailableLogForkNumber(
192
+ log.date,
193
+ log.forkNumber ?? 1,
194
+ 0
195
+ ),
196
+ isDefaultModel,
197
+ isResumedConversation: true
198
+ }
199
+ ),
200
+ {
201
+ exitOnCtrlC: false
202
+ }
203
+ );
204
+ } catch (e) {
205
+ logError(`Failed to load conversation: ${e}`);
206
+ throw e;
207
+ }
208
+ };
209
+ return /* @__PURE__ */ React.createElement(
210
+ TabbedListView,
211
+ {
212
+ title: t("commands.resume.title"),
213
+ tabs,
214
+ activeTab,
215
+ onTabChange: setActiveTab,
216
+ items,
217
+ searchEnabled: true,
218
+ searchPlaceholder: t("ui.tabbedList.search"),
219
+ searchQuery,
220
+ onSearchChange: setSearchQuery,
221
+ onSelect: handleSelect,
222
+ onClose,
223
+ emptyText: t("commands.resume.noSessions"),
224
+ footerHint: t("commands.resume.navigationHint"),
225
+ groupByCategory: activeTab === "all"
226
+ }
227
+ );
228
+ }
5
229
  var resume_default = {
6
230
  type: "local-jsx",
7
231
  name: "resume",
8
- description: "Resume a previous conversation from this project",
232
+ description: t("commands.resume.description"),
9
233
  isEnabled: true,
10
234
  isHidden: false,
235
+ hidePromptInput: true,
11
236
  aliases: ["r"],
12
237
  userFacingName() {
13
238
  return "resume";
14
239
  },
15
240
  async call(onDone, context) {
16
241
  const { commands = [], tools = [], verbose = false } = context.options || {};
17
- const logs = await loadLogList(CACHE_PATHS.messages());
18
- render(
19
- /* @__PURE__ */ React.createElement(
20
- ResumeConversation,
21
- {
22
- commands,
23
- context: { unmount: onDone },
24
- logs,
25
- tools,
26
- verbose
27
- }
28
- )
242
+ const cwd = getCwd();
243
+ const allRawLogs = await loadLogList(CACHE_PATHS.messages());
244
+ const validLogs = deduplicateLogForks(
245
+ allRawLogs.filter((log) => !wasCleared(log))
246
+ );
247
+ const recentLogs = validLogs.slice(0, 20);
248
+ const projectLogs = validLogs.filter((log) => {
249
+ const logPath = getLogProjectPath(log);
250
+ return logPath === cwd;
251
+ });
252
+ const allLogs = validLogs.slice(0, 100);
253
+ return /* @__PURE__ */ React.createElement(
254
+ ResumeDisplay,
255
+ {
256
+ recentLogs,
257
+ projectLogs,
258
+ allLogs,
259
+ tools,
260
+ commands,
261
+ verbose,
262
+ onClose: onDone,
263
+ unmount: context.unmount
264
+ }
29
265
  );
30
- return null;
31
266
  }
32
267
  };
33
268
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/resume.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport type { Command } from '@commands'\nimport { ResumeConversation } from '@screens/ResumeConversation'\nimport { render } from 'ink'\nimport { CACHE_PATHS, loadLogList } from '@utils/log'\n\nexport default {\n type: 'local-jsx',\n name: 'resume',\n description: 'Resume a previous conversation from this project',\n isEnabled: true,\n isHidden: false,\n aliases: ['r'],\n userFacingName() {\n return 'resume'\n },\n async call(onDone, context) {\n const { commands = [], tools = [], verbose = false } = context.options || {}\n const logs = await loadLogList(CACHE_PATHS.messages())\n render(\n <ResumeConversation\n commands={commands}\n context={{ unmount: onDone }}\n logs={logs}\n tools={tools}\n verbose={verbose}\n />,\n )\n // This return is here for type only\n return null\n },\n} satisfies Command\n"],
5
- "mappings": "AAAA,YAAY,WAAW;AAEvB,SAAS,0BAA0B;AACnC,SAAS,cAAc;AACvB,SAAS,aAAa,mBAAmB;AAEzC,IAAO,iBAAQ;AAAA,EACb,MAAM;AAAA,EACN,MAAM;AAAA,EACN,aAAa;AAAA,EACb,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS,CAAC,GAAG;AAAA,EACb,iBAAiB;AACf,WAAO;AAAA,EACT;AAAA,EACA,MAAM,KAAK,QAAQ,SAAS;AAC1B,UAAM,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG,UAAU,MAAM,IAAI,QAAQ,WAAW,CAAC;AAC3E,UAAM,OAAO,MAAM,YAAY,YAAY,SAAS,CAAC;AACrD;AAAA,MACE;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,SAAS,EAAE,SAAS,OAAO;AAAA,UAC3B;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;",
4
+ "sourcesContent": ["/**\n * Resume Command\n *\n * Resume a previous conversation.\n * Uses TabbedListView with 3 tabs: Recent, Project, All.\n *\n * Usage:\n * /resume # Show sessions with tabs\n * /r # Alias\n */\n\nimport React, { useState, useMemo } from 'react'\nimport { render } from 'ink'\nimport path from 'path'\nimport type { Command } from '@commands'\nimport { t } from '@i18n'\nimport { getCwd } from '@utils/state'\nimport { CACHE_PATHS, loadLogList } from '@utils/log'\nimport type { LogOption } from '@minto-types/logs'\nimport { deserializeMessages } from '@utils/conversationRecovery'\nimport { getNextAvailableLogForkNumber, logError } from '@utils/log'\nimport { isDefaultSlowAndCapableModel } from '@utils/model'\nimport { prepareTerminalForREPL } from '@utils/terminal'\nimport { REPL } from '@screens/REPL'\nimport type { Tool } from '@tool'\nimport {\n TabbedListView,\n type ListItem,\n type TabDefinition,\n} from '@components/TabbedListView'\n\n/**\n * Format date for display\n * - Today: \"Today HH:mm\"\n * - Yesterday: \"Yesterday HH:mm\"\n * - Other: \"MM-DD HH:mm\"\n */\nfunction formatDate(date: Date): string {\n const now = new Date()\n\n const hours = date.getHours().toString().padStart(2, '0')\n const minutes = date.getMinutes().toString().padStart(2, '0')\n const timeStr = `${hours}:${minutes}`\n\n // Check if it's today\n const isToday =\n date.getDate() === now.getDate() &&\n date.getMonth() === now.getMonth() &&\n date.getFullYear() === now.getFullYear()\n\n if (isToday) {\n return `${t('commands.sessions.today')} ${timeStr}`\n }\n\n // Check if it's yesterday\n const yesterday = new Date(now)\n yesterday.setDate(yesterday.getDate() - 1)\n const isYesterday =\n date.getDate() === yesterday.getDate() &&\n date.getMonth() === yesterday.getMonth() &&\n date.getFullYear() === yesterday.getFullYear()\n\n if (isYesterday) {\n return `${t('commands.sessions.yesterday')} ${timeStr}`\n }\n\n // Default format: MM-DD HH:mm\n const month = (date.getMonth() + 1).toString().padStart(2, '0')\n const day = date.getDate().toString().padStart(2, '0')\n return `${month}-${day} ${timeStr}`\n}\n\n/**\n * Truncate string with ellipsis\n */\nfunction truncate(str: string, maxLength: number): string {\n if (str.length <= maxLength) return str\n return str.slice(0, maxLength - 3) + '...'\n}\n\n/**\n * Check if the conversation was discarded (last user message is /clear)\n */\nfunction wasCleared(log: LogOption): boolean {\n // Find the last user message by iterating backwards\n for (let i = log.messages.length - 1; i >= 0; i--) {\n const msg = log.messages[i]\n if (msg.type === 'user') {\n const content =\n typeof msg.message?.content === 'string'\n ? msg.message.content\n : Array.isArray(msg.message?.content)\n ? msg.message.content\n .filter((c: any) => c.type === 'text')\n .map((c: any) => c.text)\n .join('')\n : ''\n // Check if the last user message is a /clear command\n return content.includes('<command-name>clear</command-name>')\n }\n }\n return false\n}\n\n/**\n * Deduplicate logs by keeping only the latest fork for each base conversation.\n * Groups logs by their base date (timestamp without fork number) and keeps\n * the one with the highest fork number or most recent modification.\n */\nfunction deduplicateLogForks(logs: LogOption[]): LogOption[] {\n // Group by base date (the original conversation timestamp)\n const groupedByDate = new Map<string, LogOption[]>()\n\n for (const log of logs) {\n const existing = groupedByDate.get(log.date) || []\n existing.push(log)\n groupedByDate.set(log.date, existing)\n }\n\n // For each group, keep the one with highest fork number (most recent state)\n const deduped: LogOption[] = []\n for (const [, group] of groupedByDate) {\n // Sort by forkNumber desc (higher = more recent), then by modified desc\n const sorted = group.sort((a, b) => {\n const forkDiff = (b.forkNumber ?? 0) - (a.forkNumber ?? 0)\n if (forkDiff !== 0) return forkDiff\n return b.modified.getTime() - a.modified.getTime()\n })\n // Keep only the first one (highest fork / most recently modified)\n if (sorted[0]) {\n deduped.push(sorted[0])\n }\n }\n\n // Re-sort by modified time descending\n return deduped.sort((a, b) => b.modified.getTime() - a.modified.getTime())\n}\n\n/**\n * Get project path from log's first message\n */\nfunction getLogProjectPath(log: LogOption): string | null {\n const firstMessage = log.messages[0]\n return firstMessage?.cwd || null\n}\n\n/**\n * Get project name from path\n */\nfunction getProjectName(projectPath: string): string {\n return path.basename(projectPath) || projectPath\n}\n\n/**\n * Extract clean first prompt from log (skip system messages and XML tags)\n */\nfunction extractFirstPrompt(log: LogOption): string {\n for (const msg of log.messages) {\n if (msg.type === 'user') {\n let content = ''\n if (typeof msg.message?.content === 'string') {\n content = msg.message.content\n } else if (Array.isArray(msg.message?.content)) {\n content = msg.message.content\n .filter((c: any) => c.type === 'text')\n .map((c: any) => c.text)\n .join('')\n }\n\n // Skip system messages (XML tags like <system-reminder>, <command-name>, etc.)\n const trimmed = content.trim()\n if (\n trimmed.startsWith('<system-reminder>') ||\n trimmed.startsWith('<command-name>') ||\n (trimmed.startsWith('<') && trimmed.includes('</'))\n ) {\n continue\n }\n\n // Return first line, cleaned up\n const firstLine = content.split('\\n')[0]?.trim() || ''\n if (firstLine) {\n return firstLine\n }\n }\n }\n return log.firstPrompt || 'No prompt'\n}\n\n/**\n * Count tool calls in a log\n */\nfunction countToolCalls(log: LogOption): number {\n let count = 0\n for (const msg of log.messages) {\n if (msg.type === 'assistant' && Array.isArray(msg.message?.content)) {\n for (const block of msg.message.content) {\n if (block.type === 'tool_use') {\n count++\n }\n }\n }\n }\n return count\n}\n\n/**\n * Convert LogOption to ListItem for TabbedListView\n */\nfunction logToListItem(log: LogOption): ListItem {\n const dateStr = formatDate(log.modified)\n const projectPath = getLogProjectPath(log) || ''\n const projectName = projectPath\n ? truncate(getProjectName(projectPath), 15)\n : ''\n const msgCount = log.messageCount\n const toolCount = countToolCalls(log)\n const firstPrompt = extractFirstPrompt(log)\n\n return {\n id: log.fullPath, // Use fullPath as unique id (date can be duplicated for forks)\n label: truncate(firstPrompt, 45),\n description: projectName || undefined,\n metadata: `${dateStr} \u00B7 ${msgCount} msgs \u00B7 ${toolCount} tools`,\n category: projectName || 'Unknown',\n data: log,\n }\n}\n\ninterface ResumeDisplayProps {\n recentLogs: LogOption[]\n projectLogs: LogOption[]\n allLogs: LogOption[]\n tools: Tool[]\n commands: Command[]\n verbose: boolean | undefined\n onClose: () => void\n unmount: () => void\n}\n\n/**\n * Resume display component with TabbedListView\n */\nfunction ResumeDisplay({\n recentLogs,\n projectLogs,\n allLogs,\n tools,\n commands,\n verbose,\n onClose,\n unmount,\n}: ResumeDisplayProps) {\n const [activeTab, setActiveTab] = useState('recent')\n const [searchQuery, setSearchQuery] = useState('')\n\n // Tab definitions\n const tabs: TabDefinition[] = useMemo(\n () => [\n {\n id: 'recent',\n label: t('commands.sessions.title'),\n badge: recentLogs.length,\n },\n {\n id: 'project',\n label: t('commands.sessions.headerProject'),\n badge: projectLogs.length,\n },\n {\n id: 'all',\n label: t('common.all'),\n badge: allLogs.length,\n },\n ],\n [recentLogs.length, projectLogs.length, allLogs.length],\n )\n\n // Get items based on active tab\n const items: ListItem[] = useMemo(() => {\n let logs: LogOption[]\n\n switch (activeTab) {\n case 'project':\n logs = projectLogs\n break\n case 'all':\n logs = allLogs\n break\n case 'recent':\n default:\n logs = recentLogs\n }\n\n return logs.map(logToListItem)\n }, [activeTab, recentLogs, projectLogs, allLogs])\n\n // Handle item selection - load and resume conversation\n const handleSelect = async (item: ListItem) => {\n const log = item.data as LogOption\n\n try {\n // First close the selection UI\n onClose()\n\n // Check if using default model before unmounting\n const isDefaultModel = await isDefaultSlowAndCapableModel()\n\n // Unmount current REPL before creating new one\n // This prevents two Ink instances from conflicting\n unmount()\n\n // Clear screen and prepare terminal for REPL\n await prepareTerminalForREPL()\n\n render(\n <REPL\n messageLogName={log.date}\n initialPrompt=\"\"\n shouldShowPromptInput={true}\n verbose={verbose}\n commands={commands}\n tools={tools}\n initialMessages={deserializeMessages(log.messages, tools)}\n initialForkNumber={getNextAvailableLogForkNumber(\n log.date,\n log.forkNumber ?? 1,\n 0,\n )}\n isDefaultModel={isDefaultModel}\n isResumedConversation={true}\n />,\n {\n exitOnCtrlC: false,\n },\n )\n } catch (e) {\n logError(`Failed to load conversation: ${e}`)\n throw e\n }\n }\n\n return (\n <TabbedListView\n title={t('commands.resume.title')}\n tabs={tabs}\n activeTab={activeTab}\n onTabChange={setActiveTab}\n items={items}\n searchEnabled={true}\n searchPlaceholder={t('ui.tabbedList.search')}\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n onSelect={handleSelect}\n onClose={onClose}\n emptyText={t('commands.resume.noSessions')}\n footerHint={t('commands.resume.navigationHint')}\n groupByCategory={activeTab === 'all'}\n />\n )\n}\n\nexport default {\n type: 'local-jsx',\n name: 'resume',\n description: t('commands.resume.description'),\n isEnabled: true,\n isHidden: false,\n hidePromptInput: true,\n aliases: ['r'],\n userFacingName() {\n return 'resume'\n },\n async call(onDone, context) {\n const { commands = [], tools = [], verbose = false } = context.options || {}\n const cwd = getCwd()\n\n // Load all logs\n const allRawLogs = await loadLogList(CACHE_PATHS.messages())\n\n // Filter out cleared conversations, then deduplicate forks\n // (keep only the latest fork for each base conversation)\n const validLogs = deduplicateLogForks(\n allRawLogs.filter(log => !wasCleared(log)),\n )\n\n // Recent: first 20 logs (already sorted by modified time)\n const recentLogs = validLogs.slice(0, 20)\n\n // Project: logs from current project\n const projectLogs = validLogs.filter(log => {\n const logPath = getLogProjectPath(log)\n return logPath === cwd\n })\n\n // All: all valid logs (up to 100)\n const allLogs = validLogs.slice(0, 100)\n\n return (\n <ResumeDisplay\n recentLogs={recentLogs}\n projectLogs={projectLogs}\n allLogs={allLogs}\n tools={tools}\n commands={commands}\n verbose={verbose}\n onClose={onDone}\n unmount={context.unmount}\n />\n )\n },\n} satisfies Command\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,68 +1,180 @@
1
1
  import React, { useState } from "react";
2
- import { Box, Text, useInput } from "ink";
3
2
  import {
4
3
  getSandboxController
5
4
  } from "../services/sandbox/index.js";
6
5
  import { getCwd } from "../utils/state.js";
7
6
  import { SEMANTIC_COLORS } from "../constants/colors.js";
8
- const SandboxStatus = ({
9
- config,
10
- implementation,
11
- isAvailable,
12
- onToggle,
13
- onDone
14
- }) => {
15
- const [showHelp, setShowHelp] = useState(false);
16
- useInput((input, key) => {
17
- if (input === "t" || input === "T") {
18
- onToggle();
19
- } else if (input === "h" || input === "H") {
20
- setShowHelp(!showHelp);
21
- } else if (input === "q" || key.escape) {
22
- onDone();
7
+ import { InfoPanel } from "../components/InfoPanel/index.js";
8
+ import { t } from "../i18n/index.js";
9
+ function getImplColor(impl) {
10
+ switch (impl) {
11
+ case "seatbelt":
12
+ return SEMANTIC_COLORS.success;
13
+ case "bubblewrap":
14
+ return SEMANTIC_COLORS.info;
15
+ case "docker":
16
+ return "#FFB86C";
17
+ case "none":
18
+ return SEMANTIC_COLORS.dim;
19
+ }
20
+ }
21
+ function getImplDescription(impl) {
22
+ switch (impl) {
23
+ case "seatbelt":
24
+ return "macOS sandbox-exec (built-in)";
25
+ case "bubblewrap":
26
+ return "Linux bubblewrap container";
27
+ case "docker":
28
+ return "Docker container isolation";
29
+ case "none":
30
+ return "No sandbox available";
31
+ }
32
+ }
33
+ function buildSections(config, implementation, isAvailable, showHelp) {
34
+ const sections = [];
35
+ const statusValue = config.enabled && isAvailable ? `\u25CF ${t("commands.sandbox.enabled")}` : `\u25CB ${t("commands.sandbox.disabled")}`;
36
+ const statusColor = config.enabled && isAvailable ? SEMANTIC_COLORS.success : SEMANTIC_COLORS.error;
37
+ const statusItems = [
38
+ { label: "Status", value: statusValue, valueColor: statusColor },
39
+ {
40
+ label: t("commands.sandbox.implementation"),
41
+ value: `${implementation.toUpperCase()} - ${getImplDescription(implementation)}`,
42
+ valueColor: getImplColor(implementation)
23
43
  }
44
+ ];
45
+ if (!isAvailable) {
46
+ statusItems.push({
47
+ label: "\u26A0",
48
+ value: t("commands.sandbox.notAvailable"),
49
+ valueColor: "#FFB86C"
50
+ });
51
+ }
52
+ sections.push({
53
+ title: t("commands.sandbox.statusSection"),
54
+ items: statusItems
24
55
  });
25
- const getImplColor = (impl) => {
26
- switch (impl) {
27
- case "seatbelt":
28
- return "green";
29
- case "bubblewrap":
30
- return "cyan";
31
- case "docker":
32
- return "yellow";
33
- case "none":
34
- return "gray";
56
+ const fsItems = [
57
+ {
58
+ label: "Write Allowed",
59
+ value: config.filesystem.writeAllowed.length > 0 ? config.filesystem.writeAllowed.slice(0, 3).join(", ") + (config.filesystem.writeAllowed.length > 3 ? " ..." : "") : "None",
60
+ valueColor: config.filesystem.writeAllowed.length > 0 ? SEMANTIC_COLORS.success : SEMANTIC_COLORS.error
61
+ },
62
+ {
63
+ label: "Denied Paths",
64
+ value: config.filesystem.denied.length > 0 ? `${config.filesystem.denied.length} paths blocked` : "None blocked",
65
+ valueColor: config.filesystem.denied.length > 0 ? SEMANTIC_COLORS.error : SEMANTIC_COLORS.success
35
66
  }
36
- };
37
- const getImplDescription = (impl) => {
38
- switch (impl) {
39
- case "seatbelt":
40
- return "macOS sandbox-exec (built-in)";
41
- case "bubblewrap":
42
- return "Linux bubblewrap container";
43
- case "docker":
44
- return "Docker container isolation";
45
- case "none":
46
- return "No sandbox available";
67
+ ];
68
+ sections.push({
69
+ title: t("commands.sandbox.filesystemPolicy"),
70
+ items: fsItems
71
+ });
72
+ const netItems = [
73
+ {
74
+ label: "Network Access",
75
+ value: config.network.blockAll ? "Blocked" : "Allowed",
76
+ valueColor: config.network.blockAll ? SEMANTIC_COLORS.error : SEMANTIC_COLORS.success
47
77
  }
78
+ ];
79
+ if (!config.network.blockAll && config.network.allowedDomains.length > 0) {
80
+ netItems.push({
81
+ label: "Allowed Domains",
82
+ value: config.network.allowedDomains.slice(0, 3).join(", ") + (config.network.allowedDomains.length > 3 ? " ..." : "")
83
+ });
84
+ }
85
+ sections.push({ title: t("commands.sandbox.networkPolicy"), items: netItems });
86
+ sections.push({
87
+ title: t("commands.sandbox.processPolicy"),
88
+ items: [
89
+ {
90
+ label: "Max Exec Time",
91
+ value: `${config.process.maxExecutionTime / 1e3}s`
92
+ },
93
+ {
94
+ label: "Excluded Cmds",
95
+ value: config.process.excludedCommands.length > 0 ? config.process.excludedCommands.slice(0, 3).join(", ") + (config.process.excludedCommands.length > 3 ? " ..." : "") : "None"
96
+ }
97
+ ]
98
+ });
99
+ if (showHelp) {
100
+ sections.push({
101
+ title: t("commands.sandbox.helpSection"),
102
+ items: [
103
+ {
104
+ label: "\u2022 seatbelt",
105
+ value: "macOS built-in sandbox-exec",
106
+ valueColor: SEMANTIC_COLORS.success
107
+ },
108
+ {
109
+ label: "\u2022 bubblewrap",
110
+ value: "Linux container isolation (bwrap)",
111
+ valueColor: SEMANTIC_COLORS.info
112
+ },
113
+ {
114
+ label: "\u2022 docker",
115
+ value: "Docker container isolation",
116
+ valueColor: "#FFB86C"
117
+ },
118
+ {
119
+ label: "\u2022 none",
120
+ value: "No sandboxing (not recommended)",
121
+ valueColor: SEMANTIC_COLORS.dim
122
+ }
123
+ ]
124
+ });
125
+ }
126
+ return sections;
127
+ }
128
+ const SandboxView = ({
129
+ initialConfig,
130
+ implementation,
131
+ initialAvailable,
132
+ onDone
133
+ }) => {
134
+ const [config, setConfig] = useState(initialConfig);
135
+ const [isAvailable, setIsAvailable] = useState(initialAvailable);
136
+ const [showHelp, setShowHelp] = useState(false);
137
+ const cwd = getCwd();
138
+ const controller = getSandboxController(cwd);
139
+ const handleToggle = async () => {
140
+ const newEnabled = !config.enabled;
141
+ controller.updateConfig({ enabled: newEnabled });
142
+ const newConfig = controller.getConfig();
143
+ setConfig(newConfig);
144
+ const available = await controller.isAvailable();
145
+ setIsAvailable(available);
48
146
  };
49
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "cyan" }, "Sandbox Configuration")), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Status: "), config.enabled && isAvailable ? /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u25CF ENABLED") : /* @__PURE__ */ React.createElement(Text, { color: "red" }, "\u25CB DISABLED"))), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Implementation: "), /* @__PURE__ */ React.createElement(Text, { color: getImplColor(implementation) }, implementation.toUpperCase())), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", "- ", getImplDescription(implementation))), !isAvailable && /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "\u26A0 Sandbox not available on this system")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Filesystem Policy:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Write Allowed: "), config.filesystem.writeAllowed.length > 0 ? /* @__PURE__ */ React.createElement(Text, { color: "green" }, config.filesystem.writeAllowed.slice(0, 3).join(", "), config.filesystem.writeAllowed.length > 3 && " ...") : /* @__PURE__ */ React.createElement(Text, { color: "red" }, "None")), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Denied Paths: "), config.filesystem.denied.length > 0 ? /* @__PURE__ */ React.createElement(Text, { color: "red" }, config.filesystem.denied.length, " paths blocked") : /* @__PURE__ */ React.createElement(Text, { color: "green" }, "None blocked")))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Network Policy:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Network Access: "), config.network.blockAll ? /* @__PURE__ */ React.createElement(Text, { color: "red" }, "Blocked") : /* @__PURE__ */ React.createElement(Text, { color: "green" }, "Allowed")), !config.network.blockAll && config.network.allowedDomains.length > 0 && /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Allowed Domains: "), /* @__PURE__ */ React.createElement(Text, null, config.network.allowedDomains.slice(0, 3).join(", "), config.network.allowedDomains.length > 3 && " ...")))), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Process Policy:"), /* @__PURE__ */ React.createElement(Box, { paddingLeft: 2, flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Max Execution Time: "), /* @__PURE__ */ React.createElement(Text, null, config.process.maxExecutionTime / 1e3, "s")), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "Excluded Commands: "), /* @__PURE__ */ React.createElement(Text, null, config.process.excludedCommands.length > 0 ? config.process.excludedCommands.slice(0, 3).join(", ") : "None", config.process.excludedCommands.length > 3 && " ...")))), showHelp && /* @__PURE__ */ React.createElement(
50
- Box,
147
+ const sections = buildSections(config, implementation, isAvailable, showHelp);
148
+ const actions = [
51
149
  {
52
- flexDirection: "column",
53
- marginY: 1,
54
- borderStyle: "round",
55
- paddingX: 1
150
+ key: "t",
151
+ keyLabel: "T",
152
+ description: t("commands.sandbox.toggle"),
153
+ onPress: handleToggle
56
154
  },
57
- /* @__PURE__ */ React.createElement(Text, { bold: true }, "Sandbox Implementations:"),
58
- /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingLeft: 1 }, /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, "\u2022 seatbelt:"), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", "macOS built-in sandbox-exec")), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, "\u2022 bubblewrap:"), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", "Linux container isolation (bwrap)")), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, "\u2022 docker:"), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", "Docker container isolation")), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "gray" }, "\u2022 none:"), /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, " ", "No sandboxing (not recommended)")))
59
- ), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: SEMANTIC_COLORS.dim }, "t: Toggle sandbox | h: Show/hide help | q/Esc: Exit")));
155
+ {
156
+ key: "h",
157
+ keyLabel: "H",
158
+ description: t("commands.sandbox.toggleHelp"),
159
+ onPress: () => setShowHelp(!showHelp)
160
+ }
161
+ ];
162
+ return /* @__PURE__ */ React.createElement(
163
+ InfoPanel,
164
+ {
165
+ title: t("commands.sandbox.title"),
166
+ sections,
167
+ actions,
168
+ onClose: onDone
169
+ }
170
+ );
60
171
  };
61
172
  const command = {
62
173
  name: "sandbox",
63
- description: "View and toggle sandbox mode for command execution",
174
+ description: t("commands.sandbox.description"),
64
175
  isEnabled: true,
65
176
  isHidden: false,
177
+ hidePromptInput: true,
66
178
  type: "local-jsx",
67
179
  userFacingName() {
68
180
  return this.name;
@@ -73,29 +185,15 @@ const command = {
73
185
  const initialConfig = controller.getConfig();
74
186
  const implementation = controller.getImplementationType();
75
187
  const initialAvailable = await controller.isAvailable();
76
- const SandboxWrapper = () => {
77
- const [config, setConfig] = useState(initialConfig);
78
- const [isAvailable, setIsAvailable] = useState(initialAvailable);
79
- const handleToggle = async () => {
80
- const newEnabled = !config.enabled;
81
- controller.updateConfig({ enabled: newEnabled });
82
- const newConfig = controller.getConfig();
83
- setConfig(newConfig);
84
- const available = await controller.isAvailable();
85
- setIsAvailable(available);
86
- };
87
- return /* @__PURE__ */ React.createElement(
88
- SandboxStatus,
89
- {
90
- config,
91
- implementation,
92
- isAvailable,
93
- onToggle: handleToggle,
94
- onDone
95
- }
96
- );
97
- };
98
- return /* @__PURE__ */ React.createElement(SandboxWrapper, null);
188
+ return /* @__PURE__ */ React.createElement(
189
+ SandboxView,
190
+ {
191
+ initialConfig,
192
+ implementation,
193
+ initialAvailable,
194
+ onDone
195
+ }
196
+ );
99
197
  }
100
198
  };
101
199
  var sandbox_default = command;