saeeol 1.2.1 → 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.
- package/package.json +11 -11
- package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
- package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
- package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
- package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
- package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
- package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
- package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
- package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
- package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
- package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
- package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
- package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
- package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
- package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
- package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
- package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
- package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
- package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
- package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
- package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
- package/src/tool/apply_patch.ts +1 -334
- package/src/tool/bash.ts +1 -656
- package/src/tool/core/external-directory.ts +55 -0
- package/src/tool/core/invalid.ts +21 -0
- package/src/tool/core/recall.ts +164 -0
- package/src/tool/core/recall.txt +12 -0
- package/src/tool/core/schema.ts +16 -0
- package/src/tool/core/tool.ts +162 -0
- package/src/tool/core/truncate.ts +160 -0
- package/src/tool/core/truncation-dir.ts +4 -0
- package/src/tool/diagnostics.ts +1 -20
- package/src/tool/edit-replacers.ts +1 -288
- package/src/tool/edit-utils.ts +1 -86
- package/src/tool/edit.ts +1 -262
- package/src/tool/external-directory.ts +1 -55
- package/src/tool/file/apply_patch.ts +334 -0
- package/src/tool/file/apply_patch.txt +33 -0
- package/src/tool/file/bash.ts +656 -0
- package/src/tool/file/bash.txt +119 -0
- package/src/tool/file/edit-replacers.ts +288 -0
- package/src/tool/file/edit-utils.ts +86 -0
- package/src/tool/file/edit.ts +262 -0
- package/src/tool/file/edit.txt +10 -0
- package/src/tool/file/read.ts +389 -0
- package/src/tool/file/read.txt +14 -0
- package/src/tool/file/write.ts +114 -0
- package/src/tool/file/write.txt +8 -0
- package/src/tool/glob.ts +1 -115
- package/src/tool/grep.ts +1 -151
- package/src/tool/integration/diagnostics.ts +20 -0
- package/src/tool/integration/lsp.ts +113 -0
- package/src/tool/integration/lsp.txt +24 -0
- package/src/tool/integration/mcp-exa.ts +73 -0
- package/src/tool/integration/package.ts +168 -0
- package/src/tool/integration/registry.ts +375 -0
- package/src/tool/invalid.ts +1 -21
- package/src/tool/lsp.ts +1 -113
- package/src/tool/mcp-exa.ts +1 -73
- package/src/tool/package.ts +1 -168
- package/src/tool/plan.ts +1 -30
- package/src/tool/question.ts +1 -52
- package/src/tool/read.ts +1 -389
- package/src/tool/recall.ts +1 -164
- package/src/tool/registry.ts +1 -375
- package/src/tool/schema.ts +1 -16
- package/src/tool/search/glob.ts +115 -0
- package/src/tool/search/glob.txt +6 -0
- package/src/tool/search/grep.ts +151 -0
- package/src/tool/search/grep.txt +8 -0
- package/src/tool/search/warpgrep.ts +107 -0
- package/src/tool/search/warpgrep.txt +10 -0
- package/src/tool/search/webfetch.ts +202 -0
- package/src/tool/search/webfetch.txt +13 -0
- package/src/tool/search/websearch.ts +71 -0
- package/src/tool/search/websearch.txt +14 -0
- package/src/tool/skill.ts +1 -91
- package/src/tool/task.ts +1 -197
- package/src/tool/todo.ts +1 -62
- package/src/tool/tool.ts +1 -162
- package/src/tool/truncate.ts +1 -160
- package/src/tool/truncation-dir.ts +1 -4
- package/src/tool/warpgrep.ts +1 -107
- package/src/tool/webfetch.ts +1 -202
- package/src/tool/websearch.ts +1 -71
- package/src/tool/workflow/plan-enter.txt +14 -0
- package/src/tool/workflow/plan-exit.txt +13 -0
- package/src/tool/workflow/plan.ts +30 -0
- package/src/tool/workflow/question.ts +52 -0
- package/src/tool/workflow/question.txt +11 -0
- package/src/tool/workflow/skill.ts +91 -0
- package/src/tool/workflow/skill.txt +5 -0
- package/src/tool/workflow/task.ts +197 -0
- package/src/tool/workflow/task.txt +57 -0
- package/src/tool/workflow/todo.ts +62 -0
- package/src/tool/workflow/todowrite.txt +167 -0
- package/src/tool/write.ts +1 -114
|
@@ -1,301 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|