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.
- package/package.json +14 -14
- 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/session/compaction-helpers.ts +1 -169
- package/src/session/compaction.ts +1 -712
- package/src/session/core/compaction/compaction-helpers.ts +169 -0
- package/src/session/core/compaction/compaction.ts +712 -0
- package/src/session/core/compaction/overflow.ts +28 -0
- package/src/session/core/instruction.ts +234 -0
- package/src/session/core/llm.ts +504 -0
- package/src/session/core/network.ts +392 -0
- package/src/session/core/processor.ts +731 -0
- package/src/session/core/projectors.ts +139 -0
- package/src/session/core/resolve-tools.ts +241 -0
- package/src/session/core/retry.ts +149 -0
- package/src/session/core/revert.ts +173 -0
- package/src/session/core/run-state.ts +110 -0
- package/src/session/core/schema.ts +35 -0
- package/src/session/core/session-types.ts +160 -0
- package/src/session/core/session.sql.ts +124 -0
- package/src/session/core/session.ts +948 -0
- package/src/session/core/shell-exec.ts +205 -0
- package/src/session/core/status.ts +100 -0
- package/src/session/core/subtask.ts +268 -0
- package/src/session/core/summary.ts +173 -0
- package/src/session/core/system.ts +114 -0
- package/src/session/core/todo.ts +86 -0
- package/src/session/core/user-part.ts +293 -0
- package/src/session/instruction.ts +1 -234
- package/src/session/llm.ts +1 -504
- package/src/session/message/message-errors.ts +83 -0
- package/src/session/message/message-parts.ts +89 -0
- package/src/session/message/message-query.ts +107 -0
- package/src/session/message/message-transform.ts +156 -0
- package/src/session/message/message-types.ts +68 -0
- package/src/session/message/message-v2.ts +73 -0
- package/src/session/message/message.ts +192 -0
- package/src/session/message-errors.ts +1 -83
- package/src/session/message-parts.ts +1 -89
- package/src/session/message-query.ts +1 -107
- package/src/session/message-transform.ts +1 -156
- package/src/session/message-types.ts +1 -68
- package/src/session/message-v2.ts +1 -73
- package/src/session/message.ts +1 -192
- package/src/session/network.ts +1 -392
- package/src/session/overflow.ts +1 -28
- package/src/session/processor.ts +1 -731
- package/src/session/projectors.ts +2 -139
- package/src/session/prompt/prompt-command.ts +93 -0
- package/src/session/prompt/prompt-loop.ts +299 -0
- package/src/session/prompt/prompt-model.ts +44 -0
- package/src/session/prompt/prompt-reminders.ts +120 -0
- package/src/session/prompt/prompt-resolve.ts +42 -0
- package/src/session/prompt/prompt-schemas.ts +128 -0
- package/src/session/prompt/prompt-title.ts +55 -0
- package/src/session/prompt/prompt-types.ts +47 -0
- package/src/session/prompt/prompt-user-msg.ts +80 -0
- package/src/session/prompt/prompt.ts +211 -0
- package/src/session/prompt-command.ts +1 -93
- package/src/session/prompt-loop.ts +1 -299
- package/src/session/prompt-model.ts +1 -44
- package/src/session/prompt-reminders.ts +1 -120
- package/src/session/prompt-resolve.ts +1 -42
- package/src/session/prompt-schemas.ts +1 -128
- package/src/session/prompt-title.ts +1 -55
- package/src/session/prompt-types.ts +1 -47
- package/src/session/prompt-user-msg.ts +1 -80
- package/src/session/prompt.ts +1 -211
- package/src/session/resolve-tools.ts +1 -241
- package/src/session/retry.ts +1 -149
- package/src/session/revert.ts +1 -173
- package/src/session/run-state.ts +1 -110
- package/src/session/schema.ts +1 -35
- package/src/session/session-types.ts +1 -160
- package/src/session/session.sql.ts +1 -124
- package/src/session/session.ts +1 -948
- package/src/session/shell-exec.ts +1 -205
- package/src/session/status.ts +1 -100
- package/src/session/subtask.ts +1 -268
- package/src/session/summary.ts +1 -173
- package/src/session/system.ts +1 -114
- package/src/session/todo.ts +1 -86
- package/src/session/user-part.ts +1 -293
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"name": "saeeol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"@babel/core": "7.28.4",
|
|
51
51
|
"@effect/language-service": "0.84.2",
|
|
52
52
|
"@octokit/webhooks-types": "7.6.1",
|
|
53
|
-
"@saeeol/script": "
|
|
54
|
-
"@saeeol/core": "
|
|
53
|
+
"@saeeol/script": "7.3.2",
|
|
54
|
+
"@saeeol/core": "7.3.2",
|
|
55
55
|
"@parcel/watcher-darwin-arm64": "2.5.1",
|
|
56
56
|
"@parcel/watcher-darwin-x64": "2.5.1",
|
|
57
57
|
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
|
@@ -132,15 +132,15 @@
|
|
|
132
132
|
"@opentui/solid": "0.2.2",
|
|
133
133
|
"@parcel/watcher": "2.5.1",
|
|
134
134
|
"@pierre/diffs": "1.1.0-beta.18",
|
|
135
|
-
"@saeeol/boxes": "
|
|
136
|
-
"@saeeol/core": "
|
|
137
|
-
"@saeeol/gateway": "
|
|
138
|
-
"@saeeol/i18n": "
|
|
139
|
-
"@saeeol/indexing": "
|
|
140
|
-
"@saeeol/plugin": "
|
|
141
|
-
"@saeeol/script": "
|
|
142
|
-
"@saeeol/sdk": "
|
|
143
|
-
"@saeeol/telemetry": "
|
|
135
|
+
"@saeeol/boxes": "0.2.0",
|
|
136
|
+
"@saeeol/core": "7.3.2",
|
|
137
|
+
"@saeeol/gateway": "7.3.2",
|
|
138
|
+
"@saeeol/i18n": "7.3.2",
|
|
139
|
+
"@saeeol/indexing": "7.3.2",
|
|
140
|
+
"@saeeol/plugin": "7.3.3",
|
|
141
|
+
"@saeeol/script": "7.3.2",
|
|
142
|
+
"@saeeol/sdk": "7.3.3",
|
|
143
|
+
"@saeeol/telemetry": "7.3.2",
|
|
144
144
|
"@solid-primitives/event-bus": "1.1.2",
|
|
145
145
|
"@solid-primitives/scheduled": "1.5.2",
|
|
146
146
|
"@standard-schema/spec": "1.0.0",
|
|
@@ -179,8 +179,8 @@
|
|
|
179
179
|
"remeda": "2.26.0",
|
|
180
180
|
"ripgrep": "0.3.1",
|
|
181
181
|
"rotating-file-stream": "3.2.9",
|
|
182
|
-
"saeeol-gitlab-auth": "
|
|
183
|
-
"saeeol-poe-auth": "
|
|
182
|
+
"saeeol-gitlab-auth": "2.0.2",
|
|
183
|
+
"saeeol-poe-auth": "0.0.4",
|
|
184
184
|
"semver": "^7.6.3",
|
|
185
185
|
"simple-git": "3.36.0",
|
|
186
186
|
"solid-js": "1.9.12",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createMemo } from "solid-js"
|
|
2
|
+
import { useLocal } from "@tui/context/local"
|
|
3
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
4
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
+
|
|
6
|
+
export function DialogAgent() {
|
|
7
|
+
const local = useLocal()
|
|
8
|
+
const dialog = useDialog()
|
|
9
|
+
|
|
10
|
+
const options = createMemo(() =>
|
|
11
|
+
local.agent.list().map((item) => {
|
|
12
|
+
return {
|
|
13
|
+
value: item.name,
|
|
14
|
+
title: item.displayName ?? item.name,
|
|
15
|
+
description:
|
|
16
|
+
[item.deprecated && "deprecated", item.native && "native"].filter(Boolean).join(", ") || item.description,
|
|
17
|
+
}
|
|
18
|
+
}),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<DialogSelect
|
|
23
|
+
title="Select agent"
|
|
24
|
+
current={local.agent.current()?.name ?? ""}
|
|
25
|
+
options={options()}
|
|
26
|
+
onSelect={(option) => {
|
|
27
|
+
local.agent.set(option.value)
|
|
28
|
+
dialog.clear()
|
|
29
|
+
}}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
2
|
+
import { DialogSelect, type DialogSelectOption, type DialogSelectRef } from "@tui/ui/dialog-select"
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
createMemo,
|
|
6
|
+
createSignal,
|
|
7
|
+
getOwner,
|
|
8
|
+
onCleanup,
|
|
9
|
+
runWithOwner,
|
|
10
|
+
useContext,
|
|
11
|
+
type Accessor,
|
|
12
|
+
type ParentProps,
|
|
13
|
+
} from "solid-js"
|
|
14
|
+
import { useKeyboard } from "@opentui/solid"
|
|
15
|
+
import { useKeybind } from "@tui/context/keybind"
|
|
16
|
+
import { t } from "@/util/i18n"
|
|
17
|
+
|
|
18
|
+
type Context = ReturnType<typeof init>
|
|
19
|
+
const ctx = createContext<Context>()
|
|
20
|
+
|
|
21
|
+
export type Slash = {
|
|
22
|
+
name: string
|
|
23
|
+
aliases?: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type CommandOption = DialogSelectOption<string> & {
|
|
27
|
+
keybind?: string
|
|
28
|
+
suggested?: boolean
|
|
29
|
+
slash?: Slash
|
|
30
|
+
hidden?: boolean
|
|
31
|
+
enabled?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function init() {
|
|
35
|
+
const root = getOwner()
|
|
36
|
+
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
|
37
|
+
const [suspendCount, setSuspendCount] = createSignal(0)
|
|
38
|
+
const dialog = useDialog()
|
|
39
|
+
const keybind = useKeybind()
|
|
40
|
+
|
|
41
|
+
// Double-tap tracking for agent_cycle keybinds
|
|
42
|
+
const DOUBLE_TAB_KEYS = new Set(["agent_cycle", "agent_cycle_reverse"])
|
|
43
|
+
const DOUBLE_TAB_WINDOW = 400
|
|
44
|
+
let lastTabKey = ""
|
|
45
|
+
let lastTabTime = 0
|
|
46
|
+
|
|
47
|
+
const entries = createMemo(() => {
|
|
48
|
+
const all = registrations().flatMap((x) => x())
|
|
49
|
+
return all.map((x) => ({
|
|
50
|
+
...x,
|
|
51
|
+
footer: x.keybind ? keybind.print(x.keybind) : undefined,
|
|
52
|
+
}))
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const isEnabled = (option: CommandOption) => option.enabled !== false
|
|
56
|
+
const isVisible = (option: CommandOption) => isEnabled(option) && !option.hidden
|
|
57
|
+
|
|
58
|
+
const visibleOptions = createMemo(() => entries().filter((option) => isVisible(option)))
|
|
59
|
+
const suggestedOptions = createMemo(() =>
|
|
60
|
+
visibleOptions()
|
|
61
|
+
.filter((option) => option.suggested)
|
|
62
|
+
.map((option) => ({
|
|
63
|
+
...option,
|
|
64
|
+
value: `suggested:${option.value}`,
|
|
65
|
+
category: t("cmd.suggested"),
|
|
66
|
+
})),
|
|
67
|
+
)
|
|
68
|
+
const suspended = () => suspendCount() > 0
|
|
69
|
+
|
|
70
|
+
useKeyboard((evt) => {
|
|
71
|
+
if (suspended()) return
|
|
72
|
+
if (dialog.stack.length > 0) return
|
|
73
|
+
if (evt.defaultPrevented) return
|
|
74
|
+
for (const option of entries()) {
|
|
75
|
+
if (!isEnabled(option)) continue
|
|
76
|
+
if (option.keybind && keybind.match(option.keybind, evt)) {
|
|
77
|
+
// Require double-tap for agent cycle keybinds
|
|
78
|
+
if (DOUBLE_TAB_KEYS.has(option.keybind)) {
|
|
79
|
+
const now = Date.now()
|
|
80
|
+
const match = option.keybind === lastTabKey && now - lastTabTime < DOUBLE_TAB_WINDOW
|
|
81
|
+
lastTabKey = option.keybind
|
|
82
|
+
lastTabTime = now
|
|
83
|
+
if (!match) {
|
|
84
|
+
evt.preventDefault()
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
evt.preventDefault()
|
|
89
|
+
option.onSelect?.(dialog)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const result = {
|
|
96
|
+
trigger(name: string) {
|
|
97
|
+
for (const option of entries()) {
|
|
98
|
+
if (option.value === name) {
|
|
99
|
+
if (!isEnabled(option)) return
|
|
100
|
+
option.onSelect?.(dialog)
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
slashes() {
|
|
106
|
+
return visibleOptions().flatMap((option) => {
|
|
107
|
+
const slash = option.slash
|
|
108
|
+
if (!slash) return []
|
|
109
|
+
return {
|
|
110
|
+
display: "/" + slash.name,
|
|
111
|
+
description: option.description ?? option.title,
|
|
112
|
+
aliases: slash.aliases?.map((alias) => "/" + alias),
|
|
113
|
+
onSelect: () => result.trigger(option.value),
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
},
|
|
117
|
+
keybinds(enabled: boolean) {
|
|
118
|
+
setSuspendCount((count) => count + (enabled ? -1 : 1))
|
|
119
|
+
},
|
|
120
|
+
suspended,
|
|
121
|
+
show() {
|
|
122
|
+
dialog.replace(() => <DialogCommand options={visibleOptions()} suggestedOptions={suggestedOptions()} />)
|
|
123
|
+
},
|
|
124
|
+
register(cb: () => CommandOption[]) {
|
|
125
|
+
const owner = getOwner() ?? root
|
|
126
|
+
if (!owner) return () => {}
|
|
127
|
+
|
|
128
|
+
let list: Accessor<CommandOption[]> | undefined
|
|
129
|
+
|
|
130
|
+
// TUI plugins now register commands via an async store that runs outside an active reactive scope.
|
|
131
|
+
// runWithOwner attaches createMemo/onCleanup to this owner so plugin registrations stay reactive and dispose correctly.
|
|
132
|
+
runWithOwner(owner, () => {
|
|
133
|
+
list = createMemo(cb)
|
|
134
|
+
const ref = list
|
|
135
|
+
if (!ref) return
|
|
136
|
+
setRegistrations((arr) => [ref, ...arr])
|
|
137
|
+
onCleanup(() => {
|
|
138
|
+
setRegistrations((arr) => arr.filter((x) => x !== ref))
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (!list) return () => {}
|
|
143
|
+
let done = false
|
|
144
|
+
return () => {
|
|
145
|
+
if (done) return
|
|
146
|
+
done = true
|
|
147
|
+
const ref = list
|
|
148
|
+
if (!ref) return
|
|
149
|
+
setRegistrations((arr) => arr.filter((x) => x !== ref))
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function useCommandDialog() {
|
|
157
|
+
const value = useContext(ctx)
|
|
158
|
+
if (!value) {
|
|
159
|
+
throw new Error("useCommandDialog must be used within a CommandProvider")
|
|
160
|
+
}
|
|
161
|
+
return value
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function CommandProvider(props: ParentProps) {
|
|
165
|
+
const value = init()
|
|
166
|
+
const dialog = useDialog()
|
|
167
|
+
const keybind = useKeybind()
|
|
168
|
+
|
|
169
|
+
useKeyboard((evt) => {
|
|
170
|
+
if (value.suspended()) return
|
|
171
|
+
if (dialog.stack.length > 0) return
|
|
172
|
+
if (evt.defaultPrevented) return
|
|
173
|
+
if (keybind.match("command_list", evt)) {
|
|
174
|
+
evt.preventDefault()
|
|
175
|
+
value.show()
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function DialogCommand(props: { options: CommandOption[]; suggestedOptions: CommandOption[] }) {
|
|
184
|
+
let ref: DialogSelectRef<string>
|
|
185
|
+
const list = () => {
|
|
186
|
+
if (ref?.filter) return props.options
|
|
187
|
+
return [...props.suggestedOptions, ...props.options]
|
|
188
|
+
}
|
|
189
|
+
return <DialogSelect ref={(r) => (ref = r)} title={t("cmd.title")} options={list()} />
|
|
190
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createResource, createMemo } from "solid-js"
|
|
2
|
+
import { DialogSelect } from "@tui/ui/dialog-select"
|
|
3
|
+
import { useSDK } from "@tui/context/sdk"
|
|
4
|
+
import { useDialog } from "@tui/ui/dialog"
|
|
5
|
+
import { useToast } from "@tui/ui/toast"
|
|
6
|
+
import { useTheme } from "@tui/context/theme"
|
|
7
|
+
import type { ExperimentalConsoleListOrgsResponse } from "@saeeol/sdk/v2"
|
|
8
|
+
|
|
9
|
+
type OrgOption = ExperimentalConsoleListOrgsResponse["orgs"][number]
|
|
10
|
+
|
|
11
|
+
const accountHost = (url: string) => {
|
|
12
|
+
try {
|
|
13
|
+
return new URL(url).host
|
|
14
|
+
} catch {
|
|
15
|
+
return url
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const accountLabel = (item: Pick<OrgOption, "accountEmail" | "accountUrl">) =>
|
|
20
|
+
`${item.accountEmail} ${accountHost(item.accountUrl)}`
|
|
21
|
+
|
|
22
|
+
export function DialogConsoleOrg() {
|
|
23
|
+
const sdk = useSDK()
|
|
24
|
+
const dialog = useDialog()
|
|
25
|
+
const toast = useToast()
|
|
26
|
+
const { theme } = useTheme()
|
|
27
|
+
|
|
28
|
+
const [orgs] = createResource(async () => {
|
|
29
|
+
const result = await sdk.client.experimental.console.listOrgs({}, { throwOnError: true })
|
|
30
|
+
return result.data?.orgs ?? []
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const current = createMemo(() => orgs()?.find((item) => item.active))
|
|
34
|
+
|
|
35
|
+
const options = createMemo(() => {
|
|
36
|
+
const listed = orgs()
|
|
37
|
+
if (listed === undefined) {
|
|
38
|
+
return [
|
|
39
|
+
{
|
|
40
|
+
title: "Loading orgs...",
|
|
41
|
+
value: "loading",
|
|
42
|
+
onSelect: () => {},
|
|
43
|
+
},
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (listed.length === 0) {
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
title: "No orgs found",
|
|
51
|
+
value: "empty",
|
|
52
|
+
onSelect: () => {},
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return listed
|
|
58
|
+
.toSorted((a, b) => {
|
|
59
|
+
const activeAccountA = a.active ? 0 : 1
|
|
60
|
+
const activeAccountB = b.active ? 0 : 1
|
|
61
|
+
if (activeAccountA !== activeAccountB) return activeAccountA - activeAccountB
|
|
62
|
+
|
|
63
|
+
const accountCompare = accountLabel(a).localeCompare(accountLabel(b))
|
|
64
|
+
if (accountCompare !== 0) return accountCompare
|
|
65
|
+
|
|
66
|
+
return a.orgName.localeCompare(b.orgName)
|
|
67
|
+
})
|
|
68
|
+
.map((item) => ({
|
|
69
|
+
title: item.orgName,
|
|
70
|
+
value: item,
|
|
71
|
+
category: accountLabel(item),
|
|
72
|
+
categoryView: (
|
|
73
|
+
<box flexDirection="row" gap={2}>
|
|
74
|
+
<text fg={theme.accent}>{item.accountEmail}</text>
|
|
75
|
+
<text fg={theme.textMuted}>{accountHost(item.accountUrl)}</text>
|
|
76
|
+
</box>
|
|
77
|
+
),
|
|
78
|
+
onSelect: async () => {
|
|
79
|
+
if (item.active) {
|
|
80
|
+
dialog.clear()
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await sdk.client.experimental.console.switchOrg(
|
|
85
|
+
{
|
|
86
|
+
accountID: item.accountID,
|
|
87
|
+
orgID: item.orgID,
|
|
88
|
+
},
|
|
89
|
+
{ throwOnError: true },
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
await sdk.client.instance.dispose()
|
|
93
|
+
toast.show({
|
|
94
|
+
message: `Switched to ${item.orgName}`,
|
|
95
|
+
variant: "info",
|
|
96
|
+
})
|
|
97
|
+
dialog.clear()
|
|
98
|
+
},
|
|
99
|
+
}))
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
return <DialogSelect<string | OrgOption> title="Switch org" options={options()} current={current()} />
|
|
103
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { BoxRenderable, RGBA, TextAttributes } from "@opentui/core"
|
|
2
|
+
import { useKeyboard } from "@opentui/solid"
|
|
3
|
+
import open from "open"
|
|
4
|
+
import { createSignal, onCleanup, onMount } from "solid-js"
|
|
5
|
+
import { selectedForeground, useTheme } from "@tui/context/theme"
|
|
6
|
+
import { useDialog, type DialogContext } from "@tui/ui/dialog"
|
|
7
|
+
import { Link } from "@tui/ui/link"
|
|
8
|
+
import { GoLogo } from "../logo"
|
|
9
|
+
import { BgPulse, type BgPulseMask } from "../bg-pulse"
|
|
10
|
+
|
|
11
|
+
const GO_URL = "https://saeeol.ai/go"
|
|
12
|
+
const PAD_X = 3
|
|
13
|
+
const PAD_TOP_OUTER = 1
|
|
14
|
+
|
|
15
|
+
export type DialogGoUpsellProps = {
|
|
16
|
+
onClose?: (dontShowAgain?: boolean) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function subscribe(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
20
|
+
open(GO_URL).catch(() => {})
|
|
21
|
+
props.onClose?.()
|
|
22
|
+
dialog.clear()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function dismiss(props: DialogGoUpsellProps, dialog: ReturnType<typeof useDialog>) {
|
|
26
|
+
props.onClose?.(true)
|
|
27
|
+
dialog.clear()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function DialogGoUpsell(props: DialogGoUpsellProps) {
|
|
31
|
+
const dialog = useDialog()
|
|
32
|
+
const { theme } = useTheme()
|
|
33
|
+
const fg = selectedForeground(theme)
|
|
34
|
+
const [selected, setSelected] = createSignal<"dismiss" | "subscribe">("subscribe")
|
|
35
|
+
const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>()
|
|
36
|
+
const [masks, setMasks] = createSignal<BgPulseMask[]>([])
|
|
37
|
+
let content: BoxRenderable | undefined
|
|
38
|
+
let logoBox: BoxRenderable | undefined
|
|
39
|
+
let headingBox: BoxRenderable | undefined
|
|
40
|
+
let descBox: BoxRenderable | undefined
|
|
41
|
+
let buttonsBox: BoxRenderable | undefined
|
|
42
|
+
|
|
43
|
+
const sync = () => {
|
|
44
|
+
if (!content || !logoBox) return
|
|
45
|
+
setCenter({
|
|
46
|
+
x: logoBox.x - content.x + logoBox.width / 2,
|
|
47
|
+
y: logoBox.y - content.y + logoBox.height / 2 + PAD_TOP_OUTER,
|
|
48
|
+
})
|
|
49
|
+
const next: BgPulseMask[] = []
|
|
50
|
+
const baseY = PAD_TOP_OUTER
|
|
51
|
+
for (const b of [headingBox, descBox, buttonsBox]) {
|
|
52
|
+
if (!b) continue
|
|
53
|
+
next.push({
|
|
54
|
+
x: b.x - content.x,
|
|
55
|
+
y: b.y - content.y + baseY,
|
|
56
|
+
width: b.width,
|
|
57
|
+
height: b.height,
|
|
58
|
+
pad: 2,
|
|
59
|
+
strength: 0.78,
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
setMasks(next)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
onMount(() => {
|
|
66
|
+
sync()
|
|
67
|
+
for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.on("resize", sync)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
onCleanup(() => {
|
|
71
|
+
for (const b of [content, logoBox, headingBox, descBox, buttonsBox]) b?.off("resize", sync)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
useKeyboard((evt) => {
|
|
75
|
+
if (evt.name === "left" || evt.name === "right" || evt.name === "tab") {
|
|
76
|
+
setSelected((s) => (s === "subscribe" ? "dismiss" : "subscribe"))
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
if (evt.name === "return") {
|
|
80
|
+
evt.preventDefault()
|
|
81
|
+
evt.stopPropagation()
|
|
82
|
+
if (selected() === "subscribe") subscribe(props, dialog)
|
|
83
|
+
else dismiss(props, dialog)
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<box ref={(item: BoxRenderable) => (content = item)}>
|
|
89
|
+
<box position="absolute" top={-PAD_TOP_OUTER} left={0} right={0} bottom={0} zIndex={0}>
|
|
90
|
+
<BgPulse centerX={center()?.x} centerY={center()?.y} masks={masks()} />
|
|
91
|
+
</box>
|
|
92
|
+
<box paddingLeft={PAD_X} paddingRight={PAD_X} paddingBottom={1} gap={1}>
|
|
93
|
+
<box ref={(item: BoxRenderable) => (headingBox = item)} flexDirection="row" justifyContent="space-between">
|
|
94
|
+
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
|
95
|
+
Free limit reached
|
|
96
|
+
</text>
|
|
97
|
+
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
|
98
|
+
esc
|
|
99
|
+
</text>
|
|
100
|
+
</box>
|
|
101
|
+
<box ref={(item: BoxRenderable) => (descBox = item)} gap={0}>
|
|
102
|
+
<box flexDirection="row">
|
|
103
|
+
<text fg={theme.textMuted}>Subscribe to </text>
|
|
104
|
+
<text attributes={TextAttributes.BOLD} fg={theme.textMuted}>
|
|
105
|
+
Saeeol Go
|
|
106
|
+
</text>
|
|
107
|
+
<text fg={theme.textMuted}> for reliable access to the</text>
|
|
108
|
+
</box>
|
|
109
|
+
<text fg={theme.textMuted}>best open-source models, starting at $5/month.</text>
|
|
110
|
+
</box>
|
|
111
|
+
<box alignItems="center" gap={1} paddingBottom={1}>
|
|
112
|
+
<box ref={(item: BoxRenderable) => (logoBox = item)}>
|
|
113
|
+
<GoLogo />
|
|
114
|
+
</box>
|
|
115
|
+
<Link href={GO_URL} fg={theme.primary} />
|
|
116
|
+
</box>
|
|
117
|
+
<box ref={(item: BoxRenderable) => (buttonsBox = item)} flexDirection="row" justifyContent="space-between">
|
|
118
|
+
<box
|
|
119
|
+
paddingLeft={2}
|
|
120
|
+
paddingRight={2}
|
|
121
|
+
backgroundColor={selected() === "dismiss" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
122
|
+
onMouseOver={() => setSelected("dismiss")}
|
|
123
|
+
onMouseUp={() => dismiss(props, dialog)}
|
|
124
|
+
>
|
|
125
|
+
<text
|
|
126
|
+
fg={selected() === "dismiss" ? fg : theme.textMuted}
|
|
127
|
+
attributes={selected() === "dismiss" ? TextAttributes.BOLD : undefined}
|
|
128
|
+
>
|
|
129
|
+
don't show again
|
|
130
|
+
</text>
|
|
131
|
+
</box>
|
|
132
|
+
<box
|
|
133
|
+
paddingLeft={2}
|
|
134
|
+
paddingRight={2}
|
|
135
|
+
backgroundColor={selected() === "subscribe" ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
|
136
|
+
onMouseOver={() => setSelected("subscribe")}
|
|
137
|
+
onMouseUp={() => subscribe(props, dialog)}
|
|
138
|
+
>
|
|
139
|
+
<text
|
|
140
|
+
fg={selected() === "subscribe" ? fg : theme.text}
|
|
141
|
+
attributes={selected() === "subscribe" ? TextAttributes.BOLD : undefined}
|
|
142
|
+
>
|
|
143
|
+
subscribe
|
|
144
|
+
</text>
|
|
145
|
+
</box>
|
|
146
|
+
</box>
|
|
147
|
+
</box>
|
|
148
|
+
</box>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
DialogGoUpsell.show = (dialog: DialogContext) => {
|
|
153
|
+
return new Promise<boolean>((resolve) => {
|
|
154
|
+
dialog.replace(
|
|
155
|
+
() => <DialogGoUpsell onClose={(dontShow) => resolve(dontShow ?? false)} />,
|
|
156
|
+
() => resolve(false),
|
|
157
|
+
)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createMemo, createSignal } from "solid-js"
|
|
2
|
+
import { useLocal } from "@tui/context/local"
|
|
3
|
+
import { useSync } from "@tui/context/sync"
|
|
4
|
+
import { map, pipe, entries, sortBy } from "remeda"
|
|
5
|
+
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "@tui/ui/dialog-select"
|
|
6
|
+
import { useTheme } from "../context/theme"
|
|
7
|
+
import { Keybind } from "@/util/keybind"
|
|
8
|
+
import { TextAttributes } from "@opentui/core"
|
|
9
|
+
import { useSDK } from "@tui/context/sdk"
|
|
10
|
+
|
|
11
|
+
function Status(props: { enabled: boolean; loading: boolean }) {
|
|
12
|
+
const { theme } = useTheme()
|
|
13
|
+
if (props.loading) {
|
|
14
|
+
return <span style={{ fg: theme.textMuted }}>⋯ Loading</span>
|
|
15
|
+
}
|
|
16
|
+
if (props.enabled) {
|
|
17
|
+
return <span style={{ fg: theme.success, attributes: TextAttributes.BOLD }}>✓ Enabled</span>
|
|
18
|
+
}
|
|
19
|
+
return <span style={{ fg: theme.textMuted }}>○ Disabled</span>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function DialogMcp() {
|
|
23
|
+
const local = useLocal()
|
|
24
|
+
const sync = useSync()
|
|
25
|
+
const sdk = useSDK()
|
|
26
|
+
const [, setRef] = createSignal<DialogSelectRef<unknown>>()
|
|
27
|
+
const [loading, setLoading] = createSignal<string | null>(null)
|
|
28
|
+
|
|
29
|
+
const options = createMemo(() => {
|
|
30
|
+
// Track sync data and loading state to trigger re-render when they change
|
|
31
|
+
const mcpData = sync.data.mcp
|
|
32
|
+
const loadingMcp = loading()
|
|
33
|
+
|
|
34
|
+
return pipe(
|
|
35
|
+
mcpData ?? {},
|
|
36
|
+
entries(),
|
|
37
|
+
sortBy(([name]) => name),
|
|
38
|
+
map(([name, status]) => ({
|
|
39
|
+
value: name,
|
|
40
|
+
title: name,
|
|
41
|
+
description: status.status === "failed" ? "failed" : status.status,
|
|
42
|
+
footer: <Status enabled={local.mcp.isEnabled(name)} loading={loadingMcp === name} />,
|
|
43
|
+
category: undefined,
|
|
44
|
+
})),
|
|
45
|
+
)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const keybinds = createMemo(() => [
|
|
49
|
+
{
|
|
50
|
+
keybind: Keybind.parse("space")[0],
|
|
51
|
+
title: "toggle",
|
|
52
|
+
onTrigger: async (option: DialogSelectOption<string>) => {
|
|
53
|
+
// Prevent toggling while an operation is already in progress
|
|
54
|
+
if (loading() !== null) return
|
|
55
|
+
|
|
56
|
+
setLoading(option.value)
|
|
57
|
+
try {
|
|
58
|
+
await local.mcp.toggle(option.value)
|
|
59
|
+
// Refresh MCP status from server
|
|
60
|
+
const status = await sdk.client.mcp.status()
|
|
61
|
+
if (status.data) {
|
|
62
|
+
sync.set("mcp", status.data)
|
|
63
|
+
} else {
|
|
64
|
+
console.error("Failed to refresh MCP status: no data returned")
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("Failed to toggle MCP:", error)
|
|
68
|
+
} finally {
|
|
69
|
+
setLoading(null)
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<DialogSelect
|
|
77
|
+
ref={setRef}
|
|
78
|
+
title="MCPs"
|
|
79
|
+
options={options()}
|
|
80
|
+
keybind={keybinds()}
|
|
81
|
+
onSelect={(_option) => {
|
|
82
|
+
// Don't close on select, only on escape
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
86
|
+
}
|