saeeol 1.2.1 → 1.2.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 (151) hide show
  1. package/bin/saeeol.cjs +187 -0
  2. package/npm/bin/saeeol +0 -0
  3. package/package.json +12 -12
  4. package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
  5. package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
  6. package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
  7. package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
  8. package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
  9. package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
  10. package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
  11. package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
  12. package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
  13. package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
  14. package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
  15. package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
  16. package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
  17. package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
  18. package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
  19. package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
  20. package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
  21. package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
  22. package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
  23. package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
  24. package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
  25. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
  26. package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
  27. package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
  28. package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
  29. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
  30. package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
  31. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
  32. package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
  33. package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
  34. package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
  35. package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
  36. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
  37. package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
  38. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
  39. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
  40. package/src/cli/cmd/tui/context/app/args.tsx +15 -0
  41. package/src/cli/cmd/tui/context/app/directory.ts +15 -0
  42. package/src/cli/cmd/tui/context/app/editor-zed.ts +281 -0
  43. package/src/cli/cmd/tui/context/app/editor.ts +425 -0
  44. package/src/cli/cmd/tui/context/app/helper.tsx +25 -0
  45. package/src/cli/cmd/tui/context/app/project.tsx +109 -0
  46. package/src/cli/cmd/tui/context/app/route.tsx +67 -0
  47. package/src/cli/cmd/tui/context/app/sdk.tsx +142 -0
  48. package/src/cli/cmd/tui/context/app/sync.tsx +713 -0
  49. package/src/cli/cmd/tui/context/app/theme.tsx +307 -0
  50. package/src/cli/cmd/tui/context/app/tui-config.tsx +9 -0
  51. package/src/cli/cmd/tui/context/args.tsx +1 -15
  52. package/src/cli/cmd/tui/context/directory.ts +1 -15
  53. package/src/cli/cmd/tui/context/editor-zed.ts +1 -281
  54. package/src/cli/cmd/tui/context/editor.ts +1 -425
  55. package/src/cli/cmd/tui/context/event.ts +1 -45
  56. package/src/cli/cmd/tui/context/exit.tsx +1 -67
  57. package/src/cli/cmd/tui/context/helper.tsx +1 -25
  58. package/src/cli/cmd/tui/context/keybind.tsx +1 -105
  59. package/src/cli/cmd/tui/context/kv.tsx +1 -76
  60. package/src/cli/cmd/tui/context/local.tsx +1 -478
  61. package/src/cli/cmd/tui/context/plugin-keybinds.ts +1 -41
  62. package/src/cli/cmd/tui/context/project.tsx +1 -109
  63. package/src/cli/cmd/tui/context/prompt.tsx +1 -18
  64. package/src/cli/cmd/tui/context/route.tsx +1 -67
  65. package/src/cli/cmd/tui/context/runtime/event.ts +45 -0
  66. package/src/cli/cmd/tui/context/runtime/exit.tsx +67 -0
  67. package/src/cli/cmd/tui/context/runtime/keybind.tsx +105 -0
  68. package/src/cli/cmd/tui/context/runtime/kv.tsx +76 -0
  69. package/src/cli/cmd/tui/context/runtime/local.tsx +478 -0
  70. package/src/cli/cmd/tui/context/runtime/plugin-keybinds.ts +41 -0
  71. package/src/cli/cmd/tui/context/sdk.tsx +1 -142
  72. package/src/cli/cmd/tui/context/session/prompt.tsx +18 -0
  73. package/src/cli/cmd/tui/context/sync.tsx +1 -713
  74. package/src/cli/cmd/tui/context/theme.tsx +1 -307
  75. package/src/cli/cmd/tui/context/tui-config.tsx +1 -9
  76. package/src/tool/apply_patch.ts +1 -334
  77. package/src/tool/bash.ts +1 -656
  78. package/src/tool/core/external-directory.ts +55 -0
  79. package/src/tool/core/invalid.ts +21 -0
  80. package/src/tool/core/recall.ts +164 -0
  81. package/src/tool/core/recall.txt +12 -0
  82. package/src/tool/core/schema.ts +16 -0
  83. package/src/tool/core/tool.ts +162 -0
  84. package/src/tool/core/truncate.ts +160 -0
  85. package/src/tool/core/truncation-dir.ts +4 -0
  86. package/src/tool/diagnostics.ts +1 -20
  87. package/src/tool/edit-replacers.ts +1 -288
  88. package/src/tool/edit-utils.ts +1 -86
  89. package/src/tool/edit.ts +1 -262
  90. package/src/tool/external-directory.ts +1 -55
  91. package/src/tool/file/apply_patch.ts +334 -0
  92. package/src/tool/file/apply_patch.txt +33 -0
  93. package/src/tool/file/bash.ts +656 -0
  94. package/src/tool/file/bash.txt +119 -0
  95. package/src/tool/file/edit-replacers.ts +288 -0
  96. package/src/tool/file/edit-utils.ts +86 -0
  97. package/src/tool/file/edit.ts +262 -0
  98. package/src/tool/file/edit.txt +10 -0
  99. package/src/tool/file/read.ts +389 -0
  100. package/src/tool/file/read.txt +14 -0
  101. package/src/tool/file/write.ts +114 -0
  102. package/src/tool/file/write.txt +8 -0
  103. package/src/tool/glob.ts +1 -115
  104. package/src/tool/grep.ts +1 -151
  105. package/src/tool/integration/diagnostics.ts +20 -0
  106. package/src/tool/integration/lsp.ts +113 -0
  107. package/src/tool/integration/lsp.txt +24 -0
  108. package/src/tool/integration/mcp-exa.ts +73 -0
  109. package/src/tool/integration/package.ts +168 -0
  110. package/src/tool/integration/registry.ts +375 -0
  111. package/src/tool/invalid.ts +1 -21
  112. package/src/tool/lsp.ts +1 -113
  113. package/src/tool/mcp-exa.ts +1 -73
  114. package/src/tool/package.ts +1 -168
  115. package/src/tool/plan.ts +1 -30
  116. package/src/tool/question.ts +1 -52
  117. package/src/tool/read.ts +1 -389
  118. package/src/tool/recall.ts +1 -164
  119. package/src/tool/registry.ts +1 -375
  120. package/src/tool/schema.ts +1 -16
  121. package/src/tool/search/glob.ts +115 -0
  122. package/src/tool/search/glob.txt +6 -0
  123. package/src/tool/search/grep.ts +151 -0
  124. package/src/tool/search/grep.txt +8 -0
  125. package/src/tool/search/warpgrep.ts +107 -0
  126. package/src/tool/search/warpgrep.txt +10 -0
  127. package/src/tool/search/webfetch.ts +202 -0
  128. package/src/tool/search/webfetch.txt +13 -0
  129. package/src/tool/search/websearch.ts +71 -0
  130. package/src/tool/search/websearch.txt +14 -0
  131. package/src/tool/skill.ts +1 -91
  132. package/src/tool/task.ts +1 -197
  133. package/src/tool/todo.ts +1 -62
  134. package/src/tool/tool.ts +1 -162
  135. package/src/tool/truncate.ts +1 -160
  136. package/src/tool/truncation-dir.ts +1 -4
  137. package/src/tool/warpgrep.ts +1 -107
  138. package/src/tool/webfetch.ts +1 -202
  139. package/src/tool/websearch.ts +1 -71
  140. package/src/tool/workflow/plan-enter.txt +14 -0
  141. package/src/tool/workflow/plan-exit.txt +13 -0
  142. package/src/tool/workflow/plan.ts +30 -0
  143. package/src/tool/workflow/question.ts +52 -0
  144. package/src/tool/workflow/question.txt +11 -0
  145. package/src/tool/workflow/skill.ts +91 -0
  146. package/src/tool/workflow/skill.txt +5 -0
  147. package/src/tool/workflow/task.ts +197 -0
  148. package/src/tool/workflow/task.txt +57 -0
  149. package/src/tool/workflow/todo.ts +62 -0
  150. package/src/tool/workflow/todowrite.txt +167 -0
  151. package/src/tool/write.ts +1 -114
@@ -0,0 +1,343 @@
1
+ import { createMemo, createSignal, onMount, Show } from "solid-js"
2
+ import { useSync } from "@tui/context/sync"
3
+ import { map, pipe, sortBy } from "remeda"
4
+ import { DialogSelect } from "@tui/ui/dialog-select"
5
+ import { useDialog } from "@tui/ui/dialog"
6
+ import { useSDK } from "../../context/sdk"
7
+ import { DialogPrompt } from "../../ui/dialog-prompt"
8
+ import { Link } from "../../ui/link"
9
+ import { useTheme } from "../../context/theme"
10
+ import { TextAttributes } from "@opentui/core"
11
+ import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@saeeol/sdk/v2"
12
+ import { DialogModel } from "./dialog-model"
13
+ import { useKeyboard } from "@opentui/solid"
14
+ import * as Clipboard from "@tui/util/clipboard"
15
+ import { useToast } from "../../ui/toast"
16
+ import { isConsoleManagedProvider } from "@tui/util/provider-origin"
17
+ import * as Provider from "@/saeeol/cli/cmd/tui/component/dialog-provider"
18
+ import { useConnected } from "../use-connected"
19
+
20
+ const PROVIDER_PRIORITY: Record<string, number> = Provider.PROVIDER_PRIORITY
21
+
22
+ export function createDialogProviderOptions() {
23
+ const sync = useSync()
24
+ const dialog = useDialog()
25
+ const sdk = useSDK()
26
+ const toast = useToast()
27
+ const { theme } = useTheme()
28
+ const onboarded = useConnected()
29
+ const options = createMemo(() => {
30
+ return pipe(
31
+ sync.data.provider_next.all,
32
+ sortBy((x) => PROVIDER_PRIORITY[x.id] ?? 99),
33
+ map((provider) => {
34
+ const consoleManaged = isConsoleManagedProvider(sync.data.console_state.consoleManagedProviders, provider.id)
35
+ const connected = sync.data.provider_next.connected.includes(provider.id)
36
+ const failed = sync.data.provider_next.failed ?? []
37
+ const failedGutter = Provider.renderGutter(provider.id, failed, theme)
38
+ const failedDesc = Provider.failedDescription(provider.id, failed)
39
+ const baseDesc = Provider.PROVIDER_DESCRIPTIONS[provider.id]
40
+
41
+ return {
42
+ title: Provider.PROVIDER_TITLES[provider.id] ?? provider.name,
43
+ value: provider.id,
44
+ description: failedDesc ?? baseDesc,
45
+ footer: consoleManaged ? sync.data.console_state.activeOrgName : undefined,
46
+ category: provider.id in PROVIDER_PRIORITY ? "Popular" : "Other",
47
+ gutter: failedGutter ?? (connected && onboarded() ? () => <text fg={theme.success}>✓</text> : undefined),
48
+ async onSelect() {
49
+ if (consoleManaged) return
50
+
51
+ const methods = sync.data.provider_auth[provider.id] ?? [
52
+ {
53
+ type: "api",
54
+ label: "API key",
55
+ },
56
+ ]
57
+ let index: number | null = 0
58
+ if (methods.length > 1) {
59
+ index = await new Promise<number | null>((resolve) => {
60
+ dialog.replace(
61
+ () => (
62
+ <DialogSelect
63
+ title="Select auth method"
64
+ options={methods.map((x, index) => ({
65
+ title: x.label,
66
+ value: index,
67
+ }))}
68
+ onSelect={(option) => resolve(option.value)}
69
+ />
70
+ ),
71
+ () => resolve(null),
72
+ )
73
+ })
74
+ }
75
+ if (index == null) return
76
+ const method = methods[index]
77
+ if (method.type === "oauth") {
78
+ let inputs: Record<string, string> | undefined
79
+ if (method.prompts?.length) {
80
+ const value = await PromptsMethod({
81
+ dialog,
82
+ prompts: method.prompts,
83
+ })
84
+ if (!value) return
85
+ inputs = value
86
+ }
87
+
88
+ const result = await sdk.client.provider.oauth.authorize({
89
+ providerID: provider.id,
90
+ method: index,
91
+ inputs,
92
+ })
93
+ if (result.error) {
94
+ toast.show({
95
+ variant: "error",
96
+ message: JSON.stringify(result.error),
97
+ })
98
+ dialog.clear()
99
+ return
100
+ }
101
+ if (result.data?.method === "code") {
102
+ dialog.replace(() => (
103
+ <CodeMethod
104
+ providerID={provider.id}
105
+ title={method.label}
106
+ index={index}
107
+ authorization={result.data!}
108
+ />
109
+ ))
110
+ }
111
+ if (result.data?.method === "auto") {
112
+ const saeeol = Provider.renderAutoMethod({
113
+ providerID: provider.id,
114
+ title: method.label,
115
+ index,
116
+ authorization: result.data!,
117
+ useSDK,
118
+ useTheme,
119
+ DialogModel,
120
+ })
121
+ if (saeeol) {
122
+ dialog.replace(saeeol)
123
+ } else {
124
+ dialog.replace(() => (
125
+ <AutoMethod
126
+ providerID={provider.id}
127
+ title={method.label}
128
+ index={index}
129
+ authorization={result.data!}
130
+ />
131
+ ))
132
+ }
133
+ }
134
+ }
135
+ if (method.type === "api") {
136
+ let metadata: Record<string, string> | undefined
137
+ if (method.prompts?.length) {
138
+ const value = await PromptsMethod({ dialog, prompts: method.prompts })
139
+ if (!value) return
140
+ metadata = value
141
+ }
142
+ return dialog.replace(() => (
143
+ <ApiMethod providerID={provider.id} title={method.label} metadata={metadata} />
144
+ ))
145
+ }
146
+ },
147
+ }
148
+ }),
149
+ )
150
+ })
151
+ return options
152
+ }
153
+
154
+ export function DialogProvider() {
155
+ const options = createDialogProviderOptions()
156
+ return <DialogSelect title="Connect a provider" options={options()} />
157
+ }
158
+
159
+ interface AutoMethodProps {
160
+ index: number
161
+ providerID: string
162
+ title: string
163
+ authorization: ProviderAuthAuthorization
164
+ }
165
+ function AutoMethod(props: AutoMethodProps) {
166
+ const { theme } = useTheme()
167
+ const sdk = useSDK()
168
+ const dialog = useDialog()
169
+ const sync = useSync()
170
+ const toast = useToast()
171
+
172
+ useKeyboard((evt) => {
173
+ if (evt.name === "c" && !evt.ctrl && !evt.meta) {
174
+ const code = props.authorization.instructions.match(/[A-Z0-9]{4}-[A-Z0-9]{4,5}/)?.[0] ?? props.authorization.url
175
+ Clipboard.copy(code)
176
+ .then(() => toast.show({ message: "Copied to clipboard", variant: "info" }))
177
+ .catch(toast.error)
178
+ }
179
+ })
180
+
181
+ onMount(async () => {
182
+ const result = await sdk.client.provider.oauth.callback({
183
+ providerID: props.providerID,
184
+ method: props.index,
185
+ })
186
+ if (result.error) {
187
+ dialog.clear()
188
+ return
189
+ }
190
+ await sdk.client.instance.dispose()
191
+ await sync.bootstrap()
192
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
193
+ })
194
+
195
+ return (
196
+ <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
197
+ <box flexDirection="row" justifyContent="space-between">
198
+ <text attributes={TextAttributes.BOLD} fg={theme.text}>
199
+ {props.title}
200
+ </text>
201
+ <text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
202
+ esc
203
+ </text>
204
+ </box>
205
+ <box gap={1}>
206
+ <Link href={props.authorization.url} fg={theme.primary} />
207
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
208
+ </box>
209
+ <text fg={theme.textMuted}>Waiting for authorization...</text>
210
+ <text fg={theme.text}>
211
+ c <span style={{ fg: theme.textMuted }}>copy</span>
212
+ </text>
213
+ </box>
214
+ )
215
+ }
216
+
217
+ interface CodeMethodProps {
218
+ index: number
219
+ title: string
220
+ providerID: string
221
+ authorization: ProviderAuthAuthorization
222
+ }
223
+ function CodeMethod(props: CodeMethodProps) {
224
+ const { theme } = useTheme()
225
+ const sdk = useSDK()
226
+ const sync = useSync()
227
+ const dialog = useDialog()
228
+ const [error, setError] = createSignal(false)
229
+
230
+ return (
231
+ <DialogPrompt
232
+ title={props.title}
233
+ placeholder="Authorization code"
234
+ onConfirm={async (value) => {
235
+ const { error } = await sdk.client.provider.oauth.callback({
236
+ providerID: props.providerID,
237
+ method: props.index,
238
+ code: value,
239
+ })
240
+ if (!error) {
241
+ await sdk.client.instance.dispose()
242
+ await sync.bootstrap()
243
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
244
+ return
245
+ }
246
+ setError(true)
247
+ }}
248
+ description={() => (
249
+ <box gap={1}>
250
+ <text fg={theme.textMuted}>{props.authorization.instructions}</text>
251
+ <Link href={props.authorization.url} fg={theme.primary} />
252
+ <Show when={error()}>
253
+ <text fg={theme.error}>Invalid code</text>
254
+ </Show>
255
+ </box>
256
+ )}
257
+ />
258
+ )
259
+ }
260
+
261
+ interface ApiMethodProps {
262
+ providerID: string
263
+ title: string
264
+ metadata?: Record<string, string>
265
+ }
266
+ function ApiMethod(props: ApiMethodProps) {
267
+ const dialog = useDialog()
268
+ const sdk = useSDK()
269
+ const sync = useSync()
270
+ const { theme } = useTheme()
271
+
272
+ return (
273
+ <DialogPrompt
274
+ title={props.title}
275
+ placeholder="API key"
276
+ description={Provider.renderApiDescription(props.providerID, theme)}
277
+ onConfirm={async (value) => {
278
+ if (!value) return
279
+ await sdk.client.auth.set({
280
+ providerID: props.providerID,
281
+ auth: {
282
+ type: "api",
283
+ key: value,
284
+ ...(props.metadata ? { metadata: props.metadata } : {}),
285
+ },
286
+ })
287
+ await sdk.client.instance.dispose()
288
+ await sync.bootstrap()
289
+ dialog.replace(() => <DialogModel providerID={props.providerID} />)
290
+ }}
291
+ />
292
+ )
293
+ }
294
+
295
+ interface PromptsMethodProps {
296
+ dialog: ReturnType<typeof useDialog>
297
+ prompts: NonNullable<ProviderAuthMethod["prompts"]>[number][]
298
+ }
299
+ async function PromptsMethod(props: PromptsMethodProps) {
300
+ const inputs: Record<string, string> = {}
301
+ for (const prompt of props.prompts) {
302
+ if (prompt.when) {
303
+ const value = inputs[prompt.when.key]
304
+ if (value === undefined) continue
305
+ const matches = prompt.when.op === "eq" ? value === prompt.when.value : value !== prompt.when.value
306
+ if (!matches) continue
307
+ }
308
+
309
+ if (prompt.type === "select") {
310
+ const value = await new Promise<string | null>((resolve) => {
311
+ props.dialog.replace(
312
+ () => (
313
+ <DialogSelect
314
+ title={prompt.message}
315
+ options={prompt.options.map((x) => ({
316
+ title: x.label,
317
+ value: x.value,
318
+ description: x.hint,
319
+ }))}
320
+ onSelect={(option) => resolve(option.value)}
321
+ />
322
+ ),
323
+ () => resolve(null),
324
+ )
325
+ })
326
+ if (value === null) return null
327
+ inputs[prompt.key] = value
328
+ continue
329
+ }
330
+
331
+ const value = await new Promise<string | null>((resolve) => {
332
+ props.dialog.replace(
333
+ () => (
334
+ <DialogPrompt title={prompt.message} placeholder={prompt.placeholder} onConfirm={(value) => resolve(value)} />
335
+ ),
336
+ () => resolve(null),
337
+ )
338
+ })
339
+ if (value === null) return null
340
+ inputs[prompt.key] = value
341
+ }
342
+ return inputs
343
+ }
@@ -0,0 +1,103 @@
1
+ import { TextAttributes } from "@opentui/core"
2
+ import { useTheme } from "../../context/theme"
3
+ import { useDialog } from "../../ui/dialog"
4
+ import { createStore } from "solid-js/store"
5
+ import { For } from "solid-js"
6
+ import { useKeyboard } from "@opentui/solid"
7
+
8
+ export function DialogSessionDeleteFailed(props: {
9
+ session: string
10
+ workspace: string
11
+ onDelete?: () => boolean | void | Promise<boolean | void>
12
+ onRestore?: () => boolean | void | Promise<boolean | void>
13
+ onDone?: () => void
14
+ }) {
15
+ const dialog = useDialog()
16
+ const { theme } = useTheme()
17
+ const [store, setStore] = createStore({
18
+ active: "delete" as "delete" | "restore",
19
+ })
20
+
21
+ const options = [
22
+ {
23
+ id: "delete" as const,
24
+ title: "Delete workspace",
25
+ description: "Delete the workspace and all sessions attached to it.",
26
+ run: props.onDelete,
27
+ },
28
+ {
29
+ id: "restore" as const,
30
+ title: "Restore to new workspace",
31
+ description: "Try to restore this session into a new workspace.",
32
+ run: props.onRestore,
33
+ },
34
+ ]
35
+
36
+ async function confirm() {
37
+ const result = await options.find((item) => item.id === store.active)?.run?.()
38
+ if (result === false) return
39
+ props.onDone?.()
40
+ if (!props.onDone) dialog.clear()
41
+ }
42
+
43
+ useKeyboard((evt) => {
44
+ if (evt.name === "return") {
45
+ evt.preventDefault()
46
+ evt.stopPropagation()
47
+ void confirm()
48
+ }
49
+ if (evt.name === "left" || evt.name === "up") {
50
+ setStore("active", "delete")
51
+ }
52
+ if (evt.name === "right" || evt.name === "down") {
53
+ setStore("active", "restore")
54
+ }
55
+ })
56
+
57
+ return (
58
+ <box paddingLeft={2} paddingRight={2} gap={1}>
59
+ <box flexDirection="row" justifyContent="space-between">
60
+ <text attributes={TextAttributes.BOLD} fg={theme.text}>
61
+ Failed to Delete Session
62
+ </text>
63
+ <text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
64
+ esc
65
+ </text>
66
+ </box>
67
+ <text fg={theme.textMuted} wrapMode="word">
68
+ {`The session "${props.session}" could not be deleted because the workspace "${props.workspace}" is not available.`}
69
+ </text>
70
+ <text fg={theme.textMuted} wrapMode="word">
71
+ Choose how you want to recover this broken workspace session.
72
+ </text>
73
+ <box flexDirection="column" paddingBottom={1} gap={1}>
74
+ <For each={options}>
75
+ {(item) => (
76
+ <box
77
+ flexDirection="column"
78
+ paddingLeft={1}
79
+ paddingRight={1}
80
+ paddingTop={1}
81
+ paddingBottom={1}
82
+ backgroundColor={item.id === store.active ? theme.primary : undefined}
83
+ onMouseUp={() => {
84
+ setStore("active", item.id)
85
+ void confirm()
86
+ }}
87
+ >
88
+ <text
89
+ attributes={TextAttributes.BOLD}
90
+ fg={item.id === store.active ? theme.selectedListItemText : theme.text}
91
+ >
92
+ {item.title}
93
+ </text>
94
+ <text fg={item.id === store.active ? theme.selectedListItemText : theme.textMuted} wrapMode="word">
95
+ {item.description}
96
+ </text>
97
+ </box>
98
+ )}
99
+ </For>
100
+ </box>
101
+ </box>
102
+ )
103
+ }