saeeol 1.0.7 → 1.0.9
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 +30 -30
- 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 +3 -3
- package/src/provider/provider-events.ts +29 -0
- package/src/provider/provider-resolve.ts +23 -3
- package/src/provider/tiers/code.ts +0 -1
- package/src/provider/tiers/light.ts +2 -6
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"name": "saeeol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -61,21 +61,21 @@
|
|
|
61
61
|
"@parcel/watcher-win32-arm64": "2.5.1",
|
|
62
62
|
"@parcel/watcher-win32-x64": "2.5.1",
|
|
63
63
|
"@standard-schema/spec": "1.0.0",
|
|
64
|
-
"@tsconfig/bun": "
|
|
64
|
+
"@tsconfig/bun": "1.0.9",
|
|
65
65
|
"@types/babel__core": "7.20.5",
|
|
66
|
-
"@types/bun": "
|
|
67
|
-
"@types/cross-spawn": "
|
|
66
|
+
"@types/bun": "1.3.12",
|
|
67
|
+
"@types/cross-spawn": "6.0.6",
|
|
68
68
|
"@types/mime-types": "3.0.1",
|
|
69
69
|
"@types/npm-package-arg": "6.1.4",
|
|
70
70
|
"@types/semver": "^7.5.8",
|
|
71
71
|
"@types/turndown": "5.0.5",
|
|
72
72
|
"@types/which": "3.0.4",
|
|
73
73
|
"@types/yargs": "17.0.33",
|
|
74
|
-
"@typescript/native-preview": "
|
|
75
|
-
"drizzle-kit": "
|
|
76
|
-
"drizzle-orm": "
|
|
74
|
+
"@typescript/native-preview": "7.0.0-dev.20260316.1",
|
|
75
|
+
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
|
76
|
+
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
|
77
77
|
"prettier": "3.6.2",
|
|
78
|
-
"typescript": "
|
|
78
|
+
"typescript": "5.8.2",
|
|
79
79
|
"vscode-languageserver-types": "3.17.5",
|
|
80
80
|
"why-is-node-running": "3.2.2",
|
|
81
81
|
"zod-to-json-schema": "3.24.5",
|
|
@@ -107,31 +107,31 @@
|
|
|
107
107
|
"@ai-sdk/xai": "3.0.82",
|
|
108
108
|
"@aws-sdk/credential-providers": "3.1025.0",
|
|
109
109
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
110
|
-
"@effect/opentelemetry": "
|
|
111
|
-
"@effect/platform-node": "
|
|
110
|
+
"@effect/opentelemetry": "4.0.0-beta.57",
|
|
111
|
+
"@effect/platform-node": "4.0.0-beta.57",
|
|
112
112
|
"@gitlab/gitlab-ai-provider": "3.6.0",
|
|
113
113
|
"@hono/node-server": "1.19.13",
|
|
114
114
|
"@hono/node-ws": "1.3.0",
|
|
115
115
|
"@hono/standard-validator": "0.1.5",
|
|
116
|
-
"@hono/zod-validator": "
|
|
117
|
-
"@lydell/node-pty": "
|
|
116
|
+
"@hono/zod-validator": "0.4.2",
|
|
117
|
+
"@lydell/node-pty": "1.2.0-beta.10",
|
|
118
118
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
119
119
|
"@morphllm/morphsdk": "0.2.166",
|
|
120
120
|
"@npmcli/arborist": "9.4.0",
|
|
121
121
|
"@npmcli/config": "10.8.1",
|
|
122
122
|
"@octokit/graphql": "9.0.2",
|
|
123
|
-
"@octokit/rest": "
|
|
124
|
-
"@openauthjs/openauth": "
|
|
123
|
+
"@octokit/rest": "22.0.0",
|
|
124
|
+
"@openauthjs/openauth": "0.0.0-20250322224806",
|
|
125
125
|
"@openrouter/ai-sdk-provider": "2.8.1",
|
|
126
126
|
"@opentelemetry/api": "1.9.0",
|
|
127
127
|
"@opentelemetry/context-async-hooks": "2.6.1",
|
|
128
128
|
"@opentelemetry/exporter-trace-otlp-http": "0.214.0",
|
|
129
129
|
"@opentelemetry/sdk-trace-base": "2.6.1",
|
|
130
130
|
"@opentelemetry/sdk-trace-node": "2.6.1",
|
|
131
|
-
"@opentui/core": "
|
|
132
|
-
"@opentui/solid": "
|
|
131
|
+
"@opentui/core": "0.2.2",
|
|
132
|
+
"@opentui/solid": "0.2.2",
|
|
133
133
|
"@parcel/watcher": "2.5.1",
|
|
134
|
-
"@pierre/diffs": "
|
|
134
|
+
"@pierre/diffs": "1.1.0-beta.18",
|
|
135
135
|
"@saeeol/boxes": "workspace:*",
|
|
136
136
|
"@saeeol/core": "workspace:*",
|
|
137
137
|
"@saeeol/i18n": "workspace:*",
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
"@solid-primitives/scheduled": "1.5.2",
|
|
146
146
|
"@standard-schema/spec": "1.0.0",
|
|
147
147
|
"@zip.js/zip.js": "2.7.62",
|
|
148
|
-
"ai": "
|
|
148
|
+
"ai": "6.0.168",
|
|
149
149
|
"ai-gateway-provider": "3.1.2",
|
|
150
150
|
"bonjour-service": "1.3.0",
|
|
151
151
|
"bun-pty": "0.4.8",
|
|
@@ -153,19 +153,19 @@
|
|
|
153
153
|
"chokidar": "4.0.3",
|
|
154
154
|
"cli-sound": "1.1.3",
|
|
155
155
|
"clipboardy": "4.0.0",
|
|
156
|
-
"cross-spawn": "
|
|
156
|
+
"cross-spawn": "7.0.6",
|
|
157
157
|
"decimal.js": "10.5.0",
|
|
158
|
-
"diff": "
|
|
159
|
-
"drizzle-orm": "
|
|
160
|
-
"effect": "
|
|
158
|
+
"diff": "8.0.4",
|
|
159
|
+
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
|
160
|
+
"effect": "4.0.0-beta.57",
|
|
161
161
|
"fuzzysort": "3.1.0",
|
|
162
162
|
"gensync": "1.0.0-beta.2",
|
|
163
163
|
"gitlab-ai-provider": "6.6.0",
|
|
164
164
|
"glob": "13.0.5",
|
|
165
165
|
"google-auth-library": "10.5.0",
|
|
166
166
|
"gray-matter": "4.0.3",
|
|
167
|
-
"hono": "
|
|
168
|
-
"hono-openapi": "
|
|
167
|
+
"hono": "4.12.12",
|
|
168
|
+
"hono-openapi": "1.1.2",
|
|
169
169
|
"iconv-lite": "0.7.2",
|
|
170
170
|
"ignore": "7.0.5",
|
|
171
171
|
"immer": "11.1.4",
|
|
@@ -174,33 +174,33 @@
|
|
|
174
174
|
"minimatch": "10.2.5",
|
|
175
175
|
"npm-package-arg": "13.0.2",
|
|
176
176
|
"open": "10.1.2",
|
|
177
|
-
"opentui-spinner": "
|
|
177
|
+
"opentui-spinner": "0.0.6",
|
|
178
178
|
"partial-json": "0.1.7",
|
|
179
|
-
"remeda": "
|
|
179
|
+
"remeda": "2.26.0",
|
|
180
180
|
"ripgrep": "0.3.1",
|
|
181
181
|
"rotating-file-stream": "3.2.9",
|
|
182
182
|
"saeeol-gitlab-auth": "workspace:*",
|
|
183
183
|
"saeeol-poe-auth": "workspace:*",
|
|
184
184
|
"semver": "^7.6.3",
|
|
185
185
|
"simple-git": "3.36.0",
|
|
186
|
-
"solid-js": "
|
|
186
|
+
"solid-js": "1.9.12",
|
|
187
187
|
"strip-ansi": "7.1.2",
|
|
188
188
|
"tree-sitter-bash": "0.25.0",
|
|
189
189
|
"tree-sitter-powershell": "0.25.10",
|
|
190
190
|
"tree-sitter-wasms": "^0.1.12",
|
|
191
191
|
"turndown": "7.2.0",
|
|
192
|
-
"ulid": "
|
|
192
|
+
"ulid": "3.0.1",
|
|
193
193
|
"venice-ai-sdk-provider": "2.0.1",
|
|
194
194
|
"vscode-jsonrpc": "8.2.1",
|
|
195
195
|
"web-tree-sitter": "0.25.10",
|
|
196
196
|
"which": "6.0.1",
|
|
197
197
|
"xdg-basedir": "5.1.0",
|
|
198
198
|
"yargs": "18.0.0",
|
|
199
|
-
"zod": "
|
|
199
|
+
"zod": "4.1.8",
|
|
200
200
|
"zod-to-json-schema": "3.24.5"
|
|
201
201
|
},
|
|
202
202
|
"overrides": {
|
|
203
|
-
"drizzle-orm": "
|
|
203
|
+
"drizzle-orm": "1.0.0-beta.19-d95b7a4"
|
|
204
204
|
},
|
|
205
205
|
"peerDependencies": {}
|
|
206
206
|
}
|
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
|
}
|
|
@@ -25,12 +25,12 @@ type ProviderLoader = () => Promise<(opts: any) => BundledSDK>
|
|
|
25
25
|
// Bun은 조건에 맞지 않는 branch의 import()를 dead code로 처리
|
|
26
26
|
|
|
27
27
|
function getProviders(): Record<string, ProviderLoader> {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const tier = typeof SAEEOL_TIER === "string" ? SAEEOL_TIER : "master"
|
|
29
|
+
if (tier === "light") {
|
|
30
30
|
const m = require("./tiers/light") as typeof import("./tiers/light")
|
|
31
31
|
return m.lightProviders
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (tier === "code") {
|
|
34
34
|
const m = require("./tiers/code") as typeof import("./tiers/code")
|
|
35
35
|
return m.codeProviders
|
|
36
36
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BusEvent } from "@/bus/bus-event"
|
|
2
|
+
import { Schema } from "effect"
|
|
3
|
+
|
|
4
|
+
// Provider SDK 동적 설치 이벤트 — TUI/웹뷰에서 구독하여 설치 진행 상태 표시
|
|
5
|
+
// BusEvent.define 호출로 전역 레지스트리에 자동 등록됨
|
|
6
|
+
export const ProviderInstallEvent = {
|
|
7
|
+
Started: BusEvent.define(
|
|
8
|
+
"provider.install.started",
|
|
9
|
+
Schema.Struct({
|
|
10
|
+
providerID: Schema.String,
|
|
11
|
+
pkg: Schema.String,
|
|
12
|
+
}),
|
|
13
|
+
),
|
|
14
|
+
Completed: BusEvent.define(
|
|
15
|
+
"provider.install.completed",
|
|
16
|
+
Schema.Struct({
|
|
17
|
+
providerID: Schema.String,
|
|
18
|
+
pkg: Schema.String,
|
|
19
|
+
}),
|
|
20
|
+
),
|
|
21
|
+
Failed: BusEvent.define(
|
|
22
|
+
"provider.install.failed",
|
|
23
|
+
Schema.Struct({
|
|
24
|
+
providerID: Schema.String,
|
|
25
|
+
pkg: Schema.String,
|
|
26
|
+
error: Schema.String,
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
}
|
|
@@ -5,7 +5,9 @@ import { NoSuchModelError, type Provider as SDK } from "ai"
|
|
|
5
5
|
import * as Log from "@saeeol/core/util/log"
|
|
6
6
|
import { Npm } from "@saeeol/core/npm"
|
|
7
7
|
import { Hash } from "@saeeol/core/util/hash"
|
|
8
|
+
import * as Bus from "@/bus"
|
|
8
9
|
import { BUNDLED_PROVIDERS } from "./bundled-providers"
|
|
10
|
+
import { ProviderInstallEvent } from "./provider-events"
|
|
9
11
|
import { buildTimeoutSignal } from "@/saeeol/provider/provider"
|
|
10
12
|
import { InitError } from "./provider-types"
|
|
11
13
|
import type { State, BundledSDK } from "./provider-types"
|
|
@@ -182,9 +184,27 @@ export async function resolveSDK(model: Model, s: State, envs: Record<string, st
|
|
|
182
184
|
|
|
183
185
|
let installedPath: string
|
|
184
186
|
if (!model.api.npm.startsWith("file://")) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
log.info("installing provider package", { providerID: model.providerID, pkg: model.api.npm })
|
|
188
|
+
void Bus.publish(ProviderInstallEvent.Started, {
|
|
189
|
+
providerID: model.providerID,
|
|
190
|
+
pkg: model.api.npm,
|
|
191
|
+
})
|
|
192
|
+
try {
|
|
193
|
+
const item = await Npm.add(model.api.npm)
|
|
194
|
+
if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
|
|
195
|
+
installedPath = item.entrypoint
|
|
196
|
+
void Bus.publish(ProviderInstallEvent.Completed, {
|
|
197
|
+
providerID: model.providerID,
|
|
198
|
+
pkg: model.api.npm,
|
|
199
|
+
})
|
|
200
|
+
} catch (installErr) {
|
|
201
|
+
void Bus.publish(ProviderInstallEvent.Failed, {
|
|
202
|
+
providerID: model.providerID,
|
|
203
|
+
pkg: model.api.npm,
|
|
204
|
+
error: installErr instanceof Error ? installErr.message : String(installErr),
|
|
205
|
+
})
|
|
206
|
+
throw installErr
|
|
207
|
+
}
|
|
188
208
|
} else {
|
|
189
209
|
log.info("loading local provider", { pkg: model.api.npm })
|
|
190
210
|
installedPath = model.api.npm
|