saeeol 1.0.6 → 1.0.8
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 +1 -1
- package/script/build.ts +107 -6
- package/specs/effect/http-api.md +17 -17
- package/src/cli/cmd/providers.ts +75 -1
- package/src/cli/cmd/tui/app-events.ts +32 -0
- package/src/cli/cmd/tui/component/prompt/PromptStatusBar.tsx +15 -6
- package/src/cli/cmd/tui/component/prompt/use-prompt-memos.ts +9 -3
- package/src/index.ts +14 -109
- package/src/provider/bundled-providers.ts +28 -27
- package/src/provider/provider-events.ts +29 -0
- package/src/provider/provider-resolve.ts +23 -3
- package/src/provider/tiers/code.ts +20 -0
- package/src/provider/tiers/light.ts +10 -0
- package/src/provider/tiers/master.ts +17 -0
- package/src/saeeol/plugins/sidebar-usage.tsx +25 -0
- package/src/saeeol/provider/provider.ts +2 -12
- package/src/saeeol/session/compaction-chunks-utils.ts +53 -2
- package/src/server/routes/instance/event.ts +2 -0
- package/src/server/routes/instance/httpapi/api.ts +2 -0
- package/src/server/routes/ui.ts +0 -1
- package/src/session/compaction.ts +30 -6
- package/src/session/prompt/anthropic.txt +4 -4
- package/src/session/prompt/default.txt +4 -4
- package/src/session/prompt/ling.txt +5 -5
- package/src/tool/package.ts +168 -0
- package/src/tool/registry.ts +4 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +30 -30
- package/test/fixture/skills/cloudflare/SKILL.md +66 -66
- package/test/saeeol/compaction-smart-select.test.ts +100 -0
- package/src/cli/cmd/tui/context/theme/opencode.json +0 -245
package/package.json
CHANGED
package/script/build.ts
CHANGED
|
@@ -55,7 +55,99 @@ const baselineFlag = process.argv.includes("--baseline")
|
|
|
55
55
|
const skipInstall = process.argv.includes("--skip-install")
|
|
56
56
|
const sourcemapsFlag = process.argv.includes("--sourcemaps")
|
|
57
57
|
const plugin = createSolidTransformPlugin()
|
|
58
|
-
|
|
58
|
+
|
|
59
|
+
// ══════════════════════════════════════════════════════════════════
|
|
60
|
+
// Tier-based tree-sitter WASM selection
|
|
61
|
+
//
|
|
62
|
+
// LIGHT = 5 langs (markdown, json, yaml, toml, tsx) ~8 MB
|
|
63
|
+
// CODE = 15 langs (+ python, rust, go, c, cpp...) ~18 MB
|
|
64
|
+
// MASTER = all 37 langs ~50 MB
|
|
65
|
+
// ══════════════════════════════════════════════════════════════════
|
|
66
|
+
|
|
67
|
+
// ══════════════════════════════════════════════════════════════════
|
|
68
|
+
// Tier-based external packages
|
|
69
|
+
// These packages are excluded from the bundle entirely
|
|
70
|
+
// ══════════════════════════════════════════════════════════════════
|
|
71
|
+
|
|
72
|
+
const allProviderPkgs = [
|
|
73
|
+
"@ai-sdk/amazon-bedrock",
|
|
74
|
+
"@ai-sdk/anthropic",
|
|
75
|
+
"@ai-sdk/azure",
|
|
76
|
+
"@ai-sdk/google",
|
|
77
|
+
"@ai-sdk/google-vertex",
|
|
78
|
+
"@ai-sdk/openai",
|
|
79
|
+
"@ai-sdk/openai-compatible",
|
|
80
|
+
"@openrouter/ai-sdk-provider",
|
|
81
|
+
"@ai-sdk/xai",
|
|
82
|
+
"@ai-sdk/mistral",
|
|
83
|
+
"@ai-sdk/groq",
|
|
84
|
+
"@ai-sdk/deepinfra",
|
|
85
|
+
"@ai-sdk/cerebras",
|
|
86
|
+
"@ai-sdk/cohere",
|
|
87
|
+
"@ai-sdk/gateway",
|
|
88
|
+
"@ai-sdk/togetherai",
|
|
89
|
+
"@ai-sdk/perplexity",
|
|
90
|
+
"@ai-sdk/vercel",
|
|
91
|
+
"@ai-sdk/alibaba",
|
|
92
|
+
"gitlab-ai-provider",
|
|
93
|
+
"venice-ai-sdk-provider",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const lightRequired = new Set([
|
|
97
|
+
"@ai-sdk/anthropic",
|
|
98
|
+
"@ai-sdk/openai",
|
|
99
|
+
"@ai-sdk/openai-compatible",
|
|
100
|
+
"@ai-sdk/google",
|
|
101
|
+
"@saeeol/gateway",
|
|
102
|
+
])
|
|
103
|
+
|
|
104
|
+
const codeRequired = new Set([
|
|
105
|
+
...lightRequired,
|
|
106
|
+
"@ai-sdk/amazon-bedrock",
|
|
107
|
+
"@ai-sdk/azure",
|
|
108
|
+
"@ai-sdk/google-vertex",
|
|
109
|
+
"@openrouter/ai-sdk-provider",
|
|
110
|
+
"@ai-sdk/groq",
|
|
111
|
+
"@ai-sdk/deepinfra",
|
|
112
|
+
"@ai-sdk/gateway",
|
|
113
|
+
"@ai-sdk/alibaba",
|
|
114
|
+
"@ai-sdk/cerebras",
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
function tierExternals(tier: string): string[] {
|
|
118
|
+
if (tier === "master") return []
|
|
119
|
+
const required = tier === "light" ? lightRequired : codeRequired
|
|
120
|
+
return allProviderPkgs.filter((p) => !required.has(p))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const tierArg = process.argv.find((a) => a.startsWith("--tier="))?.split("=")[1] ?? "master"
|
|
124
|
+
|
|
125
|
+
const treeSitterLanguages: Record<string, string[]> = {
|
|
126
|
+
light: [
|
|
127
|
+
"markdown", "markdown_inline",
|
|
128
|
+
"json",
|
|
129
|
+
"yaml",
|
|
130
|
+
"toml",
|
|
131
|
+
"tsx", "typescript",
|
|
132
|
+
],
|
|
133
|
+
code: [
|
|
134
|
+
"markdown", "markdown_inline",
|
|
135
|
+
"json",
|
|
136
|
+
"yaml",
|
|
137
|
+
"toml",
|
|
138
|
+
"tsx", "typescript",
|
|
139
|
+
"python",
|
|
140
|
+
"rust",
|
|
141
|
+
"go",
|
|
142
|
+
"c", "cpp",
|
|
143
|
+
"java",
|
|
144
|
+
"javascript",
|
|
145
|
+
"bash",
|
|
146
|
+
],
|
|
147
|
+
master: [], // empty = all
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function copyTreeSitterWasms(outputDir: string, tier: string) {
|
|
59
151
|
const runtimeWasmPath = require.resolve("web-tree-sitter/tree-sitter.wasm")
|
|
60
152
|
const languagePackagePath = require.resolve("tree-sitter-wasms/package.json")
|
|
61
153
|
const languageWasmDir = path.join(path.dirname(languagePackagePath), "out")
|
|
@@ -64,13 +156,21 @@ async function copyTreeSitterWasms(outputDir: string) {
|
|
|
64
156
|
await fs.promises.mkdir(targetDir, { recursive: true })
|
|
65
157
|
await fs.promises.copyFile(runtimeWasmPath, path.join(targetDir, "tree-sitter.wasm"))
|
|
66
158
|
|
|
67
|
-
const
|
|
159
|
+
const allWasmFiles = (await fs.promises.readdir(languageWasmDir)).filter((file) => file.endsWith(".wasm"))
|
|
160
|
+
const allowed = treeSitterLanguages[tier]
|
|
161
|
+
|
|
162
|
+
const filesToCopy = allowed.length === 0
|
|
163
|
+
? allWasmFiles
|
|
164
|
+
: allWasmFiles.filter((file) => {
|
|
165
|
+
const langName = file.replace("tree-sitter-", "").replace(".wasm", "")
|
|
166
|
+
return allowed.some((a) => langName === a || langName.startsWith(a + "-"))
|
|
167
|
+
})
|
|
68
168
|
|
|
69
169
|
await Promise.all(
|
|
70
|
-
|
|
170
|
+
filesToCopy.map((file) => fs.promises.copyFile(path.join(languageWasmDir, file), path.join(targetDir, file))),
|
|
71
171
|
)
|
|
72
172
|
|
|
73
|
-
console.log(`copied ${
|
|
173
|
+
console.log(`copied ${filesToCopy.length + 1} tree-sitter wasm files to ${targetDir} (tier=${tier})`)
|
|
74
174
|
}
|
|
75
175
|
|
|
76
176
|
const allTargets: {
|
|
@@ -192,7 +292,7 @@ for (const item of targets) {
|
|
|
192
292
|
tsconfig: "./tsconfig.json",
|
|
193
293
|
plugins: [plugin],
|
|
194
294
|
sourcemap: Script.release ? "none" : "external",
|
|
195
|
-
external: ["node-gyp", ...LanceDBRuntime.external],
|
|
295
|
+
external: ["node-gyp", ...LanceDBRuntime.external, ...tierExternals(tierArg)],
|
|
196
296
|
format: "esm",
|
|
197
297
|
minify: true,
|
|
198
298
|
splitting: true,
|
|
@@ -216,10 +316,11 @@ for (const item of targets) {
|
|
|
216
316
|
SAEEOL_CHANNEL: `'${Script.channel}'`,
|
|
217
317
|
SAEEOL_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
|
|
218
318
|
SAEEOL_BUILD_KIND: Script.release ? `'release'` : `'source'`,
|
|
319
|
+
SAEEOL_TIER: `'${tierArg}'`,
|
|
219
320
|
},
|
|
220
321
|
})
|
|
221
322
|
|
|
222
|
-
await copyTreeSitterWasms(path.resolve(dir, `dist/${name}/bin`))
|
|
323
|
+
await copyTreeSitterWasms(path.resolve(dir, `dist/${name}/bin`), tierArg)
|
|
223
324
|
if (item.os === "linux") {
|
|
224
325
|
const interpreters: Record<string, string> = {
|
|
225
326
|
x64: "/lib64/ld-linux-x86-64.so.2",
|
package/specs/effect/http-api.md
CHANGED
|
@@ -189,23 +189,23 @@ Use raw Effect HTTP routes where `HttpApi` does not fit. The goal is deleting Ho
|
|
|
189
189
|
|
|
190
190
|
## Current Route Status
|
|
191
191
|
|
|
192
|
-
| Area
|
|
193
|
-
|
|
194
|
-
| `question`
|
|
195
|
-
| `permission`
|
|
196
|
-
| `provider`
|
|
197
|
-
| `config`
|
|
198
|
-
| `project`
|
|
199
|
-
| `file`
|
|
200
|
-
| `mcp`
|
|
201
|
-
| `workspace`
|
|
202
|
-
| top-level instance routes | `bridged`
|
|
203
|
-
| experimental JSON routes
|
|
204
|
-
| `session`
|
|
205
|
-
| `sync`
|
|
206
|
-
| `event`
|
|
207
|
-
| `pty`
|
|
208
|
-
| `tui`
|
|
192
|
+
| Area | Status | Notes |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `question` | `bridged` | `GET /question`, reply, reject |
|
|
195
|
+
| `permission` | `bridged` | list and reply |
|
|
196
|
+
| `provider` | `bridged` | list, auth, OAuth authorize/callback |
|
|
197
|
+
| `config` | `bridged` | read, providers, update |
|
|
198
|
+
| `project` | `bridged` | list, current, git init, update |
|
|
199
|
+
| `file` | `bridged` partial | find text/file/symbol, list/content/status |
|
|
200
|
+
| `mcp` | `bridged` | status, add, OAuth, connect/disconnect |
|
|
201
|
+
| `workspace` | `bridged` | adapter/list/status/create/remove/session-restore |
|
|
202
|
+
| top-level instance routes | `bridged` | path, vcs, command, agent, skill, lsp, formatter, dispose |
|
|
203
|
+
| experimental JSON routes | `bridged` | console, tool, worktree list/mutations, global session list, resource list |
|
|
204
|
+
| `session` | `bridged` | read, lifecycle, prompt, message/part mutations, revert, permission reply |
|
|
205
|
+
| `sync` | `bridged` | start/replay/history |
|
|
206
|
+
| `event` | `bridged` | SSE via raw Effect HTTP |
|
|
207
|
+
| `pty` | `special` | websocket |
|
|
208
|
+
| `tui` | `special` | UI bridge |
|
|
209
209
|
|
|
210
210
|
## Full Route Checklist
|
|
211
211
|
|
package/src/cli/cmd/providers.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Instance } from "../../project/instance"
|
|
|
14
14
|
import { Process } from "@/util/process"
|
|
15
15
|
import { text } from "node:stream/consumers"
|
|
16
16
|
import { Effect } from "effect"
|
|
17
|
+
import { Npm } from "@saeeol/core/npm"
|
|
17
18
|
import { put, handlePluginAuth, resolvePluginProviders } from "./providers-auth"
|
|
18
19
|
|
|
19
20
|
const getModels = () => AppRuntime.runPromise(ModelsDev.Service.use((s) => s.get()))
|
|
@@ -24,7 +25,12 @@ export const ProvidersCommand = cmd({
|
|
|
24
25
|
aliases: ["providers"],
|
|
25
26
|
describe: "manage AI providers and credentials",
|
|
26
27
|
builder: (yargs) =>
|
|
27
|
-
yargs
|
|
28
|
+
yargs
|
|
29
|
+
.command(ProvidersListCommand)
|
|
30
|
+
.command(ProvidersLoginCommand)
|
|
31
|
+
.command(ProvidersLogoutCommand)
|
|
32
|
+
.command(ProvidersInstallCommand)
|
|
33
|
+
.demandCommand(),
|
|
28
34
|
async handler() {},
|
|
29
35
|
})
|
|
30
36
|
|
|
@@ -226,3 +232,71 @@ export const ProvidersLogoutCommand = cmd({
|
|
|
226
232
|
})
|
|
227
233
|
},
|
|
228
234
|
})
|
|
235
|
+
|
|
236
|
+
export const ProvidersInstallCommand = cmd({
|
|
237
|
+
command: "install [providers..]",
|
|
238
|
+
describe: "pre-install provider SDK packages (skip runtime download delay)",
|
|
239
|
+
builder: (yargs) =>
|
|
240
|
+
yargs.positional("providers", {
|
|
241
|
+
describe: "provider id(s) to install (e.g. anthropic openai google)",
|
|
242
|
+
type: "string",
|
|
243
|
+
array: true,
|
|
244
|
+
}),
|
|
245
|
+
async handler(args) {
|
|
246
|
+
await Instance.provide({
|
|
247
|
+
directory: process.cwd(),
|
|
248
|
+
async fn() {
|
|
249
|
+
UI.empty()
|
|
250
|
+
prompts.intro("Install provider SDKs")
|
|
251
|
+
|
|
252
|
+
await refreshModels().catch(() => {})
|
|
253
|
+
const database = await getModels()
|
|
254
|
+
|
|
255
|
+
// Build npm package → provider names mapping from models.dev data
|
|
256
|
+
const npmToNames: Record<string, Set<string>> = {}
|
|
257
|
+
for (const [id, info] of Object.entries(database)) {
|
|
258
|
+
const pkg = info.npm
|
|
259
|
+
if (!pkg) continue
|
|
260
|
+
if (!npmToNames[pkg]) npmToNames[pkg] = new Set()
|
|
261
|
+
npmToNames[pkg].add(info.name || id)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Resolve target packages
|
|
265
|
+
const targets = args.providers?.length
|
|
266
|
+
? args.providers
|
|
267
|
+
: await (async () => {
|
|
268
|
+
const options = Object.entries(npmToNames).map(([pkg, names]) => ({
|
|
269
|
+
label: [...names].join(", "),
|
|
270
|
+
value: pkg,
|
|
271
|
+
hint: pkg,
|
|
272
|
+
}))
|
|
273
|
+
const selected = await prompts.multiselect({
|
|
274
|
+
message: "Select providers to install",
|
|
275
|
+
options,
|
|
276
|
+
required: true,
|
|
277
|
+
})
|
|
278
|
+
if (prompts.isCancel(selected)) throw new UI.CancelledError()
|
|
279
|
+
return selected as string[]
|
|
280
|
+
})()
|
|
281
|
+
|
|
282
|
+
for (const target of targets) {
|
|
283
|
+
// If user typed a provider id (like "anthropic"), resolve to npm package
|
|
284
|
+
const pkg = npmToNames[target] ? target
|
|
285
|
+
: Object.entries(database).find(([id]) => id === target)?.[1]?.npm
|
|
286
|
+
?? target
|
|
287
|
+
|
|
288
|
+
const spinner = prompts.spinner()
|
|
289
|
+
spinner.start(`Installing ${pkg}`)
|
|
290
|
+
try {
|
|
291
|
+
await Npm.add(pkg)
|
|
292
|
+
spinner.stop(`${pkg} installed`)
|
|
293
|
+
} catch (err) {
|
|
294
|
+
spinner.stop(`${pkg} failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
prompts.outro("Done")
|
|
299
|
+
},
|
|
300
|
+
})
|
|
301
|
+
},
|
|
302
|
+
})
|
|
@@ -4,6 +4,7 @@ import { DialogConfirm } from "@tui/ui/dialog-confirm"
|
|
|
4
4
|
import { DialogAlert } from "@tui/ui/dialog-alert"
|
|
5
5
|
import { errorMessage } from "@tui/app-config"
|
|
6
6
|
import * as SaeeolApp from "@/saeeol/cli/cmd/tui/app"
|
|
7
|
+
import { ProviderInstallEvent } from "@/provider/provider-events"
|
|
7
8
|
|
|
8
9
|
type Event = ReturnType<typeof import("@tui/context/event").useEvent>
|
|
9
10
|
type Command = ReturnType<typeof import("@tui/component/dialog-command").useCommandDialog>
|
|
@@ -101,4 +102,35 @@ export function registerAppEvents(deps: EventDeps) {
|
|
|
101
102
|
)
|
|
102
103
|
void exit()
|
|
103
104
|
})
|
|
105
|
+
|
|
106
|
+
// Provider SDK 동적 설치 진행 상태 표시
|
|
107
|
+
// subscribe() 사용 + 타입 단언 — SDK Event 유니온에 아직 타입이 없음
|
|
108
|
+
// SDK 재생성(provider-events 레지스트리 포함) 후 타입 안전하게 전환 가능
|
|
109
|
+
event.subscribe((raw: any) => {
|
|
110
|
+
const evt = raw as { type: string; properties: { providerID: string; pkg: string; error?: string } }
|
|
111
|
+
if (evt.type === ProviderInstallEvent.Started.type) {
|
|
112
|
+
toast.show({
|
|
113
|
+
title: "Installing Provider",
|
|
114
|
+
message: `Downloading ${evt.properties.pkg} for ${evt.properties.providerID}...`,
|
|
115
|
+
variant: "info",
|
|
116
|
+
duration: 60000,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
if (evt.type === ProviderInstallEvent.Completed.type) {
|
|
120
|
+
toast.show({
|
|
121
|
+
title: "Provider Ready",
|
|
122
|
+
message: `${evt.properties.providerID} installed successfully`,
|
|
123
|
+
variant: "success",
|
|
124
|
+
duration: 3000,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
if (evt.type === ProviderInstallEvent.Failed.type) {
|
|
128
|
+
toast.show({
|
|
129
|
+
title: "Installation Failed",
|
|
130
|
+
message: `Failed to install ${evt.properties.pkg}: ${evt.properties.error}`,
|
|
131
|
+
variant: "error",
|
|
132
|
+
duration: 10000,
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
})
|
|
104
136
|
}
|
|
@@ -21,7 +21,7 @@ export type PromptStatusBarProps = {
|
|
|
21
21
|
exitPress: number
|
|
22
22
|
}
|
|
23
23
|
hint: any
|
|
24
|
-
usage: () => { context: string; cost?: string } | undefined
|
|
24
|
+
usage: () => { context: string; cost?: string; health: string; remaining?: number; pct: number } | undefined
|
|
25
25
|
borderHighlight: () => any
|
|
26
26
|
spinnerDef: () => { frames: any; color: any }
|
|
27
27
|
editorFileLabelDisplay: () => string | undefined
|
|
@@ -128,11 +128,20 @@ export function PromptStatusBar(props: PromptStatusBarProps) {
|
|
|
128
128
|
<Match when={props.store.mode === "normal"}>
|
|
129
129
|
<Switch>
|
|
130
130
|
<Match when={props.usage()}>
|
|
131
|
-
{(item) =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
{(item) => {
|
|
132
|
+
const u = item()
|
|
133
|
+
const color = u.health === "critical"
|
|
134
|
+
? theme.error
|
|
135
|
+
: u.health === "warning"
|
|
136
|
+
? theme.warning
|
|
137
|
+
: theme.textMuted
|
|
138
|
+
const parts = [u.context, u.cost].filter(Boolean)
|
|
139
|
+
return (
|
|
140
|
+
<text fg={color} wrapMode="none">
|
|
141
|
+
{parts.join(" · ")}
|
|
142
|
+
</text>
|
|
143
|
+
)
|
|
144
|
+
}}
|
|
136
145
|
</Match>
|
|
137
146
|
<Match when={true}>
|
|
138
147
|
<text fg={theme.text}>
|
|
@@ -47,7 +47,7 @@ export function formatEditorContext(selection: EditorSelection) {
|
|
|
47
47
|
|
|
48
48
|
export type PromptMemos = {
|
|
49
49
|
status: () => { type: string }
|
|
50
|
-
usage: () => { context: string; cost?: string } | undefined
|
|
50
|
+
usage: () => { context: string; cost?: string; health: string; remaining?: number; pct: number } | undefined
|
|
51
51
|
currentProviderLabel: () => string
|
|
52
52
|
hasRightContent: () => boolean
|
|
53
53
|
editorContext: () => EditorSelection | undefined
|
|
@@ -143,11 +143,17 @@ export function usePromptMemos(deps: MemoDeps): PromptMemos {
|
|
|
143
143
|
const tokens = last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
|
|
144
144
|
if (tokens <= 0) return
|
|
145
145
|
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
|
|
146
|
-
const
|
|
146
|
+
const contextLimit = model?.limit.context ?? 0
|
|
147
|
+
const pct = contextLimit ? Math.round((tokens / contextLimit) * 100) : 0
|
|
147
148
|
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
|
|
149
|
+
const health = !contextLimit ? "ok" : pct >= 90 ? "critical" : pct >= 75 ? "warning" : "ok"
|
|
150
|
+
const remaining = contextLimit ? Math.max(0, contextLimit - tokens) : undefined
|
|
148
151
|
return {
|
|
149
|
-
context:
|
|
152
|
+
context: contextLimit ? `${Locale.number(tokens)} (${pct}%)` : Locale.number(tokens),
|
|
150
153
|
cost: cost > 0 ? money.format(cost) : undefined,
|
|
154
|
+
health,
|
|
155
|
+
remaining,
|
|
156
|
+
pct,
|
|
151
157
|
}
|
|
152
158
|
})
|
|
153
159
|
|
package/src/index.ts
CHANGED
|
@@ -11,52 +11,10 @@ import { hideBin } from "yargs/helpers"
|
|
|
11
11
|
// ║ MASTER = CODE + web + remote + analytics ║
|
|
12
12
|
// ╚══════════════════════════════════════════════════════════════════╝
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
import { RunCommand } from "./cli/cmd/run"
|
|
17
|
-
import { GenerateCommand } from "./cli/cmd/generate"
|
|
18
|
-
import { SessionCommand } from "./cli/cmd/session"
|
|
19
|
-
|
|
20
|
-
// ── LIGHT: llm ─────────────────────────────────────────────────────
|
|
21
|
-
import { InitCommand } from "./cli/cmd/init"
|
|
22
|
-
import { ProvidersCommand } from "./cli/cmd/providers"
|
|
23
|
-
import { ModelsCommand } from "./cli/cmd/models"
|
|
24
|
-
import { ConfigCommand as ConfigCLICommand } from "./cli/cmd/config"
|
|
25
|
-
|
|
26
|
-
// ── LIGHT: lifecycle ───────────────────────────────────────────────
|
|
27
|
-
import { UpgradeCommand } from "./cli/cmd/upgrade"
|
|
28
|
-
import { UninstallCommand } from "./cli/cmd/uninstall"
|
|
29
|
-
|
|
30
|
-
// ── CODE: server ───────────────────────────────────────────────────
|
|
31
|
-
import { ServeCommand } from "./cli/cmd/serve"
|
|
32
|
-
import { AttachCommand } from "./cli/cmd/tui/attach"
|
|
33
|
-
import { AcpCommand } from "./cli/cmd/acp"
|
|
34
|
-
|
|
35
|
-
// ── CODE: tools ────────────────────────────────────────────────────
|
|
36
|
-
import { McpCommand } from "./cli/cmd/mcp"
|
|
37
|
-
import { PluginCommand } from "./cli/cmd/plug"
|
|
38
|
-
|
|
39
|
-
// ── CODE: data ─────────────────────────────────────────────────────
|
|
40
|
-
import { ExportCommand } from "./cli/cmd/export"
|
|
41
|
-
import { ImportCommand } from "./cli/cmd/import"
|
|
42
|
-
import { DbCommand } from "./cli/cmd/db"
|
|
43
|
-
|
|
44
|
-
// ── CODE: dev (선택) ───────────────────────────────────────────────
|
|
45
|
-
import { AgentCommand } from "./cli/cmd/agent"
|
|
46
|
-
import { DebugCommand } from "./cli/cmd/debug"
|
|
47
|
-
|
|
48
|
-
// ── MASTER: web ────────────────────────────────────────────────────
|
|
49
|
-
import { WebCommand } from "./cli/cmd/web"
|
|
50
|
-
|
|
51
|
-
// ── MASTER: remote (선택) ──────────────────────────────────────────
|
|
52
|
-
import { RemoteCommand } from "./cli/cmd/remote"
|
|
53
|
-
|
|
54
|
-
// ── MASTER: analytics (선택) ───────────────────────────────────────
|
|
55
|
-
import { StatsCommand } from "./cli/cmd/stats"
|
|
56
|
-
import { PrCommand } from "./cli/cmd/pr"
|
|
57
|
-
import { RollCallCommand } from "./saeeol/cli/cmd/roll-call"
|
|
14
|
+
import type { Tier } from "./addons/types"
|
|
15
|
+
import { commandsForTier, manifest } from "./addons/registry"
|
|
58
16
|
|
|
59
|
-
// ── DEV 전용
|
|
17
|
+
// ── DEV 전용 (항상 포함) ────────────────────────────────────────────
|
|
60
18
|
import { DevSetupCommand, DevAliasCommand } from "./saeeol/cli/dev-setup"
|
|
61
19
|
|
|
62
20
|
// ── 공통 ────────────────────────────────────────────────────────────
|
|
@@ -82,8 +40,7 @@ import { errorMessage } from "./util/error"
|
|
|
82
40
|
import { Heap } from "./cli/heap"
|
|
83
41
|
import { drizzle } from "drizzle-orm/bun-sqlite"
|
|
84
42
|
import { ensureProcessMetadata } from "@saeeol/core/util/saeeol-process"
|
|
85
|
-
|
|
86
|
-
import { addonsForTier, manifest } from "./addons/registry"
|
|
43
|
+
|
|
87
44
|
|
|
88
45
|
if (!process.env[ENV_FEATURE]) {
|
|
89
46
|
const isServe = process.argv.includes("serve")
|
|
@@ -235,70 +192,18 @@ let cli = yargs(args)
|
|
|
235
192
|
.usage("")
|
|
236
193
|
.completion("completion", "generate shell completion script")
|
|
237
194
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
.command(RunCommand) // run 프롬프트 실행
|
|
243
|
-
.command(GenerateCommand) // generate 코드 생성
|
|
244
|
-
.command(SessionCommand) // session 세션 관리
|
|
245
|
-
|
|
246
|
-
// ════════════════════════════════════════════════════════════════
|
|
247
|
-
// LIGHT: llm (provider + 모델 + 설정)
|
|
248
|
-
// ════════════════════════════════════════════════════════════════
|
|
249
|
-
.command(InitCommand) // init 최초 설정
|
|
250
|
-
.command(ProvidersCommand) // auth provider 인증
|
|
251
|
-
.command(ModelsCommand) // models 모델 목록
|
|
252
|
-
.command(ConfigCLICommand) // config 설정 관리
|
|
253
|
-
|
|
254
|
-
// ════════════════════════════════════════════════════════════════
|
|
255
|
-
// LIGHT: lifecycle (설치/업그레이드/제거)
|
|
256
|
-
// ════════════════════════════════════════════════════════════════
|
|
257
|
-
.command(UpgradeCommand) // upgrade 버전 업그레이드
|
|
258
|
-
.command(UninstallCommand) // uninstall 제거
|
|
259
|
-
|
|
260
|
-
// ════════════════════════════════════════════════════════════════
|
|
261
|
-
// CODE: server (headless + 원격 + ACP)
|
|
262
|
-
// ════════════════════════════════════════════════════════════════
|
|
263
|
-
.command(ServeCommand) // serve headless 서버
|
|
264
|
-
.command(AttachCommand) // attach 원격 연결
|
|
265
|
-
.command(AcpCommand) // acp ACP 서버
|
|
195
|
+
// ════════════════════════════════════════════════════════════════
|
|
196
|
+
// 애드온 기반 명령어 등록
|
|
197
|
+
// SAEEOL_TIER define에 따라 활성 애드온이 결정됨
|
|
198
|
+
// ════════════════════════════════════════════════════════════════
|
|
266
199
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
.command(
|
|
272
|
-
|
|
273
|
-
// ════════════════════════════════════════════════════════════════
|
|
274
|
-
// CODE: data (export + import + db)
|
|
275
|
-
// ════════════════════════════════════════════════════════════════
|
|
276
|
-
.command(ExportCommand) // export 세션 내보내기
|
|
277
|
-
.command(ImportCommand) // import 세션 가져오기
|
|
278
|
-
.command(DbCommand) // db 데이터베이스
|
|
279
|
-
|
|
280
|
-
// ════════════════════════════════════════════════════════════════
|
|
281
|
-
// CODE: dev (선택)
|
|
282
|
-
// ════════════════════════════════════════════════════════════════
|
|
283
|
-
.command(AgentCommand) // agent 에이전트 관리
|
|
284
|
-
.command(DebugCommand) // debug 디버깅
|
|
285
|
-
|
|
286
|
-
// ════════════════════════════════════════════════════════════════
|
|
287
|
-
// MASTER: web (브라우저 채팅 UI)
|
|
288
|
-
// ════════════════════════════════════════════════════════════════
|
|
289
|
-
.command(WebCommand) // web 웹 UI + 브라우저
|
|
290
|
-
|
|
291
|
-
// ════════════════════════════════════════════════════════════════
|
|
292
|
-
// MASTER: remote (선택)
|
|
293
|
-
// ════════════════════════════════════════════════════════════════
|
|
294
|
-
.command(RemoteCommand) // remote 실시간 릴레이
|
|
200
|
+
declare const SAEEOL_TIER: string
|
|
201
|
+
const tier = (typeof SAEEOL_TIER === "string" ? SAEEOL_TIER : "master") as Tier
|
|
202
|
+
const addonCommands = await commandsForTier(tier)
|
|
203
|
+
for (const cmd of addonCommands) {
|
|
204
|
+
cli = cli.command(cmd)
|
|
205
|
+
}
|
|
295
206
|
|
|
296
|
-
// ════════════════════════════════════════════════════════════════
|
|
297
|
-
// MASTER: analytics (선택)
|
|
298
|
-
// ════════════════════════════════════════════════════════════════
|
|
299
|
-
.command(StatsCommand) // stats 사용량 통계
|
|
300
|
-
.command(PrCommand) // pr GitHub PR
|
|
301
|
-
.command(RollCallCommand) // roll-call 모델 연결 테스트
|
|
302
207
|
if (InstallationBuildKind !== "release") {
|
|
303
208
|
cli = cli.command(DevSetupCommand).command(DevAliasCommand)
|
|
304
209
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SAEEOL_BUNDLED_PROVIDERS } from "@/saeeol/provider/provider"
|
|
2
1
|
import type { BundledSDK } from "./provider-types"
|
|
3
2
|
|
|
4
3
|
export function shouldUseCopilotResponsesApi(modelID: string): boolean {
|
|
@@ -11,30 +10,32 @@ export function useLanguageModel(sdk: any) {
|
|
|
11
10
|
return sdk.responses === undefined && sdk.chat === undefined
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
13
|
+
// ╔══════════════════════════════════════════════════════════════════╗
|
|
14
|
+
// ║ 티어별 provider 로딩 ║
|
|
15
|
+
// ║ ║
|
|
16
|
+
// ║ SAEEOL_TIER는 build.ts define으로 "light"|"code"|"master" 주입 ║
|
|
17
|
+
// ║ Bun은 dead branch를 제거하므로 다른 티어의 import()가 제외됨 ║
|
|
18
|
+
// ╚══════════════════════════════════════════════════════════════════╝
|
|
19
|
+
|
|
20
|
+
declare const SAEEOL_TIER: string
|
|
21
|
+
|
|
22
|
+
type ProviderLoader = () => Promise<(opts: any) => BundledSDK>
|
|
23
|
+
|
|
24
|
+
// 티어별 파일에서 각각 서로 다른 provider 집합을 import
|
|
25
|
+
// Bun은 조건에 맞지 않는 branch의 import()를 dead code로 처리
|
|
26
|
+
|
|
27
|
+
function getProviders(): Record<string, ProviderLoader> {
|
|
28
|
+
const tier = typeof SAEEOL_TIER === "string" ? SAEEOL_TIER : "master"
|
|
29
|
+
if (tier === "light") {
|
|
30
|
+
const m = require("./tiers/light") as typeof import("./tiers/light")
|
|
31
|
+
return m.lightProviders
|
|
32
|
+
}
|
|
33
|
+
if (tier === "code") {
|
|
34
|
+
const m = require("./tiers/code") as typeof import("./tiers/code")
|
|
35
|
+
return m.codeProviders
|
|
36
|
+
}
|
|
37
|
+
const m = require("./tiers/master") as typeof import("./tiers/master")
|
|
38
|
+
return m.masterProviders
|
|
40
39
|
}
|
|
40
|
+
|
|
41
|
+
export const BUNDLED_PROVIDERS: Record<string, ProviderLoader> = getProviders()
|