saeeol 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/saeeol.cjs +187 -0
- package/npm/bin/saeeol +0 -0
- package/package.json +12 -12
- 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/cli/cmd/tui/context/app/args.tsx +15 -0
- package/src/cli/cmd/tui/context/app/directory.ts +15 -0
- package/src/cli/cmd/tui/context/app/editor-zed.ts +281 -0
- package/src/cli/cmd/tui/context/app/editor.ts +425 -0
- package/src/cli/cmd/tui/context/app/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/app/project.tsx +109 -0
- package/src/cli/cmd/tui/context/app/route.tsx +67 -0
- package/src/cli/cmd/tui/context/app/sdk.tsx +142 -0
- package/src/cli/cmd/tui/context/app/sync.tsx +713 -0
- package/src/cli/cmd/tui/context/app/theme.tsx +307 -0
- package/src/cli/cmd/tui/context/app/tui-config.tsx +9 -0
- package/src/cli/cmd/tui/context/args.tsx +1 -15
- package/src/cli/cmd/tui/context/directory.ts +1 -15
- package/src/cli/cmd/tui/context/editor-zed.ts +1 -281
- package/src/cli/cmd/tui/context/editor.ts +1 -425
- package/src/cli/cmd/tui/context/event.ts +1 -45
- package/src/cli/cmd/tui/context/exit.tsx +1 -67
- package/src/cli/cmd/tui/context/helper.tsx +1 -25
- package/src/cli/cmd/tui/context/keybind.tsx +1 -105
- package/src/cli/cmd/tui/context/kv.tsx +1 -76
- package/src/cli/cmd/tui/context/local.tsx +1 -478
- package/src/cli/cmd/tui/context/plugin-keybinds.ts +1 -41
- package/src/cli/cmd/tui/context/project.tsx +1 -109
- package/src/cli/cmd/tui/context/prompt.tsx +1 -18
- package/src/cli/cmd/tui/context/route.tsx +1 -67
- package/src/cli/cmd/tui/context/runtime/event.ts +45 -0
- package/src/cli/cmd/tui/context/runtime/exit.tsx +67 -0
- package/src/cli/cmd/tui/context/runtime/keybind.tsx +105 -0
- package/src/cli/cmd/tui/context/runtime/kv.tsx +76 -0
- package/src/cli/cmd/tui/context/runtime/local.tsx +478 -0
- package/src/cli/cmd/tui/context/runtime/plugin-keybinds.ts +41 -0
- package/src/cli/cmd/tui/context/sdk.tsx +1 -142
- package/src/cli/cmd/tui/context/session/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/sync.tsx +1 -713
- package/src/cli/cmd/tui/context/theme.tsx +1 -307
- package/src/cli/cmd/tui/context/tui-config.tsx +1 -9
- 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/bin/saeeol.cjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process")
|
|
4
|
+
const fs = require("fs")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const os = require("os")
|
|
7
|
+
|
|
8
|
+
function run(target) {
|
|
9
|
+
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
})
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.error(result.error.message)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
const code = typeof result.status === "number" ? result.status : 0
|
|
17
|
+
process.exit(code)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const envPath = process.env.SAEEOL_BIN_PATH
|
|
21
|
+
if (envPath) {
|
|
22
|
+
run(envPath)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const scriptPath = fs.realpathSync(__filename)
|
|
26
|
+
const scriptDir = path.dirname(scriptPath)
|
|
27
|
+
|
|
28
|
+
// fall through to findBinary() if cached binary fails
|
|
29
|
+
const cached = path.join(scriptDir, ".saeeol")
|
|
30
|
+
if (fs.existsSync(cached)) {
|
|
31
|
+
const result = childProcess.spawnSync(cached, process.argv.slice(2), {
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
})
|
|
34
|
+
if (!result.error) {
|
|
35
|
+
const code = typeof result.status === "number" ? result.status : 0
|
|
36
|
+
process.exit(code)
|
|
37
|
+
}
|
|
38
|
+
// cached binary failed (e.g. wrong platform/arch, missing dynamic linker),
|
|
39
|
+
// fall through to findBinary() which has better variant detection
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const platformMap = {
|
|
43
|
+
darwin: "darwin",
|
|
44
|
+
linux: "linux",
|
|
45
|
+
win32: "windows",
|
|
46
|
+
}
|
|
47
|
+
const archMap = {
|
|
48
|
+
x64: "x64",
|
|
49
|
+
arm64: "arm64",
|
|
50
|
+
arm: "arm",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let platform = platformMap[os.platform()]
|
|
54
|
+
if (!platform) {
|
|
55
|
+
platform = os.platform()
|
|
56
|
+
}
|
|
57
|
+
let arch = archMap[os.arch()]
|
|
58
|
+
if (!arch) {
|
|
59
|
+
arch = os.arch()
|
|
60
|
+
}
|
|
61
|
+
const base = "saeeol-" + platform + "-" + arch
|
|
62
|
+
const binary = platform === "windows" ? "saeeol.exe" : "saeeol"
|
|
63
|
+
|
|
64
|
+
function supportsAvx2() {
|
|
65
|
+
if (arch !== "x64") return false
|
|
66
|
+
|
|
67
|
+
if (platform === "linux") {
|
|
68
|
+
try {
|
|
69
|
+
return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
|
|
70
|
+
} catch {
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (platform === "darwin") {
|
|
76
|
+
try {
|
|
77
|
+
const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
timeout: 1500,
|
|
80
|
+
})
|
|
81
|
+
if (result.status !== 0) return false
|
|
82
|
+
return (result.stdout || "").trim() === "1"
|
|
83
|
+
} catch {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (platform === "windows") {
|
|
89
|
+
const cmd =
|
|
90
|
+
'(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'
|
|
91
|
+
|
|
92
|
+
for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
|
|
93
|
+
try {
|
|
94
|
+
const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
|
|
95
|
+
encoding: "utf8",
|
|
96
|
+
timeout: 3000,
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
})
|
|
99
|
+
if (result.status !== 0) continue
|
|
100
|
+
const out = (result.stdout || "").trim().toLowerCase()
|
|
101
|
+
if (out === "true" || out === "1") return true
|
|
102
|
+
if (out === "false" || out === "0") return false
|
|
103
|
+
} catch {
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const names = (() => {
|
|
115
|
+
const avx2 = supportsAvx2()
|
|
116
|
+
const baseline = arch === "x64" && !avx2
|
|
117
|
+
|
|
118
|
+
if (platform === "linux") {
|
|
119
|
+
const musl = (() => {
|
|
120
|
+
try {
|
|
121
|
+
if (fs.existsSync("/etc/alpine-release")) return true
|
|
122
|
+
} catch {
|
|
123
|
+
// ignore
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const result = childProcess.spawnSync("ldd", ["--version"], { encoding: "utf8" })
|
|
128
|
+
const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
|
|
129
|
+
if (text.includes("musl")) return true
|
|
130
|
+
} catch {
|
|
131
|
+
// ignore
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return false
|
|
135
|
+
})()
|
|
136
|
+
|
|
137
|
+
if (musl) {
|
|
138
|
+
if (arch === "x64") {
|
|
139
|
+
if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
|
|
140
|
+
return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
|
|
141
|
+
}
|
|
142
|
+
return [`${base}-musl`, base]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (arch === "x64") {
|
|
146
|
+
if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
|
|
147
|
+
return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
|
|
148
|
+
}
|
|
149
|
+
return [base, `${base}-musl`]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (arch === "x64") {
|
|
153
|
+
if (baseline) return [`${base}-baseline`, base]
|
|
154
|
+
return [base, `${base}-baseline`]
|
|
155
|
+
}
|
|
156
|
+
return [base]
|
|
157
|
+
})()
|
|
158
|
+
|
|
159
|
+
function findBinary(startDir) {
|
|
160
|
+
let current = startDir
|
|
161
|
+
for (;;) {
|
|
162
|
+
const modules = path.join(current, "node_modules")
|
|
163
|
+
if (fs.existsSync(modules)) {
|
|
164
|
+
for (const name of names) {
|
|
165
|
+
const candidate = path.join(modules, name, "bin", binary)
|
|
166
|
+
if (fs.existsSync(candidate)) return candidate
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const parent = path.dirname(current)
|
|
170
|
+
if (parent === current) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
current = parent
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const resolved = findBinary(scriptDir)
|
|
178
|
+
if (!resolved) {
|
|
179
|
+
console.error(
|
|
180
|
+
"It seems that your package manager failed to install the right version of the SAEEOL CLI for your platform. You can try manually installing " +
|
|
181
|
+
names.map((n) => `\"${n}\"`).join(" or ") +
|
|
182
|
+
" package",
|
|
183
|
+
)
|
|
184
|
+
process.exit(1)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
run(resolved)
|
package/npm/bin/saeeol
CHANGED
|
File without changes
|
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.3",
|
|
4
4
|
"name": "saeeol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"db": "bun drizzle-kit"
|
|
25
25
|
},
|
|
26
26
|
"bin": {
|
|
27
|
-
"saeeol": "./bin/saeeol"
|
|
27
|
+
"saeeol": "./bin/saeeol.cjs"
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
"./*": "./src/*.ts"
|
|
@@ -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": "7.3.
|
|
54
|
-
"@saeeol/core": "7.3.
|
|
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",
|
|
@@ -133,14 +133,14 @@
|
|
|
133
133
|
"@parcel/watcher": "2.5.1",
|
|
134
134
|
"@pierre/diffs": "1.1.0-beta.18",
|
|
135
135
|
"@saeeol/boxes": "0.2.0",
|
|
136
|
-
"@saeeol/core": "7.3.
|
|
137
|
-
"@saeeol/gateway": "7.3.
|
|
138
|
-
"@saeeol/i18n": "7.3.
|
|
139
|
-
"@saeeol/indexing": "7.3.
|
|
140
|
-
"@saeeol/plugin": "7.3.
|
|
141
|
-
"@saeeol/script": "7.3.
|
|
142
|
-
"@saeeol/sdk": "7.3.
|
|
143
|
-
"@saeeol/telemetry": "7.3.
|
|
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",
|
|
@@ -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
|
+
}
|