saeeol 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/bin/saeeol.cjs +187 -0
  2. package/npm/bin/saeeol +0 -0
  3. package/package.json +12 -12
  4. package/src/cli/cmd/tui/component/dialog/dialog-agent.tsx +32 -0
  5. package/src/cli/cmd/tui/component/dialog/dialog-command.tsx +190 -0
  6. package/src/cli/cmd/tui/component/dialog/dialog-console-org.tsx +103 -0
  7. package/src/cli/cmd/tui/component/dialog/dialog-go-upsell.tsx +159 -0
  8. package/src/cli/cmd/tui/component/dialog/dialog-mcp.tsx +86 -0
  9. package/src/cli/cmd/tui/component/dialog/dialog-model.tsx +238 -0
  10. package/src/cli/cmd/tui/component/dialog/dialog-provider.tsx +343 -0
  11. package/src/cli/cmd/tui/component/dialog/dialog-session-delete-failed.tsx +103 -0
  12. package/src/cli/cmd/tui/component/dialog/dialog-session-list.tsx +301 -0
  13. package/src/cli/cmd/tui/component/dialog/dialog-session-rename.tsx +35 -0
  14. package/src/cli/cmd/tui/component/dialog/dialog-skill.tsx +37 -0
  15. package/src/cli/cmd/tui/component/dialog/dialog-stash.tsx +87 -0
  16. package/src/cli/cmd/tui/component/dialog/dialog-status.tsx +190 -0
  17. package/src/cli/cmd/tui/component/dialog/dialog-tag.tsx +44 -0
  18. package/src/cli/cmd/tui/component/dialog/dialog-theme-list.tsx +50 -0
  19. package/src/cli/cmd/tui/component/dialog/dialog-variant.tsx +39 -0
  20. package/src/cli/cmd/tui/component/dialog/dialog-workspace-create.tsx +200 -0
  21. package/src/cli/cmd/tui/component/dialog/dialog-workspace-unavailable.tsx +81 -0
  22. package/src/cli/cmd/tui/component/dialog-agent.tsx +1 -32
  23. package/src/cli/cmd/tui/component/dialog-command.tsx +1 -190
  24. package/src/cli/cmd/tui/component/dialog-console-org.tsx +1 -103
  25. package/src/cli/cmd/tui/component/dialog-go-upsell.tsx +1 -159
  26. package/src/cli/cmd/tui/component/dialog-mcp.tsx +1 -86
  27. package/src/cli/cmd/tui/component/dialog-model.tsx +1 -238
  28. package/src/cli/cmd/tui/component/dialog-provider.tsx +1 -343
  29. package/src/cli/cmd/tui/component/dialog-session-delete-failed.tsx +1 -103
  30. package/src/cli/cmd/tui/component/dialog-session-list.tsx +1 -301
  31. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +1 -35
  32. package/src/cli/cmd/tui/component/dialog-skill.tsx +1 -37
  33. package/src/cli/cmd/tui/component/dialog-stash.tsx +1 -87
  34. package/src/cli/cmd/tui/component/dialog-status.tsx +1 -190
  35. package/src/cli/cmd/tui/component/dialog-tag.tsx +1 -44
  36. package/src/cli/cmd/tui/component/dialog-theme-list.tsx +1 -50
  37. package/src/cli/cmd/tui/component/dialog-variant.tsx +1 -39
  38. package/src/cli/cmd/tui/component/dialog-workspace-create.tsx +1 -200
  39. package/src/cli/cmd/tui/component/dialog-workspace-unavailable.tsx +1 -81
  40. package/src/cli/cmd/tui/context/app/args.tsx +15 -0
  41. package/src/cli/cmd/tui/context/app/directory.ts +15 -0
  42. package/src/cli/cmd/tui/context/app/editor-zed.ts +281 -0
  43. package/src/cli/cmd/tui/context/app/editor.ts +425 -0
  44. package/src/cli/cmd/tui/context/app/helper.tsx +25 -0
  45. package/src/cli/cmd/tui/context/app/project.tsx +109 -0
  46. package/src/cli/cmd/tui/context/app/route.tsx +67 -0
  47. package/src/cli/cmd/tui/context/app/sdk.tsx +142 -0
  48. package/src/cli/cmd/tui/context/app/sync.tsx +713 -0
  49. package/src/cli/cmd/tui/context/app/theme.tsx +307 -0
  50. package/src/cli/cmd/tui/context/app/tui-config.tsx +9 -0
  51. package/src/cli/cmd/tui/context/args.tsx +1 -15
  52. package/src/cli/cmd/tui/context/directory.ts +1 -15
  53. package/src/cli/cmd/tui/context/editor-zed.ts +1 -281
  54. package/src/cli/cmd/tui/context/editor.ts +1 -425
  55. package/src/cli/cmd/tui/context/event.ts +1 -45
  56. package/src/cli/cmd/tui/context/exit.tsx +1 -67
  57. package/src/cli/cmd/tui/context/helper.tsx +1 -25
  58. package/src/cli/cmd/tui/context/keybind.tsx +1 -105
  59. package/src/cli/cmd/tui/context/kv.tsx +1 -76
  60. package/src/cli/cmd/tui/context/local.tsx +1 -478
  61. package/src/cli/cmd/tui/context/plugin-keybinds.ts +1 -41
  62. package/src/cli/cmd/tui/context/project.tsx +1 -109
  63. package/src/cli/cmd/tui/context/prompt.tsx +1 -18
  64. package/src/cli/cmd/tui/context/route.tsx +1 -67
  65. package/src/cli/cmd/tui/context/runtime/event.ts +45 -0
  66. package/src/cli/cmd/tui/context/runtime/exit.tsx +67 -0
  67. package/src/cli/cmd/tui/context/runtime/keybind.tsx +105 -0
  68. package/src/cli/cmd/tui/context/runtime/kv.tsx +76 -0
  69. package/src/cli/cmd/tui/context/runtime/local.tsx +478 -0
  70. package/src/cli/cmd/tui/context/runtime/plugin-keybinds.ts +41 -0
  71. package/src/cli/cmd/tui/context/sdk.tsx +1 -142
  72. package/src/cli/cmd/tui/context/session/prompt.tsx +18 -0
  73. package/src/cli/cmd/tui/context/sync.tsx +1 -713
  74. package/src/cli/cmd/tui/context/theme.tsx +1 -307
  75. package/src/cli/cmd/tui/context/tui-config.tsx +1 -9
  76. package/src/tool/apply_patch.ts +1 -334
  77. package/src/tool/bash.ts +1 -656
  78. package/src/tool/core/external-directory.ts +55 -0
  79. package/src/tool/core/invalid.ts +21 -0
  80. package/src/tool/core/recall.ts +164 -0
  81. package/src/tool/core/recall.txt +12 -0
  82. package/src/tool/core/schema.ts +16 -0
  83. package/src/tool/core/tool.ts +162 -0
  84. package/src/tool/core/truncate.ts +160 -0
  85. package/src/tool/core/truncation-dir.ts +4 -0
  86. package/src/tool/diagnostics.ts +1 -20
  87. package/src/tool/edit-replacers.ts +1 -288
  88. package/src/tool/edit-utils.ts +1 -86
  89. package/src/tool/edit.ts +1 -262
  90. package/src/tool/external-directory.ts +1 -55
  91. package/src/tool/file/apply_patch.ts +334 -0
  92. package/src/tool/file/apply_patch.txt +33 -0
  93. package/src/tool/file/bash.ts +656 -0
  94. package/src/tool/file/bash.txt +119 -0
  95. package/src/tool/file/edit-replacers.ts +288 -0
  96. package/src/tool/file/edit-utils.ts +86 -0
  97. package/src/tool/file/edit.ts +262 -0
  98. package/src/tool/file/edit.txt +10 -0
  99. package/src/tool/file/read.ts +389 -0
  100. package/src/tool/file/read.txt +14 -0
  101. package/src/tool/file/write.ts +114 -0
  102. package/src/tool/file/write.txt +8 -0
  103. package/src/tool/glob.ts +1 -115
  104. package/src/tool/grep.ts +1 -151
  105. package/src/tool/integration/diagnostics.ts +20 -0
  106. package/src/tool/integration/lsp.ts +113 -0
  107. package/src/tool/integration/lsp.txt +24 -0
  108. package/src/tool/integration/mcp-exa.ts +73 -0
  109. package/src/tool/integration/package.ts +168 -0
  110. package/src/tool/integration/registry.ts +375 -0
  111. package/src/tool/invalid.ts +1 -21
  112. package/src/tool/lsp.ts +1 -113
  113. package/src/tool/mcp-exa.ts +1 -73
  114. package/src/tool/package.ts +1 -168
  115. package/src/tool/plan.ts +1 -30
  116. package/src/tool/question.ts +1 -52
  117. package/src/tool/read.ts +1 -389
  118. package/src/tool/recall.ts +1 -164
  119. package/src/tool/registry.ts +1 -375
  120. package/src/tool/schema.ts +1 -16
  121. package/src/tool/search/glob.ts +115 -0
  122. package/src/tool/search/glob.txt +6 -0
  123. package/src/tool/search/grep.ts +151 -0
  124. package/src/tool/search/grep.txt +8 -0
  125. package/src/tool/search/warpgrep.ts +107 -0
  126. package/src/tool/search/warpgrep.txt +10 -0
  127. package/src/tool/search/webfetch.ts +202 -0
  128. package/src/tool/search/webfetch.txt +13 -0
  129. package/src/tool/search/websearch.ts +71 -0
  130. package/src/tool/search/websearch.txt +14 -0
  131. package/src/tool/skill.ts +1 -91
  132. package/src/tool/task.ts +1 -197
  133. package/src/tool/todo.ts +1 -62
  134. package/src/tool/tool.ts +1 -162
  135. package/src/tool/truncate.ts +1 -160
  136. package/src/tool/truncation-dir.ts +1 -4
  137. package/src/tool/warpgrep.ts +1 -107
  138. package/src/tool/webfetch.ts +1 -202
  139. package/src/tool/websearch.ts +1 -71
  140. package/src/tool/workflow/plan-enter.txt +14 -0
  141. package/src/tool/workflow/plan-exit.txt +13 -0
  142. package/src/tool/workflow/plan.ts +30 -0
  143. package/src/tool/workflow/question.ts +52 -0
  144. package/src/tool/workflow/question.txt +11 -0
  145. package/src/tool/workflow/skill.ts +91 -0
  146. package/src/tool/workflow/skill.txt +5 -0
  147. package/src/tool/workflow/task.ts +197 -0
  148. package/src/tool/workflow/task.txt +57 -0
  149. package/src/tool/workflow/todo.ts +62 -0
  150. package/src/tool/workflow/todowrite.txt +167 -0
  151. package/src/tool/write.ts +1 -114
@@ -1,18 +1 @@
1
- import { createSimpleContext } from "./helper"
2
- import type { PromptRef } from "../component/prompt"
3
-
4
- export const { use: usePromptRef, provider: PromptRefProvider } = createSimpleContext({
5
- name: "PromptRef",
6
- init: () => {
7
- let current: PromptRef | undefined
8
-
9
- return {
10
- get current() {
11
- return current
12
- },
13
- set(ref: PromptRef | undefined) {
14
- current = ref
15
- },
16
- }
17
- },
18
- })
1
+ export * from "./session/prompt"
@@ -1,67 +1 @@
1
- import { createStore, reconcile, unwrap } from "solid-js/store"
2
- import { createSimpleContext } from "./helper"
3
- import type { PromptInfo } from "../component/prompt/history"
4
-
5
- export type HomeRoute = {
6
- type: "home"
7
- prompt?: PromptInfo
8
- }
9
-
10
- export type SessionRoute = {
11
- type: "session"
12
- sessionID: string
13
- prompt?: PromptInfo
14
- }
15
- export type SaeeolClawRoute = {
16
- type: "saeeolclaw"
17
- }
18
-
19
- export type LocalModelsRoute = {
20
- type: "local-models"
21
- }
22
-
23
- export type PluginRoute = {
24
- type: "plugin"
25
- id: string
26
- data?: Record<string, unknown>
27
- }
28
-
29
- export type Route = HomeRoute | SessionRoute | PluginRoute | SaeeolClawRoute | LocalModelsRoute
30
-
31
- export const { use: useRoute, provider: RouteProvider } = createSimpleContext({
32
- name: "Route",
33
- init: (props: { initialRoute?: Route }) => {
34
- const [store, setStore] = createStore<Route>(
35
- props.initialRoute ??
36
- (process.env["SAEEOL_ROUTE"]
37
- ? JSON.parse(process.env["SAEEOL_ROUTE"])
38
- : {
39
- type: "home",
40
- }),
41
- )
42
- let previous: Route | undefined
43
-
44
- return {
45
- get data() {
46
- return store
47
- },
48
- navigate(route: Route) {
49
- previous = structuredClone(unwrap(store))
50
- setStore(reconcile(route))
51
- },
52
- back() {
53
- const target = previous ?? ({ type: "home" } as const)
54
- previous = undefined
55
- console.log("navigate", target)
56
- setStore(target)
57
- },
58
- }
59
- },
60
- })
61
-
62
- export type RouteContext = ReturnType<typeof useRoute>
63
-
64
- export function useRouteData<T extends Route["type"]>(type: T) {
65
- const route = useRoute()
66
- return route.data as Extract<Route, { type: typeof type }>
67
- }
1
+ export * from "./app/route"
@@ -0,0 +1,45 @@
1
+ import type { Event } from "@saeeol/sdk/v2"
2
+ import { useProject } from "../app/project"
3
+ import { useSDK } from "../app/sdk"
4
+
5
+ export function useEvent() {
6
+ const project = useProject()
7
+ const sdk = useSDK()
8
+
9
+ function subscribe(handler: (event: Event) => void) {
10
+ return sdk.event.on("event", (event) => {
11
+ if (event.payload.type === "sync") {
12
+ return
13
+ }
14
+
15
+ // Special hack for truly global events
16
+ if (event.directory === "global") {
17
+ handler(event.payload)
18
+ }
19
+
20
+ if (project.workspace.current()) {
21
+ if (event.workspace === project.workspace.current()) {
22
+ handler(event.payload)
23
+ }
24
+
25
+ return
26
+ }
27
+
28
+ if (event.directory === project.instance.directory()) {
29
+ handler(event.payload)
30
+ }
31
+ })
32
+ }
33
+
34
+ function on<T extends Event["type"]>(type: T, handler: (event: Extract<Event, { type: T }>) => void) {
35
+ return subscribe((event) => {
36
+ if (event.type !== type) return
37
+ handler(event as Extract<Event, { type: T }>)
38
+ })
39
+ }
40
+
41
+ return {
42
+ subscribe,
43
+ on,
44
+ }
45
+ }
@@ -0,0 +1,67 @@
1
+ import { useRenderer } from "@opentui/solid"
2
+ import { createSimpleContext } from "../app/helper"
3
+ import { FormatError, FormatUnknownError } from "@/cli/error"
4
+ import { win32FlushInputBuffer } from "../../win32"
5
+ import { resetTerminalState } from "@/saeeol/cli/cmd/tui/util/terminal"
6
+ type Exit = ((reason?: unknown) => Promise<void>) & {
7
+ message: {
8
+ set: (value?: string) => () => void
9
+ clear: () => void
10
+ get: () => string | undefined
11
+ }
12
+ restart: () => Promise<void>
13
+ }
14
+
15
+ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
16
+ name: "Exit",
17
+ init: (input: { onBeforeExit?: () => Promise<void>; onExit?: () => Promise<void> }) => {
18
+ const renderer = useRenderer()
19
+ let message: string | undefined
20
+ let task: Promise<void> | undefined
21
+ const store = {
22
+ set: (value?: string) => {
23
+ const prev = message
24
+ message = value
25
+ return () => {
26
+ message = prev
27
+ }
28
+ },
29
+ clear: () => {
30
+ message = undefined
31
+ },
32
+ get: () => message,
33
+ }
34
+ const exit: Exit = Object.assign(
35
+ (reason?: unknown) => {
36
+ if (task) return task
37
+ task = (async () => {
38
+ await input.onBeforeExit?.()
39
+ // Reset window title before destroying renderer
40
+ renderer.setTerminalTitle("")
41
+ renderer.destroy()
42
+ win32FlushInputBuffer()
43
+ resetTerminalState()
44
+ if (reason) {
45
+ const formatted = FormatError(reason) ?? FormatUnknownError(reason)
46
+ if (formatted) {
47
+ process.stderr.write(formatted + "\n")
48
+ }
49
+ }
50
+ const text = store.get()
51
+ if (text) process.stdout.write(text + "\n")
52
+ await input.onExit?.()
53
+ })()
54
+ return task
55
+ },
56
+ {
57
+ message: store,
58
+ restart: () => {
59
+ globalThis.__SAEEOL_RESTART = true
60
+ return exit()
61
+ },
62
+ },
63
+ )
64
+ process.on("SIGHUP", () => exit())
65
+ return exit
66
+ },
67
+ })
@@ -0,0 +1,105 @@
1
+ import { createMemo } from "solid-js"
2
+ import { Keybind } from "@/util/keybind"
3
+ import { pipe, mapValues } from "remeda"
4
+ import type { TuiConfig } from "@/cli/cmd/tui/config/tui"
5
+ import type { ParsedKey, Renderable } from "@opentui/core"
6
+ import { createStore } from "solid-js/store"
7
+ import { useKeyboard, useRenderer } from "@opentui/solid"
8
+ import { createSimpleContext } from "../app/helper"
9
+ import { useTuiConfig } from "../app/tui-config"
10
+
11
+ export type KeybindKey = keyof NonNullable<TuiConfig.Info["keybinds"]> & string
12
+
13
+ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContext({
14
+ name: "Keybind",
15
+ init: () => {
16
+ const config = useTuiConfig()
17
+ const keybinds = createMemo<Record<string, Keybind.Info[]>>(() => {
18
+ return pipe(
19
+ (config.keybinds ?? {}) as Record<string, string>,
20
+ mapValues((value) => Keybind.parse(value)),
21
+ )
22
+ })
23
+ const [store, setStore] = createStore({
24
+ leader: false,
25
+ })
26
+ const renderer = useRenderer()
27
+
28
+ let focus: Renderable | null
29
+ let timeout: NodeJS.Timeout
30
+ function leader(active: boolean) {
31
+ if (active) {
32
+ setStore("leader", true)
33
+ focus = renderer.currentFocusedRenderable
34
+ focus?.blur()
35
+ if (timeout) clearTimeout(timeout)
36
+ timeout = setTimeout(() => {
37
+ if (!store.leader) return
38
+ leader(false)
39
+ if (!focus || focus.isDestroyed) return
40
+ focus.focus()
41
+ }, 2000)
42
+ return
43
+ }
44
+
45
+ if (!active) {
46
+ if (focus && !renderer.currentFocusedRenderable) {
47
+ focus.focus()
48
+ }
49
+ setStore("leader", false)
50
+ }
51
+ }
52
+
53
+ useKeyboard(async (evt) => {
54
+ if (!store.leader && result.match("leader", evt)) {
55
+ leader(true)
56
+ return
57
+ }
58
+
59
+ if (store.leader && evt.name) {
60
+ setImmediate(() => {
61
+ if (focus && renderer.currentFocusedRenderable === focus) {
62
+ focus.focus()
63
+ }
64
+ leader(false)
65
+ })
66
+ }
67
+ })
68
+
69
+ const result = {
70
+ get all() {
71
+ return keybinds()
72
+ },
73
+ get leader() {
74
+ return store.leader
75
+ },
76
+ parse(evt: ParsedKey): Keybind.Info {
77
+ // Handle special case for Ctrl+Underscore (represented as \x1F)
78
+ if (evt.name === "\x1F") {
79
+ return Keybind.fromParsedKey({ ...evt, name: "_", ctrl: true }, store.leader)
80
+ }
81
+ return Keybind.fromParsedKey(evt, store.leader)
82
+ },
83
+ match(key: string, evt: ParsedKey) {
84
+ const list = keybinds()[key] ?? Keybind.parse(key)
85
+ if (!list.length) return false
86
+ const parsed: Keybind.Info = result.parse(evt)
87
+ for (const item of list) {
88
+ if (Keybind.match(item, parsed)) {
89
+ return true
90
+ }
91
+ }
92
+ return false
93
+ },
94
+ print(key: string) {
95
+ const first = keybinds()[key]?.at(0) ?? Keybind.parse(key).at(0)
96
+ if (!first) return ""
97
+ const text = Keybind.toString(first)
98
+ const lead = keybinds().leader?.[0]
99
+ if (!lead) return text
100
+ return text.replace("<leader>", Keybind.toString(lead))
101
+ },
102
+ }
103
+ return result
104
+ },
105
+ })
@@ -0,0 +1,76 @@
1
+ import { Global } from "@saeeol/core/global"
2
+ import { Filesystem } from "@/util/filesystem"
3
+ import { Flock } from "@saeeol/core/util/flock"
4
+ import { rename, rm } from "fs/promises"
5
+ import { createSignal, type Setter } from "solid-js"
6
+ import { createStore, unwrap } from "solid-js/store"
7
+ import { createSimpleContext } from "../app/helper"
8
+ import path from "path"
9
+
10
+ export const { use: useKV, provider: KVProvider } = createSimpleContext({
11
+ name: "KV",
12
+ init: () => {
13
+ const [ready, setReady] = createSignal(false)
14
+ const [store, setStore] = createStore<Record<string, any>>()
15
+ const filePath = path.join(Global.Path.state, "kv.json")
16
+ const lock = `tui-kv:${filePath}`
17
+ // Queue same-process writes so rapid updates persist in order.
18
+ let write = Promise.resolve()
19
+
20
+ // Write to a temp file first so kv.json is only replaced once the JSON is complete, avoiding partial writes if shutdown interrupts persistence.
21
+ function writeSnapshot(snapshot: Record<string, any>) {
22
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`
23
+ return Filesystem.writeJson(tempPath, snapshot)
24
+ .then(() => rename(tempPath, filePath))
25
+ .catch(async (error) => {
26
+ await rm(tempPath, { force: true }).catch(() => undefined)
27
+ throw error
28
+ })
29
+ }
30
+
31
+ // Read under the same lock used for writes because kv.json is shared across processes.
32
+ Flock.withLock(lock, () => Filesystem.readJson<Record<string, any>>(filePath))
33
+ .then((x) => {
34
+ setStore(x)
35
+ })
36
+ .catch((error) => {
37
+ console.error("Failed to read KV state", { filePath, error })
38
+ })
39
+ .finally(() => {
40
+ setReady(true)
41
+ })
42
+
43
+ const result = {
44
+ get ready() {
45
+ return ready()
46
+ },
47
+ get store() {
48
+ return store
49
+ },
50
+ signal<T>(name: string, defaultValue: T) {
51
+ if (store[name] === undefined) setStore(name, defaultValue)
52
+ return [
53
+ function () {
54
+ return result.get(name)
55
+ },
56
+ function setter(next: Setter<T>) {
57
+ result.set(name, next)
58
+ },
59
+ ] as const
60
+ },
61
+ get(key: string, defaultValue?: any) {
62
+ return store[key] ?? defaultValue
63
+ },
64
+ set(key: string, value: any) {
65
+ setStore(key, value)
66
+ const snapshot = structuredClone(unwrap(store))
67
+ write = write
68
+ .then(() => Flock.withLock(lock, () => writeSnapshot(snapshot)))
69
+ .catch((error) => {
70
+ console.error("Failed to write KV state", { filePath, error })
71
+ })
72
+ },
73
+ }
74
+ return result
75
+ },
76
+ })