saeeol 1.2.0 → 1.2.2

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 (193) hide show
  1. package/package.json +14 -14
  2. package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
  3. package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
  4. package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
  5. package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
  6. package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
  7. package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
  8. package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
  9. package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
  10. package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
  11. package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
  12. package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
  13. package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
  14. package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
  15. package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
  16. package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
  17. package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
  18. package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
  19. package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
  20. package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
  21. package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
  22. package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
  23. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
  24. package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
  25. package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
  26. package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
  27. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
  28. package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
  29. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
  30. package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
  31. package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
  32. package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
  33. package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
  34. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
  35. package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
  36. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
  37. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
  38. package/src/session/compaction-helpers.ts +1 -169
  39. package/src/session/compaction.ts +1 -712
  40. package/src/session/core/compaction/compaction-helpers.ts +169 -0
  41. package/src/session/core/compaction/compaction.ts +712 -0
  42. package/src/session/core/compaction/overflow.ts +28 -0
  43. package/src/session/core/instruction.ts +234 -0
  44. package/src/session/core/llm.ts +504 -0
  45. package/src/session/core/network.ts +392 -0
  46. package/src/session/core/processor.ts +731 -0
  47. package/src/session/core/projectors.ts +139 -0
  48. package/src/session/core/resolve-tools.ts +241 -0
  49. package/src/session/core/retry.ts +149 -0
  50. package/src/session/core/revert.ts +173 -0
  51. package/src/session/core/run-state.ts +110 -0
  52. package/src/session/core/schema.ts +35 -0
  53. package/src/session/core/session-types.ts +160 -0
  54. package/src/session/core/session.sql.ts +124 -0
  55. package/src/session/core/session.ts +948 -0
  56. package/src/session/core/shell-exec.ts +205 -0
  57. package/src/session/core/status.ts +100 -0
  58. package/src/session/core/subtask.ts +268 -0
  59. package/src/session/core/summary.ts +173 -0
  60. package/src/session/core/system.ts +114 -0
  61. package/src/session/core/todo.ts +86 -0
  62. package/src/session/core/user-part.ts +293 -0
  63. package/src/session/instruction.ts +1 -234
  64. package/src/session/llm.ts +1 -504
  65. package/src/session/message/message-errors.ts +83 -0
  66. package/src/session/message/message-parts.ts +89 -0
  67. package/src/session/message/message-query.ts +107 -0
  68. package/src/session/message/message-transform.ts +156 -0
  69. package/src/session/message/message-types.ts +68 -0
  70. package/src/session/message/message-v2.ts +73 -0
  71. package/src/session/message/message.ts +192 -0
  72. package/src/session/message-errors.ts +1 -83
  73. package/src/session/message-parts.ts +1 -89
  74. package/src/session/message-query.ts +1 -107
  75. package/src/session/message-transform.ts +1 -156
  76. package/src/session/message-types.ts +1 -68
  77. package/src/session/message-v2.ts +1 -73
  78. package/src/session/message.ts +1 -192
  79. package/src/session/network.ts +1 -392
  80. package/src/session/overflow.ts +1 -28
  81. package/src/session/processor.ts +1 -731
  82. package/src/session/projectors.ts +2 -139
  83. package/src/session/prompt/prompt-command.ts +93 -0
  84. package/src/session/prompt/prompt-loop.ts +299 -0
  85. package/src/session/prompt/prompt-model.ts +44 -0
  86. package/src/session/prompt/prompt-reminders.ts +120 -0
  87. package/src/session/prompt/prompt-resolve.ts +42 -0
  88. package/src/session/prompt/prompt-schemas.ts +128 -0
  89. package/src/session/prompt/prompt-title.ts +55 -0
  90. package/src/session/prompt/prompt-types.ts +47 -0
  91. package/src/session/prompt/prompt-user-msg.ts +80 -0
  92. package/src/session/prompt/prompt.ts +211 -0
  93. package/src/session/prompt-command.ts +1 -93
  94. package/src/session/prompt-loop.ts +1 -299
  95. package/src/session/prompt-model.ts +1 -44
  96. package/src/session/prompt-reminders.ts +1 -120
  97. package/src/session/prompt-resolve.ts +1 -42
  98. package/src/session/prompt-schemas.ts +1 -128
  99. package/src/session/prompt-title.ts +1 -55
  100. package/src/session/prompt-types.ts +1 -47
  101. package/src/session/prompt-user-msg.ts +1 -80
  102. package/src/session/prompt.ts +1 -211
  103. package/src/session/resolve-tools.ts +1 -241
  104. package/src/session/retry.ts +1 -149
  105. package/src/session/revert.ts +1 -173
  106. package/src/session/run-state.ts +1 -110
  107. package/src/session/schema.ts +1 -35
  108. package/src/session/session-types.ts +1 -160
  109. package/src/session/session.sql.ts +1 -124
  110. package/src/session/session.ts +1 -948
  111. package/src/session/shell-exec.ts +1 -205
  112. package/src/session/status.ts +1 -100
  113. package/src/session/subtask.ts +1 -268
  114. package/src/session/summary.ts +1 -173
  115. package/src/session/system.ts +1 -114
  116. package/src/session/todo.ts +1 -86
  117. package/src/session/user-part.ts +1 -293
  118. package/src/tool/apply_patch.ts +1 -334
  119. package/src/tool/bash.ts +1 -656
  120. package/src/tool/core/external-directory.ts +55 -0
  121. package/src/tool/core/invalid.ts +21 -0
  122. package/src/tool/core/recall.ts +164 -0
  123. package/src/tool/core/recall.txt +12 -0
  124. package/src/tool/core/schema.ts +16 -0
  125. package/src/tool/core/tool.ts +162 -0
  126. package/src/tool/core/truncate.ts +160 -0
  127. package/src/tool/core/truncation-dir.ts +4 -0
  128. package/src/tool/diagnostics.ts +1 -20
  129. package/src/tool/edit-replacers.ts +1 -288
  130. package/src/tool/edit-utils.ts +1 -86
  131. package/src/tool/edit.ts +1 -262
  132. package/src/tool/external-directory.ts +1 -55
  133. package/src/tool/file/apply_patch.ts +334 -0
  134. package/src/tool/file/apply_patch.txt +33 -0
  135. package/src/tool/file/bash.ts +656 -0
  136. package/src/tool/file/bash.txt +119 -0
  137. package/src/tool/file/edit-replacers.ts +288 -0
  138. package/src/tool/file/edit-utils.ts +86 -0
  139. package/src/tool/file/edit.ts +262 -0
  140. package/src/tool/file/edit.txt +10 -0
  141. package/src/tool/file/read.ts +389 -0
  142. package/src/tool/file/read.txt +14 -0
  143. package/src/tool/file/write.ts +114 -0
  144. package/src/tool/file/write.txt +8 -0
  145. package/src/tool/glob.ts +1 -115
  146. package/src/tool/grep.ts +1 -151
  147. package/src/tool/integration/diagnostics.ts +20 -0
  148. package/src/tool/integration/lsp.ts +113 -0
  149. package/src/tool/integration/lsp.txt +24 -0
  150. package/src/tool/integration/mcp-exa.ts +73 -0
  151. package/src/tool/integration/package.ts +168 -0
  152. package/src/tool/integration/registry.ts +375 -0
  153. package/src/tool/invalid.ts +1 -21
  154. package/src/tool/lsp.ts +1 -113
  155. package/src/tool/mcp-exa.ts +1 -73
  156. package/src/tool/package.ts +1 -168
  157. package/src/tool/plan.ts +1 -30
  158. package/src/tool/question.ts +1 -52
  159. package/src/tool/read.ts +1 -389
  160. package/src/tool/recall.ts +1 -164
  161. package/src/tool/registry.ts +1 -375
  162. package/src/tool/schema.ts +1 -16
  163. package/src/tool/search/glob.ts +115 -0
  164. package/src/tool/search/glob.txt +6 -0
  165. package/src/tool/search/grep.ts +151 -0
  166. package/src/tool/search/grep.txt +8 -0
  167. package/src/tool/search/warpgrep.ts +107 -0
  168. package/src/tool/search/warpgrep.txt +10 -0
  169. package/src/tool/search/webfetch.ts +202 -0
  170. package/src/tool/search/webfetch.txt +13 -0
  171. package/src/tool/search/websearch.ts +71 -0
  172. package/src/tool/search/websearch.txt +14 -0
  173. package/src/tool/skill.ts +1 -91
  174. package/src/tool/task.ts +1 -197
  175. package/src/tool/todo.ts +1 -62
  176. package/src/tool/tool.ts +1 -162
  177. package/src/tool/truncate.ts +1 -160
  178. package/src/tool/truncation-dir.ts +1 -4
  179. package/src/tool/warpgrep.ts +1 -107
  180. package/src/tool/webfetch.ts +1 -202
  181. package/src/tool/websearch.ts +1 -71
  182. package/src/tool/workflow/plan-enter.txt +14 -0
  183. package/src/tool/workflow/plan-exit.txt +13 -0
  184. package/src/tool/workflow/plan.ts +30 -0
  185. package/src/tool/workflow/question.ts +52 -0
  186. package/src/tool/workflow/question.txt +11 -0
  187. package/src/tool/workflow/skill.ts +91 -0
  188. package/src/tool/workflow/skill.txt +5 -0
  189. package/src/tool/workflow/task.ts +197 -0
  190. package/src/tool/workflow/task.txt +57 -0
  191. package/src/tool/workflow/todo.ts +62 -0
  192. package/src/tool/workflow/todowrite.txt +167 -0
  193. package/src/tool/write.ts +1 -114
@@ -1,301 +1 @@
1
- import { useDialog } from "@tui/ui/dialog"
2
- import { DialogSelect } from "@tui/ui/dialog-select"
3
- import { useRoute } from "@tui/context/route"
4
- import { useSync } from "@tui/context/sync"
5
- import { createMemo, createResource, createSignal, onMount } from "solid-js"
6
- import { Locale } from "@/util/locale"
7
- import { useProject } from "@tui/context/project"
8
- import { useKeybind } from "../context/keybind"
9
- import { useTheme } from "../context/theme"
10
- import { useSDK } from "../context/sdk"
11
- import { Flag } from "@saeeol/core/flag/flag"
12
- import { DialogSessionRename } from "./dialog-session-rename"
13
- import { Keybind } from "@/util/keybind"
14
- import { createDebouncedSignal } from "../util/signal"
15
- import { useToast } from "../ui/toast"
16
- import { DialogWorkspaceCreate, openWorkspaceSession, restoreWorkspaceSession } from "./dialog-workspace-create"
17
- import { Spinner } from "./spinner"
18
- import path from "path"
19
- import { errorMessage } from "@/util/error"
20
- import { DialogSessionDeleteFailed } from "./dialog-session-delete-failed"
21
-
22
- type WorkspaceStatus = "connected" | "connecting" | "disconnected" | "error"
23
-
24
- export function DialogSessionList() {
25
- const dialog = useDialog()
26
- const route = useRoute()
27
- const sync = useSync()
28
- const project = useProject()
29
- const keybind = useKeybind()
30
- const { theme } = useTheme()
31
- const sdk = useSDK()
32
- const toast = useToast()
33
- const [toDelete, setToDelete] = createSignal<string>()
34
- const [search, setSearch] = createDebouncedSignal("", 150)
35
- const [global, setGlobal] = createSignal(true)
36
- // TODO: extend /experimental/session to accept `scope`/`path` so this dialog can respect the
37
- // upstream `session_directory_filter_enabled` KV toggle (via sync.session.query()) while
38
- // keeping worktree grouping. Currently the toggle has no effect here.
39
- const [searchResults, searchActions] = createResource(
40
- () => search(),
41
- async (query) => {
42
- const result = await sdk.client.experimental.session.list(
43
- {
44
- search: query || undefined,
45
- roots: true,
46
- worktrees: true,
47
- limit: 30,
48
- },
49
- { throwOnError: true },
50
- )
51
- return result.data ?? []
52
- },
53
- )
54
-
55
- const currentSessionID = createMemo(() => (route.data.type === "session" ? route.data.sessionID : undefined))
56
- const sessions = createMemo(() => {
57
- const all = searchResults() ?? []
58
- if (global()) return all
59
- const root = project.instance.path().worktree
60
- if (!root || root === "/") return all
61
- return all.filter((s) => s.directory === root || s.directory.startsWith(root + path.sep))
62
- })
63
-
64
- function createWorkspace() {
65
- dialog.replace(() => (
66
- <DialogWorkspaceCreate
67
- onSelect={(workspaceID) =>
68
- openWorkspaceSession({
69
- dialog,
70
- route,
71
- sdk,
72
- sync,
73
- toast,
74
- workspaceID,
75
- })
76
- }
77
- />
78
- ))
79
- }
80
-
81
- function recover(session: NonNullable<ReturnType<typeof sessions>[number]>) {
82
- const workspace = project.workspace.get(session.workspaceID!)
83
- const list = () => dialog.replace(() => <DialogSessionList />)
84
- dialog.replace(() => (
85
- <DialogSessionDeleteFailed
86
- session={session.title}
87
- workspace={workspace?.name ?? session.workspaceID!}
88
- onDone={list}
89
- onDelete={async () => {
90
- const current = currentSessionID()
91
- const info = current ? sync.data.session.find((item) => item.id === current) : undefined
92
- const result = await sdk.client.experimental.workspace.remove({ id: session.workspaceID! })
93
- if (result.error) {
94
- toast.show({
95
- variant: "error",
96
- title: "Failed to delete workspace",
97
- message: errorMessage(result.error),
98
- })
99
- return false
100
- }
101
- await project.workspace.sync()
102
- await sync.session.refresh()
103
- if (search()) await searchActions.refetch()
104
- if (info?.workspaceID === session.workspaceID) {
105
- route.navigate({ type: "home" })
106
- }
107
- return true
108
- }}
109
- onRestore={() => {
110
- dialog.replace(() => (
111
- <DialogWorkspaceCreate
112
- onSelect={(workspaceID) =>
113
- restoreWorkspaceSession({
114
- dialog,
115
- sdk,
116
- sync,
117
- project,
118
- toast,
119
- workspaceID,
120
- sessionID: session.id,
121
- done: list,
122
- })
123
- }
124
- />
125
- ))
126
- return false
127
- }}
128
- />
129
- ))
130
- }
131
-
132
- const options = createMemo(() => {
133
- const today = new Date().toDateString()
134
- const all = global()
135
- return sessions()
136
- .filter((x) => x.parentID === undefined)
137
- .toSorted((a, b) => {
138
- const updatedDay = new Date(b.time.updated).setHours(0, 0, 0, 0) - new Date(a.time.updated).setHours(0, 0, 0, 0)
139
- if (updatedDay !== 0) return updatedDay
140
- return b.time.created - a.time.created
141
- })
142
- .map((x) => {
143
- const workspace = x.workspaceID ? project.workspace.get(x.workspaceID) : undefined
144
-
145
- let workspaceStatus: WorkspaceStatus | null = null
146
- if (x.workspaceID) {
147
- workspaceStatus = project.workspace.status(x.workspaceID) || "error"
148
- }
149
-
150
- let footer = ""
151
- if (Flag.SAEEOL_EXPERIMENTAL_WORKSPACES) {
152
- if (x.workspaceID) {
153
- let desc = "unknown"
154
- if (workspace) {
155
- desc = `${workspace.type}: ${workspace.name}`
156
- }
157
-
158
- footer = (
159
- <>
160
- {desc}{" "}
161
- <span
162
- style={{
163
- fg: workspaceStatus === "connected" ? theme.success : theme.error,
164
- }}
165
- >
166
-
167
- </span>
168
- </>
169
- )
170
- }
171
- } else {
172
- footer = Locale.time(x.time.updated)
173
- }
174
-
175
- const date = new Date(x.time.updated)
176
- let category = date.toDateString()
177
- if (category === today) {
178
- category = "Today"
179
- }
180
- const isDeleting = toDelete() === x.id
181
- const status = sync.data.session_status?.[x.id]
182
- const isWorking = status?.type === "busy"
183
- return {
184
- title: isDeleting ? `Press ${keybind.print("session_delete")} again to confirm` : x.title,
185
- description: all && x.worktreeName ? `(${x.worktreeName})` : undefined,
186
- bg: isDeleting ? theme.error : undefined,
187
- value: x.id,
188
- category,
189
- footer,
190
- gutter: isWorking ? () => <Spinner /> : undefined,
191
- }
192
- })
193
- })
194
-
195
- onMount(() => {
196
- dialog.setSize("large")
197
- })
198
-
199
- return (
200
- <DialogSelect
201
- title={global() ? "Sessions (all worktrees)" : "Sessions (current worktree)"}
202
- options={options()}
203
- skipFilter={true}
204
- current={currentSessionID()}
205
- onFilter={setSearch}
206
- onMove={() => {
207
- setToDelete(undefined)
208
- }}
209
- onSelect={(option) => {
210
- route.navigate({
211
- type: "session",
212
- sessionID: option.value,
213
- })
214
- dialog.clear()
215
- }}
216
- keybind={[
217
- {
218
- keybind: keybind.all.session_delete?.[0],
219
- title: "delete",
220
- onTrigger: async (option) => {
221
- if (toDelete() === option.value) {
222
- const session = sessions().find((item) => item.id === option.value)
223
- const status = session?.workspaceID ? project.workspace.status(session.workspaceID) : undefined
224
-
225
- try {
226
- const result = await sdk.client.session.delete({
227
- sessionID: option.value,
228
- })
229
- if (result.error) {
230
- if (session?.workspaceID) {
231
- recover(session)
232
- } else {
233
- toast.show({
234
- variant: "error",
235
- title: "Failed to delete session",
236
- message: errorMessage(result.error),
237
- })
238
- }
239
- setToDelete(undefined)
240
- return
241
- }
242
- } catch (err) {
243
- if (session?.workspaceID) {
244
- recover(session)
245
- } else {
246
- toast.show({
247
- variant: "error",
248
- title: "Failed to delete session",
249
- message: errorMessage(err),
250
- })
251
- }
252
- setToDelete(undefined)
253
- return
254
- }
255
- if (status && status !== "connected") {
256
- await sync.session.refresh()
257
- }
258
- void searchActions.refetch()
259
- setToDelete(undefined)
260
- return
261
- }
262
- setToDelete(option.value)
263
- },
264
- },
265
- {
266
- keybind: keybind.all.session_rename?.[0],
267
- title: "rename",
268
- onTrigger: async (option) => {
269
- const item = sessions().find((x) => x.id === option.value)
270
- dialog.replace(() => (
271
- <DialogSessionRename
272
- session={option.value}
273
- title={item?.title}
274
- onConfirm={() => {
275
- void searchActions.refetch()
276
- }}
277
- />
278
- ))
279
- },
280
- },
281
- {
282
- keybind: { name: "a", ctrl: true, meta: false, shift: false, leader: false },
283
- title: global() ? "current" : "all",
284
- onTrigger: async () => {
285
- setToDelete(undefined)
286
- setGlobal((v) => !v)
287
- },
288
- },
289
- {
290
- keybind: Keybind.parse("ctrl+w")[0],
291
- title: "new workspace",
292
- side: "right",
293
- disabled: !Flag.SAEEOL_EXPERIMENTAL_WORKSPACES,
294
- onTrigger: () => {
295
- createWorkspace()
296
- },
297
- },
298
- ]}
299
- />
300
- )
301
- }
1
+ export * from "./dialog/dialog-session-list"
@@ -1,35 +1 @@
1
- import { DialogPrompt } from "@tui/ui/dialog-prompt"
2
- import { useDialog } from "@tui/ui/dialog"
3
- import { useSync } from "@tui/context/sync"
4
- import { createMemo } from "solid-js"
5
- import { useSDK } from "../context/sdk"
6
-
7
- interface DialogSessionRenameProps {
8
- session: string
9
- title?: string
10
- onConfirm?: () => void
11
- }
12
-
13
- export function DialogSessionRename(props: DialogSessionRenameProps) {
14
- const dialog = useDialog()
15
- const sync = useSync()
16
- const sdk = useSDK()
17
- const session = createMemo(() => sync.session.get(props.session))
18
-
19
- return (
20
- <DialogPrompt
21
- title="Rename Session"
22
- value={session()?.title ?? props.title}
23
- onConfirm={(value) => {
24
- void sdk.client.session
25
- .update({
26
- sessionID: props.session,
27
- title: value,
28
- })
29
- .then(() => props.onConfirm?.())
30
- dialog.clear()
31
- }}
32
- onCancel={() => dialog.clear()}
33
- />
34
- )
35
- }
1
+ export * from "./dialog/dialog-session-rename"
@@ -1,37 +1 @@
1
- import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
2
- import { createResource, createMemo } from "solid-js"
3
- import { useDialog } from "@tui/ui/dialog"
4
- import { useSDK } from "@tui/context/sdk"
5
- import { t } from "@/util/i18n"
6
-
7
- export type DialogSkillProps = {
8
- onSelect: (skill: string) => void
9
- }
10
-
11
- export function DialogSkill(props: DialogSkillProps) {
12
- const dialog = useDialog()
13
- const sdk = useSDK()
14
- dialog.setSize("large")
15
-
16
- const [skills] = createResource(async () => {
17
- const result = await sdk.client.app.skills()
18
- return result.data ?? []
19
- })
20
-
21
- const options = createMemo<DialogSelectOption<string>[]>(() => {
22
- const list = skills() ?? []
23
- const maxWidth = Math.max(0, ...list.map((s) => s.name.length))
24
- return list.map((skill) => ({
25
- title: skill.name.padEnd(maxWidth),
26
- description: skill.description?.replace(/\s+/g, " ").trim(),
27
- value: skill.name,
28
- category: t("cmd.skill.title"),
29
- onSelect: () => {
30
- props.onSelect(skill.name)
31
- dialog.clear()
32
- },
33
- }))
34
- })
35
-
36
- return <DialogSelect title={t("cmd.skill.title")} placeholder={t("cmd.skill.search")} options={options()} />
37
- }
1
+ export * from "./dialog/dialog-skill"
@@ -1,87 +1 @@
1
- import { useDialog } from "@tui/ui/dialog"
2
- import { DialogSelect } from "@tui/ui/dialog-select"
3
- import { createMemo, createSignal } from "solid-js"
4
- import { Locale } from "@/util/locale"
5
- import { useTheme } from "../context/theme"
6
- import { useKeybind } from "../context/keybind"
7
- import { usePromptStash, type StashEntry } from "./prompt/stash"
8
-
9
- function getRelativeTime(timestamp: number): string {
10
- const now = Date.now()
11
- const diff = now - timestamp
12
- const seconds = Math.floor(diff / 1000)
13
- const minutes = Math.floor(seconds / 60)
14
- const hours = Math.floor(minutes / 60)
15
- const days = Math.floor(hours / 24)
16
-
17
- if (seconds < 60) return "just now"
18
- if (minutes < 60) return `${minutes}m ago`
19
- if (hours < 24) return `${hours}h ago`
20
- if (days < 7) return `${days}d ago`
21
- return Locale.datetime(timestamp)
22
- }
23
-
24
- function getStashPreview(input: string, maxLength: number = 50): string {
25
- const firstLine = input.split("\n")[0].trim()
26
- return Locale.truncate(firstLine, maxLength)
27
- }
28
-
29
- export function DialogStash(props: { onSelect: (entry: StashEntry) => void }) {
30
- const dialog = useDialog()
31
- const stash = usePromptStash()
32
- const { theme } = useTheme()
33
- const keybind = useKeybind()
34
-
35
- const [toDelete, setToDelete] = createSignal<number>()
36
-
37
- const options = createMemo(() => {
38
- const entries = stash.list()
39
- // Show most recent first
40
- return entries
41
- .map((entry, index) => {
42
- const isDeleting = toDelete() === index
43
- const lineCount = (entry.input.match(/\n/g)?.length ?? 0) + 1
44
- return {
45
- title: isDeleting ? `Press ${keybind.print("stash_delete")} again to confirm` : getStashPreview(entry.input),
46
- bg: isDeleting ? theme.error : undefined,
47
- value: index,
48
- description: getRelativeTime(entry.timestamp),
49
- footer: lineCount > 1 ? `~${lineCount} lines` : undefined,
50
- }
51
- })
52
- .toReversed()
53
- })
54
-
55
- return (
56
- <DialogSelect
57
- title="Stash"
58
- options={options()}
59
- onMove={() => {
60
- setToDelete(undefined)
61
- }}
62
- onSelect={(option) => {
63
- const entries = stash.list()
64
- const entry = entries[option.value]
65
- if (entry) {
66
- stash.remove(option.value)
67
- props.onSelect(entry)
68
- }
69
- dialog.clear()
70
- }}
71
- keybind={[
72
- {
73
- keybind: keybind.all.stash_delete?.[0],
74
- title: "delete",
75
- onTrigger: (option) => {
76
- if (toDelete() === option.value) {
77
- stash.remove(option.value)
78
- setToDelete(undefined)
79
- return
80
- }
81
- setToDelete(option.value)
82
- },
83
- },
84
- ]}
85
- />
86
- )
87
- }
1
+ export * from "./dialog/dialog-stash"
@@ -1,190 +1 @@
1
- import { TextAttributes } from "@opentui/core"
2
- import { fileURLToPath } from "bun"
3
- import { useTheme } from "../context/theme"
4
- import { useDialog } from "@tui/ui/dialog"
5
- import { useSync } from "@tui/context/sync"
6
- import { useProject } from "@tui/context/project"
7
- import { For, Match, Switch, Show, createMemo } from "solid-js"
8
- import { InstallationVersion } from "@saeeol/core/installation/version"
9
- import { Global } from "@saeeol/core/global"
10
-
11
- export type DialogStatusProps = {}
12
-
13
- export function DialogStatus() {
14
- const sync = useSync()
15
- const project = useProject()
16
- const { theme } = useTheme()
17
- const dialog = useDialog()
18
-
19
- const enabledFormatters = createMemo(() => sync.data.formatter.filter((f) => f.enabled))
20
-
21
- const plugins = createMemo(() => {
22
- const list = sync.data.config.plugin ?? []
23
- const result = list.map((item) => {
24
- const value = typeof item === "string" ? item : item[0]
25
- if (value.startsWith("file://")) {
26
- const path = fileURLToPath(value)
27
- const parts = path.split(/[/\\]/)
28
- const filename = parts.pop() || path
29
- if (!filename.includes(".")) return { name: filename }
30
- const basename = filename.split(".")[0]
31
- if (basename === "index") {
32
- const dirname = parts.pop()
33
- const name = dirname || basename
34
- return { name }
35
- }
36
- return { name: basename }
37
- }
38
- const index = value.lastIndexOf("@")
39
- if (index <= 0) return { name: value, version: "latest" }
40
- const name = value.substring(0, index)
41
- const version = value.substring(index + 1)
42
- return { name, version }
43
- })
44
- return result.toSorted((a, b) => a.name.localeCompare(b.name))
45
- })
46
-
47
- return (
48
- <box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
49
- <box flexDirection="row" justifyContent="space-between">
50
- <text fg={theme.text} attributes={TextAttributes.BOLD}>
51
- Status
52
- </text>
53
- <text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
54
- esc
55
- </text>
56
- </box>
57
-
58
- <text fg={theme.textMuted}>Saeeol v{InstallationVersion}</text>
59
-
60
-
61
- <box>
62
- <text fg={theme.text}>Paths</text>
63
- <text fg={theme.textMuted}>
64
- Global config {" "}
65
- {Global.Path.config.replace(Global.Path.home, "~")}
66
- </text>
67
- <Show when={project.instance.path().directory}>
68
- <text fg={theme.textMuted}>
69
- Project {" "}
70
- {project.instance.path().directory.replace(Global.Path.home, "~")}
71
- </text>
72
- </Show>
73
- </box>
74
-
75
- <Show when={Object.keys(sync.data.mcp).length > 0} fallback={<text fg={theme.text}>No MCP Servers</text>}>
76
- <box>
77
- <text fg={theme.text}>{Object.keys(sync.data.mcp).length} MCP Servers</text>
78
- <For each={Object.entries(sync.data.mcp)}>
79
- {([key, item]) => (
80
- <box flexDirection="row" gap={1}>
81
- <text
82
- flexShrink={0}
83
- style={{
84
- fg: (
85
- {
86
- connected: theme.success,
87
- failed: theme.error,
88
- disabled: theme.textMuted,
89
- needs_auth: theme.warning,
90
- needs_client_registration: theme.error,
91
- } as Record<string, typeof theme.success>
92
- )[item.status],
93
- }}
94
- >
95
-
96
- </text>
97
- <text fg={theme.text} wrapMode="word">
98
- <b>{key}</b>{" "}
99
- <span style={{ fg: theme.textMuted }}>
100
- <Switch fallback={item.status}>
101
- <Match when={item.status === "connected"}>Connected</Match>
102
- <Match when={item.status === "failed" && item}>{(val) => val().error}</Match>
103
- <Match when={item.status === "disabled"}>Disabled in configuration</Match>
104
- <Match when={(item.status as string) === "needs_auth"}>
105
- Needs authentication (run: saeeol mcp auth {key})
106
- </Match>
107
- <Match when={(item.status as string) === "needs_client_registration" && item}>
108
- {(val) => (val() as { error: string }).error}
109
- </Match>
110
- </Switch>
111
- </span>
112
- </text>
113
- </box>
114
- )}
115
- </For>
116
- </box>
117
- </Show>
118
- {sync.data.lsp.length > 0 && (
119
- <box>
120
- <text fg={theme.text}>{sync.data.lsp.length} LSP Servers</text>
121
- <For each={sync.data.lsp}>
122
- {(item) => (
123
- <box flexDirection="row" gap={1}>
124
- <text
125
- flexShrink={0}
126
- style={{
127
- fg: {
128
- connected: theme.success,
129
- error: theme.error,
130
- }[item.status],
131
- }}
132
- >
133
-
134
- </text>
135
- <text fg={theme.text} wrapMode="word">
136
- <b>{item.id}</b> <span style={{ fg: theme.textMuted }}>{item.root}</span>
137
- </text>
138
- </box>
139
- )}
140
- </For>
141
- </box>
142
- )}
143
- <Show when={enabledFormatters().length > 0} fallback={<text fg={theme.text}>No Formatters</text>}>
144
- <box>
145
- <text fg={theme.text}>{enabledFormatters().length} Formatters</text>
146
- <For each={enabledFormatters()}>
147
- {(item) => (
148
- <box flexDirection="row" gap={1}>
149
- <text
150
- flexShrink={0}
151
- style={{
152
- fg: theme.success,
153
- }}
154
- >
155
-
156
- </text>
157
- <text wrapMode="word" fg={theme.text}>
158
- <b>{item.name}</b>
159
- </text>
160
- </box>
161
- )}
162
- </For>
163
- </box>
164
- </Show>
165
- <Show when={plugins().length > 0} fallback={<text fg={theme.text}>No Plugins</text>}>
166
- <box>
167
- <text fg={theme.text}>{plugins().length} Plugins</text>
168
- <For each={plugins()}>
169
- {(item) => (
170
- <box flexDirection="row" gap={1}>
171
- <text
172
- flexShrink={0}
173
- style={{
174
- fg: theme.success,
175
- }}
176
- >
177
-
178
- </text>
179
- <text wrapMode="word" fg={theme.text}>
180
- <b>{item.name}</b>
181
- {item.version && <span style={{ fg: theme.textMuted }}> @{item.version}</span>}
182
- </text>
183
- </box>
184
- )}
185
- </For>
186
- </box>
187
- </Show>
188
- </box>
189
- )
190
- }
1
+ export * from "./dialog/dialog-status"