bingocode 1.0.1 → 1.0.3

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 (187) hide show
  1. package/bin/bingo-win.cjs +34 -3
  2. package/desktop/README.md +30 -0
  3. package/desktop/bunfig.toml +1 -0
  4. package/desktop/index.html +17 -0
  5. package/desktop/package.json +55 -0
  6. package/desktop/pnpm-lock.yaml +3832 -0
  7. package/desktop/public/app-icon.jpg +0 -0
  8. package/desktop/public/fonts/inter-latin-ext.woff2 +0 -0
  9. package/desktop/public/fonts/inter-latin.woff2 +0 -0
  10. package/desktop/public/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  11. package/desktop/public/fonts/jetbrains-mono-latin.woff2 +0 -0
  12. package/desktop/public/fonts/manrope-latin-ext.woff2 +0 -0
  13. package/desktop/public/fonts/manrope-latin.woff2 +0 -0
  14. package/desktop/public/fonts/material-symbols-outlined.woff2 +0 -0
  15. package/desktop/public/icons/bilibili.svg +1 -0
  16. package/desktop/public/icons/douyin.svg +1 -0
  17. package/desktop/public/icons/github.svg +3 -0
  18. package/desktop/public/icons/xiaohongshu.svg +1 -0
  19. package/desktop/scripts/build-macos-arm64.sh +270 -0
  20. package/desktop/scripts/build-sidecars.ts +183 -0
  21. package/desktop/scripts/build-windows-x64.ps1 +295 -0
  22. package/desktop/scripts/scan-missing-imports.ts +235 -0
  23. package/desktop/sidecars/claude-sidecar.ts +156 -0
  24. package/desktop/src/App.tsx +5 -0
  25. package/desktop/src/__tests__/agentsSettings.test.tsx +349 -0
  26. package/desktop/src/__tests__/pages.test.tsx +290 -0
  27. package/desktop/src/__tests__/skillsSettings.test.tsx +205 -0
  28. package/desktop/src/api/adapters.ts +12 -0
  29. package/desktop/src/api/agents.ts +36 -0
  30. package/desktop/src/api/cliTasks.ts +28 -0
  31. package/desktop/src/api/client.ts +63 -0
  32. package/desktop/src/api/computerUse.ts +76 -0
  33. package/desktop/src/api/filesystem.ts +30 -0
  34. package/desktop/src/api/hahaOAuth.ts +38 -0
  35. package/desktop/src/api/models.ts +28 -0
  36. package/desktop/src/api/providers.ts +63 -0
  37. package/desktop/src/api/search.ts +29 -0
  38. package/desktop/src/api/sessions.ts +56 -0
  39. package/desktop/src/api/settings.ts +20 -0
  40. package/desktop/src/api/skills.ts +19 -0
  41. package/desktop/src/api/tasks.ts +36 -0
  42. package/desktop/src/api/teams.ts +44 -0
  43. package/desktop/src/api/websocket.ts +164 -0
  44. package/desktop/src/components/chat/AskUserQuestion.tsx +268 -0
  45. package/desktop/src/components/chat/AssistantMessage.tsx +29 -0
  46. package/desktop/src/components/chat/AttachmentGallery.tsx +113 -0
  47. package/desktop/src/components/chat/ChatInput.tsx +622 -0
  48. package/desktop/src/components/chat/CodeViewer.tsx +161 -0
  49. package/desktop/src/components/chat/ComputerUsePermissionModal.test.tsx +174 -0
  50. package/desktop/src/components/chat/ComputerUsePermissionModal.tsx +311 -0
  51. package/desktop/src/components/chat/DiffViewer.tsx +157 -0
  52. package/desktop/src/components/chat/FileSearchMenu.tsx +198 -0
  53. package/desktop/src/components/chat/ImageGalleryModal.tsx +91 -0
  54. package/desktop/src/components/chat/InlineImageGallery.tsx +106 -0
  55. package/desktop/src/components/chat/InlineTaskSummary.tsx +60 -0
  56. package/desktop/src/components/chat/MermaidRenderer.test.tsx +98 -0
  57. package/desktop/src/components/chat/MermaidRenderer.tsx +361 -0
  58. package/desktop/src/components/chat/MessageActionBar.tsx +27 -0
  59. package/desktop/src/components/chat/MessageList.test.tsx +313 -0
  60. package/desktop/src/components/chat/MessageList.tsx +249 -0
  61. package/desktop/src/components/chat/PermissionDialog.tsx +262 -0
  62. package/desktop/src/components/chat/SessionTaskBar.test.tsx +99 -0
  63. package/desktop/src/components/chat/SessionTaskBar.tsx +159 -0
  64. package/desktop/src/components/chat/StreamingIndicator.tsx +41 -0
  65. package/desktop/src/components/chat/TerminalChrome.tsx +35 -0
  66. package/desktop/src/components/chat/ThinkingBlock.tsx +87 -0
  67. package/desktop/src/components/chat/ToolCallBlock.tsx +247 -0
  68. package/desktop/src/components/chat/ToolCallGroup.tsx +617 -0
  69. package/desktop/src/components/chat/ToolResultBlock.tsx +107 -0
  70. package/desktop/src/components/chat/UserMessage.tsx +38 -0
  71. package/desktop/src/components/chat/chatBlocks.test.tsx +136 -0
  72. package/desktop/src/components/chat/clipboard.ts +25 -0
  73. package/desktop/src/components/chat/composerUtils.test.ts +55 -0
  74. package/desktop/src/components/chat/composerUtils.ts +149 -0
  75. package/desktop/src/components/controls/ModelSelector.tsx +156 -0
  76. package/desktop/src/components/controls/PermissionModeSelector.tsx +229 -0
  77. package/desktop/src/components/layout/AppShell.tsx +107 -0
  78. package/desktop/src/components/layout/ContentRouter.tsx +27 -0
  79. package/desktop/src/components/layout/ProjectFilter.tsx +126 -0
  80. package/desktop/src/components/layout/Sidebar.test.tsx +158 -0
  81. package/desktop/src/components/layout/Sidebar.tsx +384 -0
  82. package/desktop/src/components/layout/StatusBar.tsx +31 -0
  83. package/desktop/src/components/layout/TabBar.test.tsx +136 -0
  84. package/desktop/src/components/layout/TabBar.tsx +318 -0
  85. package/desktop/src/components/layout/TitleBar.tsx +96 -0
  86. package/desktop/src/components/layout/WindowControls.test.tsx +69 -0
  87. package/desktop/src/components/layout/WindowControls.tsx +89 -0
  88. package/desktop/src/components/markdown/MarkdownRenderer.test.tsx +100 -0
  89. package/desktop/src/components/markdown/MarkdownRenderer.tsx +229 -0
  90. package/desktop/src/components/settings/ClaudeOfficialLogin.tsx +107 -0
  91. package/desktop/src/components/shared/Button.tsx +63 -0
  92. package/desktop/src/components/shared/CopyButton.tsx +58 -0
  93. package/desktop/src/components/shared/DirectoryPicker.tsx +316 -0
  94. package/desktop/src/components/shared/Dropdown.tsx +91 -0
  95. package/desktop/src/components/shared/Input.tsx +38 -0
  96. package/desktop/src/components/shared/Modal.tsx +65 -0
  97. package/desktop/src/components/shared/ProjectContextChip.tsx +30 -0
  98. package/desktop/src/components/shared/Spinner.tsx +30 -0
  99. package/desktop/src/components/shared/Textarea.tsx +38 -0
  100. package/desktop/src/components/shared/Toast.tsx +47 -0
  101. package/desktop/src/components/shared/UpdateChecker.tsx +90 -0
  102. package/desktop/src/components/skills/SkillDetail.test.tsx +89 -0
  103. package/desktop/src/components/skills/SkillDetail.tsx +403 -0
  104. package/desktop/src/components/skills/SkillList.tsx +254 -0
  105. package/desktop/src/components/tasks/DayOfWeekPicker.tsx +57 -0
  106. package/desktop/src/components/tasks/NewTaskModal.tsx +407 -0
  107. package/desktop/src/components/tasks/PromptEditor.tsx +74 -0
  108. package/desktop/src/components/tasks/TaskEmptyState.tsx +30 -0
  109. package/desktop/src/components/tasks/TaskList.tsx +46 -0
  110. package/desktop/src/components/tasks/TaskRow.tsx +253 -0
  111. package/desktop/src/components/tasks/TaskRunsPanel.tsx +195 -0
  112. package/desktop/src/components/teams/TeamStatusBar.tsx +147 -0
  113. package/desktop/src/config/providerPresets.ts +78 -0
  114. package/desktop/src/config/spinnerVerbs.ts +193 -0
  115. package/desktop/src/hooks/useKeyboardShortcuts.ts +60 -0
  116. package/desktop/src/i18n/index.ts +54 -0
  117. package/desktop/src/i18n/locales/en.ts +670 -0
  118. package/desktop/src/i18n/locales/zh.ts +670 -0
  119. package/desktop/src/lib/__tests__/cronDescribe.test.ts +93 -0
  120. package/desktop/src/lib/cronDescribe.ts +188 -0
  121. package/desktop/src/lib/desktopRuntime.ts +54 -0
  122. package/desktop/src/lib/parseRunOutput.ts +79 -0
  123. package/desktop/src/main.tsx +13 -0
  124. package/desktop/src/mocks/data.ts +202 -0
  125. package/desktop/src/pages/ActiveSession.test.tsx +181 -0
  126. package/desktop/src/pages/ActiveSession.tsx +219 -0
  127. package/desktop/src/pages/AdapterSettings.tsx +375 -0
  128. package/desktop/src/pages/AgentTeams.tsx +200 -0
  129. package/desktop/src/pages/ComputerUseSettings.tsx +420 -0
  130. package/desktop/src/pages/EmptySession.tsx +518 -0
  131. package/desktop/src/pages/NewTaskModal.tsx +346 -0
  132. package/desktop/src/pages/ScheduledTasks.tsx +66 -0
  133. package/desktop/src/pages/ScheduledTasksEmpty.tsx +152 -0
  134. package/desktop/src/pages/ScheduledTasksList.tsx +416 -0
  135. package/desktop/src/pages/SessionControls.tsx +460 -0
  136. package/desktop/src/pages/Settings.tsx +1448 -0
  137. package/desktop/src/pages/ToolInspection.tsx +235 -0
  138. package/desktop/src/stores/adapterStore.ts +106 -0
  139. package/desktop/src/stores/agentStore.ts +34 -0
  140. package/desktop/src/stores/chatStore.test.ts +505 -0
  141. package/desktop/src/stores/chatStore.ts +850 -0
  142. package/desktop/src/stores/cliTaskStore.ts +152 -0
  143. package/desktop/src/stores/hahaOAuthStore.test.ts +77 -0
  144. package/desktop/src/stores/hahaOAuthStore.ts +97 -0
  145. package/desktop/src/stores/providerStore.ts +101 -0
  146. package/desktop/src/stores/sessionStore.test.ts +63 -0
  147. package/desktop/src/stores/sessionStore.ts +102 -0
  148. package/desktop/src/stores/settingsStore.ts +120 -0
  149. package/desktop/src/stores/skillStore.ts +51 -0
  150. package/desktop/src/stores/tabStore.ts +169 -0
  151. package/desktop/src/stores/taskStore.ts +68 -0
  152. package/desktop/src/stores/teamStore.ts +344 -0
  153. package/desktop/src/stores/uiStore.ts +100 -0
  154. package/desktop/src/stores/updateStore.test.ts +71 -0
  155. package/desktop/src/stores/updateStore.ts +221 -0
  156. package/desktop/src/theme/globals.css +465 -0
  157. package/desktop/src/types/adapter.ts +33 -0
  158. package/desktop/src/types/chat.ts +152 -0
  159. package/desktop/src/types/cliTask.ts +24 -0
  160. package/desktop/src/types/provider.ts +62 -0
  161. package/desktop/src/types/session.ts +27 -0
  162. package/desktop/src/types/settings.ts +22 -0
  163. package/desktop/src/types/skill.ts +38 -0
  164. package/desktop/src/types/task.ts +56 -0
  165. package/desktop/src/types/team.ts +38 -0
  166. package/desktop/src-tauri/Cargo.lock +5549 -0
  167. package/desktop/src-tauri/Cargo.toml +20 -0
  168. package/desktop/src-tauri/app-icon.svg +13 -0
  169. package/desktop/src-tauri/build.rs +3 -0
  170. package/desktop/src-tauri/capabilities/default.json +106 -0
  171. package/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml +5 -0
  172. package/desktop/src-tauri/icons/android/values/ic_launcher_background.xml +4 -0
  173. package/desktop/src-tauri/icons/icon.icns +0 -0
  174. package/desktop/src-tauri/icons/icon.ico +0 -0
  175. package/desktop/src-tauri/src/lib.rs +408 -0
  176. package/desktop/src-tauri/src/main.rs +6 -0
  177. package/desktop/src-tauri/tauri.conf.json +78 -0
  178. package/desktop/src-tauri/tauri.macos.conf.json +18 -0
  179. package/desktop/src-tauri/tauri.release-ci.json +5 -0
  180. package/desktop/src-tauri/tauri.windows.conf.json +16 -0
  181. package/desktop/src-tauri/windows-installer-hooks.nsh +17 -0
  182. package/desktop/tsconfig.json +25 -0
  183. package/desktop/vite.config.ts +26 -0
  184. package/desktop/vitest.config.ts +18 -0
  185. package/package.json +1 -1
  186. package/src/commands/desktop/desktop.tsx +9 -0
  187. package/src/commands/desktop/index.ts +26 -0
@@ -0,0 +1,420 @@
1
+ import { useState, useEffect, useCallback, useMemo } from 'react'
2
+ import { computerUseApi, type ComputerUseStatus, type SetupResult, type InstalledApp, type AuthorizedApp } from '../api/computerUse'
3
+ import { useTranslation } from '../i18n'
4
+
5
+ type CheckState = 'loading' | 'ready' | 'error'
6
+
7
+ function StatusIcon({ ok }: { ok: boolean | null }) {
8
+ if (ok === null) {
9
+ return <span className="material-symbols-outlined text-[18px] text-[var(--color-text-tertiary)]">help</span>
10
+ }
11
+ return ok ? (
12
+ <span className="material-symbols-outlined text-[18px] text-green-500" style={{ fontVariationSettings: "'FILL' 1" }}>check_circle</span>
13
+ ) : (
14
+ <span className="material-symbols-outlined text-[18px] text-red-400" style={{ fontVariationSettings: "'FILL' 1" }}>cancel</span>
15
+ )
16
+ }
17
+
18
+ function StatusRow({ label, ok, detail }: { label: string; ok: boolean | null; detail: string }) {
19
+ return (
20
+ <div className="flex items-center gap-3 py-2.5 px-4 rounded-lg bg-[var(--color-surface-container-low)]">
21
+ <StatusIcon ok={ok} />
22
+ <div className="flex-1 min-w-0">
23
+ <span className="text-sm font-medium text-[var(--color-text-primary)]">{label}</span>
24
+ <span className="ml-2 text-xs text-[var(--color-text-tertiary)]">{detail}</span>
25
+ </div>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ async function openSystemSettings(pane: 'Privacy_ScreenCapture' | 'Privacy_Accessibility') {
31
+ await computerUseApi.openSettings(pane)
32
+ }
33
+
34
+ export function ComputerUseSettings() {
35
+ const t = useTranslation()
36
+ const [status, setStatus] = useState<ComputerUseStatus | null>(null)
37
+ const [checkState, setCheckState] = useState<CheckState>('loading')
38
+ const [setupRunning, setSetupRunning] = useState(false)
39
+ const [setupResult, setSetupResult] = useState<SetupResult | null>(null)
40
+
41
+ // App authorization state
42
+ const [installedApps, setInstalledApps] = useState<InstalledApp[]>([])
43
+ const [authorizedBundleIds, setAuthorizedBundleIds] = useState<Set<string>>(new Set())
44
+ const [authorizedApps, setAuthorizedApps] = useState<AuthorizedApp[]>([])
45
+ const [appsLoading, setAppsLoading] = useState(false)
46
+ const [appsSaved, setAppsSaved] = useState(false)
47
+ const [searchQuery, setSearchQuery] = useState('')
48
+ const [clipboardAccess, setClipboardAccess] = useState(true)
49
+ const [systemKeys, setSystemKeys] = useState(true)
50
+
51
+ const fetchStatus = useCallback(async () => {
52
+ setCheckState('loading')
53
+ try {
54
+ const s = await computerUseApi.getStatus()
55
+ setStatus(s)
56
+ setCheckState('ready')
57
+ } catch {
58
+ setCheckState('error')
59
+ }
60
+ }, [])
61
+
62
+ const fetchApps = useCallback(async () => {
63
+ setAppsLoading(true)
64
+ try {
65
+ const [appsResult, configResult] = await Promise.all([
66
+ computerUseApi.getInstalledApps(),
67
+ computerUseApi.getAuthorizedApps(),
68
+ ])
69
+ setInstalledApps(appsResult.apps)
70
+ setAuthorizedApps(configResult.authorizedApps)
71
+ setAuthorizedBundleIds(new Set(configResult.authorizedApps.map(a => a.bundleId)))
72
+ setClipboardAccess(configResult.grantFlags.clipboardRead)
73
+ setSystemKeys(configResult.grantFlags.systemKeyCombos)
74
+ } catch {
75
+ // API not ready
76
+ } finally {
77
+ setAppsLoading(false)
78
+ }
79
+ }, [])
80
+
81
+ useEffect(() => {
82
+ fetchStatus()
83
+ }, [fetchStatus])
84
+
85
+ // Load apps when environment is ready
86
+ const envReady = status?.venv.created && status?.dependencies.installed
87
+ useEffect(() => {
88
+ if (envReady) fetchApps()
89
+ }, [envReady, fetchApps])
90
+
91
+ const handleSetup = async () => {
92
+ setSetupRunning(true)
93
+ setSetupResult(null)
94
+ try {
95
+ const result = await computerUseApi.runSetup()
96
+ setSetupResult(result)
97
+ await fetchStatus()
98
+ if (result.success) await fetchApps()
99
+ } catch {
100
+ setSetupResult({ success: false, steps: [{ name: 'error', ok: false, message: 'Request failed' }] })
101
+ } finally {
102
+ setSetupRunning(false)
103
+ }
104
+ }
105
+
106
+ const toggleApp = (app: InstalledApp) => {
107
+ const newSet = new Set(authorizedBundleIds)
108
+ let newAuthorized = [...authorizedApps]
109
+ if (newSet.has(app.bundleId)) {
110
+ newSet.delete(app.bundleId)
111
+ newAuthorized = newAuthorized.filter(a => a.bundleId !== app.bundleId)
112
+ } else {
113
+ newSet.add(app.bundleId)
114
+ newAuthorized.push({
115
+ bundleId: app.bundleId,
116
+ displayName: app.displayName,
117
+ authorizedAt: new Date().toISOString(),
118
+ })
119
+ }
120
+ setAuthorizedBundleIds(newSet)
121
+ setAuthorizedApps(newAuthorized)
122
+
123
+ // Auto-save
124
+ computerUseApi.setAuthorizedApps({
125
+ authorizedApps: newAuthorized,
126
+ grantFlags: { clipboardRead: clipboardAccess, clipboardWrite: clipboardAccess, systemKeyCombos: systemKeys },
127
+ }).then(() => {
128
+ setAppsSaved(true)
129
+ setTimeout(() => setAppsSaved(false), 1500)
130
+ })
131
+ }
132
+
133
+ const toggleFlag = (flag: 'clipboard' | 'systemKeys', value: boolean) => {
134
+ if (flag === 'clipboard') setClipboardAccess(value)
135
+ else setSystemKeys(value)
136
+
137
+ computerUseApi.setAuthorizedApps({
138
+ authorizedApps,
139
+ grantFlags: {
140
+ clipboardRead: flag === 'clipboard' ? value : clipboardAccess,
141
+ clipboardWrite: flag === 'clipboard' ? value : clipboardAccess,
142
+ systemKeyCombos: flag === 'systemKeys' ? value : systemKeys,
143
+ },
144
+ })
145
+ }
146
+
147
+ const allReady =
148
+ status?.supported &&
149
+ status.python.installed &&
150
+ status.venv.created &&
151
+ status.dependencies.installed
152
+
153
+ const accessibilityNeedsAttention = status?.permissions.accessibility === false
154
+ const screenRecordingNeedsAttention = status?.permissions.screenRecording === false
155
+ const screenRecordingReady = status ? status.permissions.screenRecording !== false : null
156
+
157
+ // Filter apps by search query
158
+ const filteredApps = useMemo(() => {
159
+ if (!searchQuery) return installedApps
160
+ const q = searchQuery.toLowerCase()
161
+ return installedApps.filter(
162
+ a => a.displayName.toLowerCase().includes(q) || a.bundleId.toLowerCase().includes(q)
163
+ )
164
+ }, [installedApps, searchQuery])
165
+
166
+ // Sort: authorized apps first, then alphabetical
167
+ const sortedApps = useMemo(() => {
168
+ return [...filteredApps].sort((a, b) => {
169
+ const aAuth = authorizedBundleIds.has(a.bundleId) ? 0 : 1
170
+ const bAuth = authorizedBundleIds.has(b.bundleId) ? 0 : 1
171
+ if (aAuth !== bAuth) return aAuth - bAuth
172
+ return a.displayName.localeCompare(b.displayName)
173
+ })
174
+ }, [filteredApps, authorizedBundleIds])
175
+
176
+ return (
177
+ <div className="max-w-2xl space-y-6">
178
+ {/* Title */}
179
+ <div>
180
+ <h2 className="text-lg font-semibold text-[var(--color-text-primary)]">
181
+ {t('settings.computerUse.title')}
182
+ </h2>
183
+ <p className="mt-1 text-sm text-[var(--color-text-secondary)]">
184
+ {t('settings.computerUse.description')}
185
+ </p>
186
+ </div>
187
+
188
+ {checkState === 'loading' ? (
189
+ <div className="py-8 text-center text-sm text-[var(--color-text-tertiary)]">
190
+ {t('common.loading')}
191
+ </div>
192
+ ) : checkState === 'error' ? (
193
+ <div className="py-8 text-center text-sm text-red-400">
194
+ Failed to check status.
195
+ <button onClick={fetchStatus} className="ml-2 underline">{t('common.retry')}</button>
196
+ </div>
197
+ ) : status ? (
198
+ <>
199
+ {!status.supported && (
200
+ <div className="px-4 py-3 rounded-lg bg-yellow-500/10 border border-yellow-500/30 text-sm text-yellow-600">
201
+ {t('settings.computerUse.notSupported')}
202
+ </div>
203
+ )}
204
+
205
+ {/* Status checks */}
206
+ <div className="space-y-2">
207
+ <StatusRow
208
+ label={t('settings.computerUse.python')}
209
+ ok={status.python.installed}
210
+ detail={
211
+ status.python.installed
212
+ ? `${t('settings.computerUse.pythonFound')} — ${status.python.version} (${status.python.path})`
213
+ : t('settings.computerUse.pythonNotFound')
214
+ }
215
+ />
216
+ <StatusRow
217
+ label={t('settings.computerUse.venv')}
218
+ ok={status.venv.created}
219
+ detail={status.venv.created ? `${t('settings.computerUse.venvReady')} — ${status.venv.path}` : t('settings.computerUse.venvNotReady')}
220
+ />
221
+ <StatusRow
222
+ label={t('settings.computerUse.deps')}
223
+ ok={status.dependencies.installed}
224
+ detail={status.dependencies.installed ? t('settings.computerUse.depsReady') : t('settings.computerUse.depsNotReady')}
225
+ />
226
+ </div>
227
+
228
+ {/* macOS Permissions — only shown on macOS (darwin) */}
229
+ {envReady && status.platform === 'darwin' && (
230
+ <>
231
+ <StatusRow
232
+ label={t('settings.computerUse.accessibility')}
233
+ ok={status.permissions.accessibility}
234
+ detail={
235
+ status.permissions.accessibility === null ? t('settings.computerUse.permUnknown')
236
+ : status.permissions.accessibility ? t('settings.computerUse.permGranted')
237
+ : t('settings.computerUse.permDenied')
238
+ }
239
+ />
240
+ <StatusRow
241
+ label={t('settings.computerUse.screenRecording')}
242
+ ok={screenRecordingReady}
243
+ detail={
244
+ status.permissions.screenRecording === true ? t('settings.computerUse.permGranted')
245
+ : status.permissions.screenRecording === false ? t('settings.computerUse.permDenied')
246
+ : t('settings.computerUse.permScreenRecordingUnknownSoft')
247
+ }
248
+ />
249
+ {(accessibilityNeedsAttention || screenRecordingNeedsAttention) && (
250
+ <div className="flex flex-col gap-2 px-4 py-3 rounded-lg bg-yellow-500/5 border border-yellow-500/20">
251
+ <p className="text-xs text-[var(--color-text-tertiary)]">{t('settings.computerUse.permRestartHint')}</p>
252
+ <div className="flex gap-2">
253
+ {accessibilityNeedsAttention && (
254
+ <button
255
+ onClick={() => openSystemSettings('Privacy_Accessibility')}
256
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-[var(--color-text-accent)] border border-[var(--color-border)] rounded-lg hover:bg-[var(--color-surface-hover)]"
257
+ >
258
+ <span className="material-symbols-outlined text-[14px]">open_in_new</span>
259
+ {t('settings.computerUse.openAccessibility')}
260
+ </button>
261
+ )}
262
+ {screenRecordingNeedsAttention && (
263
+ <button
264
+ onClick={() => openSystemSettings('Privacy_ScreenCapture')}
265
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-[var(--color-text-accent)] border border-[var(--color-border)] rounded-lg hover:bg-[var(--color-surface-hover)]"
266
+ >
267
+ <span className="material-symbols-outlined text-[14px]">open_in_new</span>
268
+ {t('settings.computerUse.openScreenRecording')}
269
+ </button>
270
+ )}
271
+ </div>
272
+ </div>
273
+ )}
274
+ </>
275
+ )}
276
+
277
+ {allReady && (status.platform !== 'darwin' || (status.permissions.accessibility && screenRecordingReady)) && (
278
+ <div className="px-4 py-3 rounded-lg bg-green-500/10 border border-green-500/30 text-sm text-green-600 flex items-center gap-2">
279
+ <span className="material-symbols-outlined text-[18px]" style={{ fontVariationSettings: "'FILL' 1" }}>verified</span>
280
+ {t('settings.computerUse.allReady')}
281
+ </div>
282
+ )}
283
+
284
+ {setupResult && (
285
+ <div className={`rounded-lg border p-4 space-y-2 ${setupResult.success ? 'bg-green-500/5 border-green-500/30' : 'bg-red-500/5 border-red-500/30'}`}>
286
+ <div className={`text-sm font-medium ${setupResult.success ? 'text-green-600' : 'text-red-400'}`}>
287
+ {setupResult.success ? t('settings.computerUse.setupSuccess') : t('settings.computerUse.setupFail')}
288
+ </div>
289
+ {setupResult.steps.map((step, i) => (
290
+ <div key={i} className="flex items-center gap-2 text-xs text-[var(--color-text-secondary)]">
291
+ <StatusIcon ok={step.ok} />
292
+ <span>{step.message}</span>
293
+ </div>
294
+ ))}
295
+ </div>
296
+ )}
297
+
298
+ {/* Action buttons */}
299
+ <div className="flex gap-3">
300
+ {!envReady && status.python.installed && (
301
+ <button
302
+ onClick={handleSetup}
303
+ disabled={setupRunning}
304
+ className="flex items-center gap-2 px-5 py-2.5 bg-[var(--color-brand)] text-white text-sm font-semibold rounded-lg hover:opacity-90 disabled:opacity-50 transition-opacity"
305
+ >
306
+ <span className="material-symbols-outlined text-[18px]">{setupRunning ? 'hourglass_empty' : 'download'}</span>
307
+ {setupRunning ? t('settings.computerUse.setupRunning') : t('settings.computerUse.setupBtn')}
308
+ </button>
309
+ )}
310
+ <button
311
+ onClick={fetchStatus}
312
+ className="flex items-center gap-2 px-4 py-2.5 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] border border-[var(--color-border)] rounded-lg hover:bg-[var(--color-surface-hover)] transition-colors"
313
+ >
314
+ <span className="material-symbols-outlined text-[18px]">refresh</span>
315
+ {t('settings.computerUse.recheckBtn')}
316
+ </button>
317
+ </div>
318
+
319
+ {/* ─── App Authorization Section ─── */}
320
+ {envReady && (
321
+ <div className="space-y-4 pt-4 border-t border-[var(--color-border)]">
322
+ <div>
323
+ <h3 className="text-base font-semibold text-[var(--color-text-primary)] flex items-center gap-2">
324
+ {t('settings.computerUse.appsTitle')}
325
+ {appsSaved && (
326
+ <span className="text-xs font-normal text-green-500 flex items-center gap-1">
327
+ <span className="material-symbols-outlined text-[14px]" style={{ fontVariationSettings: "'FILL' 1" }}>check</span>
328
+ {t('settings.computerUse.appsSaved')}
329
+ </span>
330
+ )}
331
+ </h3>
332
+ <p className="mt-1 text-sm text-[var(--color-text-secondary)]">
333
+ {t('settings.computerUse.appsDescription')}
334
+ </p>
335
+ </div>
336
+
337
+ {/* Grant flags */}
338
+ <div className="flex gap-4">
339
+ <label className="flex items-center gap-2 text-sm text-[var(--color-text-secondary)] cursor-pointer">
340
+ <input
341
+ type="checkbox"
342
+ checked={clipboardAccess}
343
+ onChange={e => toggleFlag('clipboard', e.target.checked)}
344
+ className="rounded border-[var(--color-border)] accent-[var(--color-brand)]"
345
+ />
346
+ {t('settings.computerUse.flagClipboard')}
347
+ </label>
348
+ <label className="flex items-center gap-2 text-sm text-[var(--color-text-secondary)] cursor-pointer">
349
+ <input
350
+ type="checkbox"
351
+ checked={systemKeys}
352
+ onChange={e => toggleFlag('systemKeys', e.target.checked)}
353
+ className="rounded border-[var(--color-border)] accent-[var(--color-brand)]"
354
+ />
355
+ {t('settings.computerUse.flagSystemKeys')}
356
+ </label>
357
+ </div>
358
+
359
+ {/* Search */}
360
+ <div className="relative">
361
+ <span className="material-symbols-outlined text-[18px] text-[var(--color-text-tertiary)] absolute left-3 top-1/2 -translate-y-1/2">search</span>
362
+ <input
363
+ type="text"
364
+ value={searchQuery}
365
+ onChange={e => setSearchQuery(e.target.value)}
366
+ placeholder={t('settings.computerUse.appsSearch')}
367
+ className="w-full pl-9 pr-4 py-2 text-sm bg-[var(--color-surface-container-low)] border border-[var(--color-border)] rounded-lg text-[var(--color-text-primary)] placeholder:text-[var(--color-text-tertiary)] focus:outline-none focus:border-[var(--color-brand)]"
368
+ />
369
+ </div>
370
+
371
+ {/* App list */}
372
+ {appsLoading ? (
373
+ <div className="py-6 text-center text-sm text-[var(--color-text-tertiary)]">
374
+ {t('settings.computerUse.appsLoading')}
375
+ </div>
376
+ ) : installedApps.length === 0 ? (
377
+ <div className="py-6 text-center text-sm text-[var(--color-text-tertiary)]">
378
+ {t('settings.computerUse.appsEmpty')}
379
+ </div>
380
+ ) : (
381
+ <div className="max-h-[400px] overflow-y-auto rounded-lg border border-[var(--color-border)]">
382
+ {sortedApps.map(app => {
383
+ const isAuthorized = authorizedBundleIds.has(app.bundleId)
384
+ return (
385
+ <button
386
+ key={app.bundleId}
387
+ onClick={() => toggleApp(app)}
388
+ className={`w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-[var(--color-surface-hover)] border-b border-[var(--color-border)] last:border-b-0 ${
389
+ isAuthorized ? 'bg-[var(--color-brand)]/5' : ''
390
+ }`}
391
+ >
392
+ <div className={`w-5 h-5 rounded flex items-center justify-center flex-shrink-0 border ${
393
+ isAuthorized
394
+ ? 'bg-[var(--color-brand)] border-[var(--color-brand)]'
395
+ : 'border-[var(--color-border)]'
396
+ }`}>
397
+ {isAuthorized && (
398
+ <span className="material-symbols-outlined text-[14px] text-white" style={{ fontVariationSettings: "'FILL' 1" }}>check</span>
399
+ )}
400
+ </div>
401
+ <div className="flex-1 min-w-0">
402
+ <div className="text-sm font-medium text-[var(--color-text-primary)] truncate">
403
+ {app.displayName}
404
+ </div>
405
+ <div className="text-[11px] text-[var(--color-text-tertiary)] truncate font-mono">
406
+ {app.bundleId}
407
+ </div>
408
+ </div>
409
+ </button>
410
+ )
411
+ })}
412
+ </div>
413
+ )}
414
+ </div>
415
+ )}
416
+ </>
417
+ ) : null}
418
+ </div>
419
+ )
420
+ }