@vibe-forge/core 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Vibe-Forge.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@vibe-forge/core",
3
+ "version": "0.1.0",
4
+ "imports": {
5
+ "#~/*.js": {
6
+ "__vibe-forge__": {
7
+ "default": "./src/*.ts"
8
+ },
9
+ "default": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ }
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "__vibe-forge__": {
18
+ "default": "./src/index.ts"
19
+ },
20
+ "default": {
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "./utils/*": {
26
+ "__vibe-forge__": {
27
+ "default": "./src/utils/*.ts"
28
+ },
29
+ "default": {
30
+ "import": "./dist/utils/*.mjs",
31
+ "require": "./dist/utils/*.js"
32
+ }
33
+ },
34
+ "./controllers/task": {
35
+ "__vibe-forge__": {
36
+ "default": "./src/controllers/task/index.ts"
37
+ },
38
+ "default": {
39
+ "import": "./dist/controllers/task/index.mjs",
40
+ "require": "./dist/controllers/task/index.js"
41
+ }
42
+ },
43
+ "./schema": {
44
+ "__vibe-forge__": {
45
+ "default": "./src/schema.ts"
46
+ },
47
+ "default": {
48
+ "import": "./dist/schema.mjs",
49
+ "require": "./dist/schema.js"
50
+ }
51
+ },
52
+ "./hooks": {
53
+ "__vibe-forge__": {
54
+ "default": "./src/hooks/index.ts"
55
+ },
56
+ "default": {
57
+ "import": "./dist/hooks/index.mjs",
58
+ "require": "./dist/hooks/index.js"
59
+ }
60
+ }
61
+ },
62
+ "dependencies": {
63
+ "fast-glob": "^3.3.3",
64
+ "front-matter": "^4.0.2",
65
+ "js-yaml": "^4.1.1",
66
+ "node-notifier": "^10.0.1",
67
+ "zod": "^3.24.1"
68
+ },
69
+ "devDependencies": {
70
+ "@types/js-yaml": "^4.0.9",
71
+ "@types/node-notifier": "^8.0.5"
72
+ }
73
+ }
@@ -0,0 +1,6 @@
1
+ import type { Adapter } from './type'
2
+
3
+ export * from './loader'
4
+ export * from './type'
5
+
6
+ export const defineAdapter = (adapter: Adapter) => adapter
@@ -0,0 +1,9 @@
1
+ import type { Adapter } from './type'
2
+
3
+ export const loadAdapter = async (type: string) =>
4
+ (
5
+ // eslint-disable-next-line ts/no-require-imports
6
+ require(
7
+ type.startsWith('@') ? type : `@vibe-forge/adapter-${type}`
8
+ )
9
+ ).default as Adapter
@@ -0,0 +1,99 @@
1
+ import type { Cache, Config } from '@vibe-forge/core'
2
+
3
+ import type { Logger } from '#~/utils/create-logger.js'
4
+
5
+ import type { ChatMessage, ChatMessageContent } from '../types'
6
+
7
+ export type AdapterMessageContent = ChatMessageContent
8
+
9
+ export type AdapterOutputEvent =
10
+ | { type: 'init'; data: SessionInitInfo }
11
+ | { type: 'summary'; data: SessionSummaryInfo }
12
+ | { type: 'message'; data: ChatMessage }
13
+ | { type: 'exit'; data: { exitCode: number | null; stderr?: string } }
14
+ | { type: 'stop'; data?: ChatMessage }
15
+
16
+ export type SessionInfo =
17
+ | ({ type: 'init' } & SessionInitInfo)
18
+ | ({ type: 'summary' } & SessionSummaryInfo)
19
+
20
+ export interface SessionInitInfo {
21
+ uuid: string
22
+ model: string
23
+ version: string
24
+ tools: string[]
25
+ slashCommands: string[]
26
+ cwd: string
27
+ agents: string[]
28
+ title?: string
29
+ }
30
+
31
+ export interface SessionSummaryInfo {
32
+ summary: string
33
+ leafUuid: string
34
+ }
35
+
36
+ export type AdapterEvent =
37
+ | { type: 'message'; content: AdapterMessageContent[]; parentUuid?: string }
38
+ | { type: 'interrupt' }
39
+ | { type: 'stop' }
40
+
41
+ export interface AdapterCtx {
42
+ ctxId: string
43
+
44
+ cwd: string
45
+ env: Record<string, string | null | undefined>
46
+
47
+ cache: {
48
+ set: <K extends keyof Cache>(key: K, value: Cache[K]) => Promise<{
49
+ cachePath: string
50
+ }>
51
+ get: <K extends keyof Cache>(key: K) => Promise<Cache[K] | undefined>
52
+ }
53
+ logger: Logger
54
+
55
+ configs: [Config?, Config?]
56
+ }
57
+
58
+ export interface AdapterQueryOptions {
59
+ description?: string
60
+
61
+ type: 'create' | 'resume'
62
+ runtime: 'server' | 'cli' | 'mcp'
63
+ sessionId: string
64
+ model?: string
65
+ mode?: 'stream' | 'direct'
66
+
67
+ systemPrompt?: string
68
+ appendSystemPrompt?: boolean
69
+
70
+ mcpServers?: {
71
+ include?: string[]
72
+ exclude?: string[]
73
+ }
74
+ tools?: {
75
+ include?: string[]
76
+ exclude?: string[]
77
+ }
78
+ skills?: {
79
+ include?: string[]
80
+ exclude?: string[]
81
+ }
82
+
83
+ extraOptions?: string[]
84
+
85
+ onEvent: (event: AdapterOutputEvent) => void
86
+ }
87
+
88
+ export interface AdapterSession {
89
+ kill: () => void
90
+ emit: (event: AdapterEvent) => void
91
+ pid?: number
92
+ }
93
+
94
+ export interface Adapter {
95
+ query: (
96
+ ctx: AdapterCtx,
97
+ options: AdapterQueryOptions
98
+ ) => Promise<AdapterSession>
99
+ }
package/src/config.ts ADDED
@@ -0,0 +1,375 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+ import process from 'node:process'
5
+
6
+ import { load } from 'js-yaml'
7
+
8
+ import type { PluginConfig } from './hooks'
9
+
10
+ export interface AdapterMap {}
11
+
12
+ export interface ModelServiceConfig {
13
+ /**
14
+ * 模型服务展示标题
15
+ */
16
+ title?: string
17
+ /**
18
+ * 模型服务展示描述
19
+ */
20
+ description?: string
21
+ /**
22
+ * 模型服务 API 基础 URL
23
+ */
24
+ apiBaseUrl: string
25
+ /**
26
+ * 模型服务 API 密钥
27
+ */
28
+ apiKey: string
29
+ /**
30
+ * 模型服务支持的模型列表
31
+ */
32
+ models?: string[]
33
+ /**
34
+ * 模型服务支持的模型别名
35
+ */
36
+ modelsAlias?: Record<string, string[]>
37
+ /**
38
+ * 拓展配置,由下游自行消费
39
+ */
40
+ extra?: Record<string, unknown>
41
+ }
42
+
43
+ export interface RecommendedModelConfig {
44
+ service?: string
45
+ model: string
46
+ title?: string
47
+ description?: string
48
+ placement?: 'modelSelector'
49
+ }
50
+
51
+ export type LanguageCode = 'zh' | 'en'
52
+
53
+ export type NotificationTrigger = 'completed' | 'failed' | 'terminated' | 'waiting_input'
54
+
55
+ export interface NotificationEventConfig {
56
+ title?: string
57
+ description?: string
58
+ disabled?: boolean
59
+ sound?: string
60
+ }
61
+
62
+ export interface NotificationConfig {
63
+ disabled?: boolean
64
+ volume?: number
65
+ events?: Partial<Record<NotificationTrigger, NotificationEventConfig>>
66
+ }
67
+
68
+ export interface Config {
69
+ /**
70
+ * 配置目录
71
+ */
72
+ baseDir?: string
73
+ /**
74
+ * 适配器配置
75
+ */
76
+ adapters?: Partial<AdapterMap>
77
+ /**
78
+ * 默认适配器名称
79
+ */
80
+ defaultAdapter?: keyof AdapterMap
81
+ /**
82
+ * 模型服务配置
83
+ */
84
+ modelServices?: Record<string, ModelServiceConfig>
85
+ /**
86
+ * 默认模型服务名称
87
+ */
88
+ defaultModelService?: string
89
+ /**
90
+ * 默认模型名称
91
+ */
92
+ defaultModel?: string
93
+ recommendedModels?: RecommendedModelConfig[]
94
+ interfaceLanguage?: LanguageCode
95
+ modelLanguage?: LanguageCode
96
+ /**
97
+ * MCP 服务器配置
98
+ */
99
+ mcpServers?: Record<
100
+ string,
101
+ & {
102
+ /**
103
+ * 是否启用
104
+ */
105
+ enabled?: boolean
106
+ /**
107
+ * 环境变量配置
108
+ */
109
+ env?: Record<string, string>
110
+ }
111
+ & (
112
+ | {
113
+ type?: undefined
114
+ command: string
115
+ args: string[]
116
+ }
117
+ | {
118
+ type: 'sse'
119
+ url: string
120
+ headers: Record<string, string>
121
+ }
122
+ | {
123
+ type: 'http'
124
+ url: string
125
+ headers?: Record<string, string>
126
+ }
127
+ )
128
+ >
129
+ /**
130
+ * 默认启用的 MCP 服务器列表
131
+ */
132
+ defaultIncludeMcpServers?: string[]
133
+ /**
134
+ * 默认禁用的 MCP 服务器列表
135
+ */
136
+ defaultExcludeMcpServers?: string[]
137
+ noDefaultVibeForgeMcpServer?: boolean
138
+ /**
139
+ * 权限配置
140
+ */
141
+ permissions?: {
142
+ allow?: string[]
143
+ deny?: string[]
144
+ ask?: string[]
145
+ }
146
+ /**
147
+ * 环境变量配置
148
+ */
149
+ env?: Record<string, string>
150
+ /**
151
+ * 公告配置
152
+ */
153
+ announcements?: string[]
154
+ /**
155
+ * 快捷键配置
156
+ */
157
+ shortcuts?: {
158
+ newSession?: string
159
+ openConfig?: string
160
+ }
161
+ notifications?: NotificationConfig
162
+ /**
163
+ * 会话配置
164
+ */
165
+ conversation?: {
166
+ /**
167
+ * 对话风格
168
+ * - `friendly`: 友好的对话风格,适合用户与助手交互
169
+ * - `programmatic`: 程序化的对话风格,适合助手执行任务
170
+ */
171
+ style?: 'friendly' | 'programmatic'
172
+ /**
173
+ * 自定义对话风格。通过指定提示词约束对话风格。
174
+ */
175
+ customInstructions?: string
176
+ }
177
+ /**
178
+ * 插件配置
179
+ */
180
+ plugins?: PluginConfig
181
+ enabledPlugins?: Record<string, boolean>
182
+ extraKnownMarketplaces?: Record<
183
+ string,
184
+ {
185
+ source:
186
+ | {
187
+ source: 'github'
188
+ repo: string
189
+ }
190
+ | {
191
+ source: 'git'
192
+ url: string
193
+ }
194
+ | {
195
+ source: 'directory'
196
+ path: string
197
+ }
198
+ }
199
+ >
200
+ }
201
+
202
+ export interface AboutInfo {
203
+ version?: string
204
+ lastReleaseAt?: string
205
+ urls?: {
206
+ repo?: string
207
+ docs?: string
208
+ contact?: string
209
+ issues?: string
210
+ releases?: string
211
+ }
212
+ }
213
+
214
+ export interface ConfigSection {
215
+ general?: {
216
+ baseDir?: Config['baseDir']
217
+ defaultAdapter?: Config['defaultAdapter']
218
+ defaultModelService?: Config['defaultModelService']
219
+ defaultModel?: Config['defaultModel']
220
+ recommendedModels?: Config['recommendedModels']
221
+ interfaceLanguage?: Config['interfaceLanguage']
222
+ modelLanguage?: Config['modelLanguage']
223
+ announcements?: Config['announcements']
224
+ permissions?: Config['permissions']
225
+ env?: Config['env']
226
+ notifications?: Config['notifications']
227
+ }
228
+ conversation?: Config['conversation']
229
+ modelServices?: Config['modelServices']
230
+ adapters?: Config['adapters']
231
+ plugins?: {
232
+ plugins?: Config['plugins']
233
+ enabledPlugins?: Config['enabledPlugins']
234
+ extraKnownMarketplaces?: Config['extraKnownMarketplaces']
235
+ }
236
+ mcp?: {
237
+ mcpServers?: Config['mcpServers']
238
+ defaultIncludeMcpServers?: Config['defaultIncludeMcpServers']
239
+ defaultExcludeMcpServers?: Config['defaultExcludeMcpServers']
240
+ noDefaultVibeForgeMcpServer?: Config['noDefaultVibeForgeMcpServer']
241
+ }
242
+ shortcuts?: Config['shortcuts']
243
+ }
244
+
245
+ export interface ConfigResponse {
246
+ sources?: {
247
+ project?: ConfigSection
248
+ user?: ConfigSection
249
+ merged?: ConfigSection
250
+ }
251
+ meta?: {
252
+ workspaceFolder?: string
253
+ configPresent?: {
254
+ project?: boolean
255
+ user?: boolean
256
+ }
257
+ experiments?: Record<string, unknown>
258
+ about?: AboutInfo
259
+ }
260
+ }
261
+
262
+ export const defineConfig = (config: Config) => config
263
+
264
+ const loadJSConfig = async (paths: string[]) => {
265
+ for (const path of paths) {
266
+ try {
267
+ const configPath = resolve(process.cwd(), path)
268
+ if (!existsSync(configPath)) {
269
+ continue
270
+ }
271
+ // eslint-disable-next-line ts/no-require-imports
272
+ return (require(configPath)?.default ?? {}) as Config
273
+ } catch (e) {
274
+ console.error(`Failed to load config file ${path}: ${e}`)
275
+ }
276
+ }
277
+ }
278
+
279
+ const loadJSONConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
280
+ for (const path of paths) {
281
+ try {
282
+ const configPath = resolve(process.cwd(), path)
283
+ if (!existsSync(configPath)) {
284
+ continue
285
+ }
286
+ const configContent = await readFile(configPath, 'utf-8')
287
+ const configResolvedContent = configContent
288
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
289
+ return JSON.parse(configResolvedContent) as Config
290
+ } catch (e) {
291
+ console.error(`Failed to load config file ${path}: ${e}`)
292
+ }
293
+ }
294
+ }
295
+
296
+ const loadYAMLConfig = async (paths: string[], jsonVariables: Record<string, string | null | undefined>) => {
297
+ for (const path of paths) {
298
+ try {
299
+ const configPath = resolve(process.cwd(), path)
300
+ if (!existsSync(configPath)) {
301
+ continue
302
+ }
303
+ const configContent = await readFile(configPath, 'utf-8')
304
+ const configResolvedContent = configContent
305
+ .replace(/\$\{(\w+)\}/g, (_, key) => jsonVariables[key] ?? `$\{${key}}`)
306
+ return load(configResolvedContent) as Config
307
+ } catch (e) {
308
+ console.error(`Failed to load config file ${path}: ${e}`)
309
+ }
310
+ }
311
+ }
312
+
313
+ let configCache: Promise<readonly [Config | undefined, Config | undefined]> | null = null
314
+
315
+ export const resetConfigCache = () => {
316
+ configCache = null
317
+ }
318
+
319
+ export const loadConfig = (options: {
320
+ jsonVariables?: Record<string, string | null | undefined>
321
+ }) => {
322
+ if (configCache) {
323
+ return configCache
324
+ }
325
+
326
+ configCache = (async () =>
327
+ [
328
+ await loadJSONConfig(
329
+ [
330
+ './.ai.config.json',
331
+ './infra/.ai.config.json'
332
+ ],
333
+ options.jsonVariables ?? {}
334
+ ) ??
335
+ await loadYAMLConfig(
336
+ [
337
+ './.ai.config.yaml',
338
+ './.ai.config.yml',
339
+ './infra/.ai.config.yaml',
340
+ './infra/.ai.config.yml'
341
+ ],
342
+ options.jsonVariables ?? {}
343
+ ),
344
+ await loadJSONConfig(
345
+ [
346
+ './.ai.dev.config.json',
347
+ './infra/.ai.dev.config.json'
348
+ ],
349
+ options.jsonVariables ?? {}
350
+ ) ??
351
+ await loadYAMLConfig(
352
+ [
353
+ './.ai.dev.config.yaml',
354
+ './.ai.dev.config.yml',
355
+ './infra/.ai.dev.config.yaml',
356
+ './infra/.ai.dev.config.yml'
357
+ ],
358
+ options.jsonVariables ?? {}
359
+ )
360
+ ] as const)()
361
+ return configCache
362
+ }
363
+
364
+ export const loadAdapterConfig = async <
365
+ K extends keyof AdapterMap,
366
+ >(
367
+ name: K,
368
+ options: { jsonVariables?: Record<string, string> }
369
+ ) => {
370
+ const [projectConfig, userConfig] = await loadConfig(options)
371
+ return {
372
+ ...(projectConfig?.adapters?.[name] ?? {}),
373
+ ...(userConfig?.adapters?.[name] ?? {})
374
+ } as unknown as NonNullable<Config['adapters']>[K]
375
+ }