@spaceflow/core 0.1.1

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 (116) hide show
  1. package/CHANGELOG.md +1176 -0
  2. package/README.md +105 -0
  3. package/nest-cli.json +10 -0
  4. package/package.json +128 -0
  5. package/rspack.config.mjs +62 -0
  6. package/src/__mocks__/@opencode-ai/sdk.js +9 -0
  7. package/src/__mocks__/c12.ts +3 -0
  8. package/src/app.module.ts +18 -0
  9. package/src/config/ci.config.ts +29 -0
  10. package/src/config/config-loader.ts +101 -0
  11. package/src/config/config-reader.module.ts +16 -0
  12. package/src/config/config-reader.service.ts +133 -0
  13. package/src/config/feishu.config.ts +35 -0
  14. package/src/config/git-provider.config.ts +29 -0
  15. package/src/config/index.ts +29 -0
  16. package/src/config/llm.config.ts +110 -0
  17. package/src/config/schema-generator.service.ts +129 -0
  18. package/src/config/spaceflow.config.ts +292 -0
  19. package/src/config/storage.config.ts +33 -0
  20. package/src/extension-system/extension.interface.ts +221 -0
  21. package/src/extension-system/index.ts +1 -0
  22. package/src/index.ts +80 -0
  23. package/src/locales/en/translation.json +11 -0
  24. package/src/locales/zh-cn/translation.json +11 -0
  25. package/src/shared/claude-setup/claude-setup.module.ts +8 -0
  26. package/src/shared/claude-setup/claude-setup.service.ts +131 -0
  27. package/src/shared/claude-setup/index.ts +2 -0
  28. package/src/shared/editor-config/index.ts +23 -0
  29. package/src/shared/feishu-sdk/feishu-sdk.module.ts +77 -0
  30. package/src/shared/feishu-sdk/feishu-sdk.service.ts +130 -0
  31. package/src/shared/feishu-sdk/fieshu-card.service.ts +139 -0
  32. package/src/shared/feishu-sdk/index.ts +4 -0
  33. package/src/shared/feishu-sdk/types/card-action.ts +132 -0
  34. package/src/shared/feishu-sdk/types/card.ts +64 -0
  35. package/src/shared/feishu-sdk/types/common.ts +22 -0
  36. package/src/shared/feishu-sdk/types/index.ts +46 -0
  37. package/src/shared/feishu-sdk/types/message.ts +35 -0
  38. package/src/shared/feishu-sdk/types/module.ts +21 -0
  39. package/src/shared/feishu-sdk/types/user.ts +77 -0
  40. package/src/shared/git-provider/adapters/gitea.adapter.spec.ts +473 -0
  41. package/src/shared/git-provider/adapters/gitea.adapter.ts +499 -0
  42. package/src/shared/git-provider/adapters/github.adapter.spec.ts +341 -0
  43. package/src/shared/git-provider/adapters/github.adapter.ts +830 -0
  44. package/src/shared/git-provider/adapters/gitlab.adapter.ts +839 -0
  45. package/src/shared/git-provider/adapters/index.ts +3 -0
  46. package/src/shared/git-provider/detect-provider.spec.ts +195 -0
  47. package/src/shared/git-provider/detect-provider.ts +112 -0
  48. package/src/shared/git-provider/git-provider.interface.ts +188 -0
  49. package/src/shared/git-provider/git-provider.module.ts +73 -0
  50. package/src/shared/git-provider/git-provider.service.spec.ts +282 -0
  51. package/src/shared/git-provider/git-provider.service.ts +309 -0
  52. package/src/shared/git-provider/index.ts +7 -0
  53. package/src/shared/git-provider/parse-repo-url.spec.ts +221 -0
  54. package/src/shared/git-provider/parse-repo-url.ts +155 -0
  55. package/src/shared/git-provider/types.ts +434 -0
  56. package/src/shared/git-sdk/git-sdk-diff.utils.spec.ts +344 -0
  57. package/src/shared/git-sdk/git-sdk-diff.utils.ts +151 -0
  58. package/src/shared/git-sdk/git-sdk.module.ts +8 -0
  59. package/src/shared/git-sdk/git-sdk.service.ts +235 -0
  60. package/src/shared/git-sdk/git-sdk.types.ts +25 -0
  61. package/src/shared/git-sdk/index.ts +4 -0
  62. package/src/shared/i18n/i18n.spec.ts +96 -0
  63. package/src/shared/i18n/i18n.ts +86 -0
  64. package/src/shared/i18n/index.ts +1 -0
  65. package/src/shared/i18n/locale-detect.ts +134 -0
  66. package/src/shared/llm-jsonput/index.ts +94 -0
  67. package/src/shared/llm-jsonput/types.ts +17 -0
  68. package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +131 -0
  69. package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +208 -0
  70. package/src/shared/llm-proxy/adapters/index.ts +4 -0
  71. package/src/shared/llm-proxy/adapters/llm-adapter.interface.ts +23 -0
  72. package/src/shared/llm-proxy/adapters/open-code.adapter.ts +342 -0
  73. package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +215 -0
  74. package/src/shared/llm-proxy/adapters/openai.adapter.ts +153 -0
  75. package/src/shared/llm-proxy/index.ts +6 -0
  76. package/src/shared/llm-proxy/interfaces/config.interface.ts +32 -0
  77. package/src/shared/llm-proxy/interfaces/index.ts +4 -0
  78. package/src/shared/llm-proxy/interfaces/message.interface.ts +48 -0
  79. package/src/shared/llm-proxy/interfaces/session.interface.ts +28 -0
  80. package/src/shared/llm-proxy/llm-proxy.module.ts +140 -0
  81. package/src/shared/llm-proxy/llm-proxy.service.spec.ts +303 -0
  82. package/src/shared/llm-proxy/llm-proxy.service.ts +132 -0
  83. package/src/shared/llm-proxy/llm-session.spec.ts +111 -0
  84. package/src/shared/llm-proxy/llm-session.ts +109 -0
  85. package/src/shared/llm-proxy/stream-logger.ts +97 -0
  86. package/src/shared/logger/index.ts +11 -0
  87. package/src/shared/logger/logger.interface.ts +93 -0
  88. package/src/shared/logger/logger.spec.ts +178 -0
  89. package/src/shared/logger/logger.ts +175 -0
  90. package/src/shared/logger/renderers/plain.renderer.ts +116 -0
  91. package/src/shared/logger/renderers/tui.renderer.ts +162 -0
  92. package/src/shared/mcp/index.ts +332 -0
  93. package/src/shared/output/index.ts +2 -0
  94. package/src/shared/output/output.module.ts +9 -0
  95. package/src/shared/output/output.service.ts +97 -0
  96. package/src/shared/package-manager/index.ts +115 -0
  97. package/src/shared/parallel/index.ts +1 -0
  98. package/src/shared/parallel/parallel-executor.ts +169 -0
  99. package/src/shared/rspack-config/index.ts +1 -0
  100. package/src/shared/rspack-config/rspack-config.ts +157 -0
  101. package/src/shared/source-utils/index.ts +130 -0
  102. package/src/shared/spaceflow-dir/index.ts +158 -0
  103. package/src/shared/storage/adapters/file.adapter.ts +113 -0
  104. package/src/shared/storage/adapters/index.ts +3 -0
  105. package/src/shared/storage/adapters/memory.adapter.ts +50 -0
  106. package/src/shared/storage/adapters/storage-adapter.interface.ts +48 -0
  107. package/src/shared/storage/index.ts +4 -0
  108. package/src/shared/storage/storage.module.ts +150 -0
  109. package/src/shared/storage/storage.service.ts +293 -0
  110. package/src/shared/storage/types.ts +51 -0
  111. package/src/shared/verbose/index.ts +73 -0
  112. package/test/app.e2e-spec.ts +22 -0
  113. package/tsconfig.build.json +4 -0
  114. package/tsconfig.json +25 -0
  115. package/tsconfig.skill.json +18 -0
  116. package/vitest.config.ts +58 -0
@@ -0,0 +1,29 @@
1
+ export * from "./git-provider.config";
2
+ export * from "./ci.config";
3
+ export * from "./llm.config";
4
+ export * from "./feishu.config";
5
+ export * from "./storage.config";
6
+ export * from "./spaceflow.config";
7
+ export * from "./config-reader.service";
8
+ export * from "./config-reader.module";
9
+ export * from "./schema-generator.service";
10
+ export * from "./config-loader";
11
+
12
+ import { gitProviderConfig } from "./git-provider.config";
13
+ import { ciConfig } from "./ci.config";
14
+ import { llmConfig } from "./llm.config";
15
+ import { feishuConfig } from "./feishu.config";
16
+ import { storageConfig } from "./storage.config";
17
+ import { spaceflowConfig } from "./spaceflow.config";
18
+
19
+ /**
20
+ * 所有配置加载器
21
+ */
22
+ export const configLoaders = [
23
+ gitProviderConfig,
24
+ ciConfig,
25
+ llmConfig,
26
+ feishuConfig,
27
+ storageConfig,
28
+ spaceflowConfig,
29
+ ];
@@ -0,0 +1,110 @@
1
+ import { z } from "zod";
2
+ import { createConfigLoader } from "./config-loader";
3
+
4
+ const schemaFactory = () => {
5
+ /** Claude Code 适配器配置 Schema */
6
+ const ClaudeCodeConfigSchema = z.object({
7
+ baseUrl: z
8
+ .string()
9
+ .default(process.env.CLAUDE_CODE_BASE_URL || "")
10
+ .describe("API 基础 URL"),
11
+ authToken: z
12
+ .string()
13
+ .default(process.env.CLAUDE_CODE_AUTH_TOKEN || "")
14
+ .describe("认证令牌"),
15
+ model: z
16
+ .string()
17
+ .default(process.env.CLAUDE_CODE_MODEL || "claude-sonnet-4-5")
18
+ .describe("模型名称"),
19
+ hasCompletedOnboarding: z.boolean().optional().describe("是否已完成 Claude Code 引导流程"),
20
+ });
21
+
22
+ /** OpenAI 适配器配置 Schema */
23
+ const OpenAIConfigSchema = z.object({
24
+ baseUrl: z
25
+ .string()
26
+ .default(process.env.OPENAI_BASE_URL || "")
27
+ .describe("API 基础 URL"),
28
+ apiKey: z
29
+ .string()
30
+ .default(process.env.OPENAI_API_KEY || "")
31
+ .describe("API Key"),
32
+ model: z
33
+ .string()
34
+ .default(process.env.OPENAI_MODEL || "gpt-4o")
35
+ .describe("模型名称"),
36
+ });
37
+
38
+ /** OpenCode 适配器配置 Schema */
39
+ const OpenCodeConfigSchema = z.object({
40
+ serverUrl: z
41
+ .string()
42
+ .default(process.env.OPENCODE_SERVER_URL || "http://localhost:4096")
43
+ .describe("服务器 URL"),
44
+ baseUrl: z
45
+ .string()
46
+ .default(process.env.OPENCODE_BASE_URL || "")
47
+ .describe("API 基础 URL"),
48
+ apiKey: z
49
+ .string()
50
+ .default(process.env.OPENCODE_API_KEY || "")
51
+ .describe("API Key"),
52
+ providerID: z
53
+ .string()
54
+ .default(process.env.OPENCODE_PROVIDER_ID || "openai")
55
+ .describe("Provider ID"),
56
+ model: z
57
+ .string()
58
+ .default(process.env.OPENCODE_MODEL || "")
59
+ .describe("模型名称"),
60
+ });
61
+
62
+ /** Gemini 适配器配置 Schema */
63
+ const GeminiConfigSchema = z.object({
64
+ baseUrl: z
65
+ .string()
66
+ .default(process.env.GEMINI_BASE_URL || "")
67
+ .describe("API 基础 URL"),
68
+ apiKey: z
69
+ .string()
70
+ .default(process.env.GEMINI_API_KEY || "")
71
+ .describe("API Key"),
72
+ model: z
73
+ .string()
74
+ .default(process.env.GEMINI_MODEL || "")
75
+ .describe("模型名称"),
76
+ });
77
+
78
+ /** LLM 配置 Schema */
79
+ const LlmConfigSchema = z.object({
80
+ claudeCode: z
81
+ .preprocess((v) => v ?? {}, ClaudeCodeConfigSchema)
82
+ .describe("Claude Code 适配器配置"),
83
+ openai: z.preprocess((v) => v ?? {}, OpenAIConfigSchema).describe("OpenAI 适配器配置"),
84
+ openCode: z.preprocess((v) => v ?? {}, OpenCodeConfigSchema).describe("OpenCode 适配器配置"),
85
+ gemini: z.preprocess((v) => v ?? {}, GeminiConfigSchema).describe("Gemini 适配器配置"),
86
+ });
87
+
88
+ return LlmConfigSchema;
89
+ };
90
+
91
+ /** LLM 系统配置类型 */
92
+ export type LlmConfig = z.infer<ReturnType<typeof schemaFactory>>;
93
+
94
+ /** Claude Code 适配器配置类型 */
95
+ export type ClaudeCodeConfig = z.infer<LlmConfig["claudeCode"]>;
96
+
97
+ /** OpenAI 适配器配置类型 */
98
+ export type OpenAIConfig = z.infer<LlmConfig["openai"]>;
99
+
100
+ /** OpenCode 适配器配置类型 */
101
+ export type OpenCodeConfig = z.infer<LlmConfig["openCode"]>;
102
+
103
+ /** Gemini 适配器配置类型 */
104
+ export type GeminiConfig = z.infer<LlmConfig["gemini"]>;
105
+
106
+ export const llmConfig = createConfigLoader({
107
+ configKey: "llm",
108
+ schemaFactory,
109
+ description: "LLM 服务配置",
110
+ });
@@ -0,0 +1,129 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { z } from "zod";
3
+ import * as fs from "fs";
4
+ import * as path from "path";
5
+
6
+ /** Schema 注册信息 */
7
+ export interface SchemaRegistry {
8
+ /** 配置 key */
9
+ configKey: string;
10
+ /** zod schema 工厂函数 */
11
+ schemaFactory: () => z.ZodType;
12
+ /** 描述 */
13
+ description?: string;
14
+ }
15
+
16
+ /** 全局 schema 注册表 */
17
+ const schemaRegistry = new Map<string, SchemaRegistry>();
18
+
19
+ /**
20
+ * 注册插件配置 schema(由插件加载器调用)
21
+ */
22
+ export function registerPluginSchema(registry: SchemaRegistry): void {
23
+ schemaRegistry.set(registry.configKey, registry);
24
+ }
25
+
26
+ /**
27
+ * 获取所有已注册的 schema
28
+ */
29
+ export function getRegisteredSchemas(): Map<string, SchemaRegistry> {
30
+ return schemaRegistry;
31
+ }
32
+
33
+ /**
34
+ * Schema 生成服务
35
+ * 用于生成 JSON Schema 文件
36
+ */
37
+ @Injectable()
38
+ export class SchemaGeneratorService {
39
+ /**
40
+ * 生成完整的 spaceflow.json 的 JSON Schema
41
+ * @param outputPath 输出路径
42
+ */
43
+ generateJsonSchema(outputPath: string): void {
44
+ const properties: Record<string, unknown> = {
45
+ dependencies: {
46
+ type: "object",
47
+ description: "已安装的技能包注册表",
48
+ additionalProperties: { type: "string" },
49
+ },
50
+ support: {
51
+ type: "array",
52
+ description: "支持的编辑器列表",
53
+ items: { type: "string" },
54
+ },
55
+ };
56
+
57
+ // 添加所有插件的 schema
58
+ for (const [configKey, registry] of schemaRegistry) {
59
+ try {
60
+ const schema = registry.schemaFactory();
61
+ const jsonSchema = z.toJSONSchema(schema, {
62
+ target: "draft-07",
63
+ });
64
+ properties[configKey] = {
65
+ ...jsonSchema,
66
+ description: registry.description || `${configKey} 插件配置`,
67
+ };
68
+ } catch (error) {
69
+ console.warn(`⚠️ 无法转换 ${configKey} 的 schema:`, error);
70
+ }
71
+ }
72
+
73
+ const fullSchema = {
74
+ $schema: "http://json-schema.org/draft-07/schema#",
75
+ title: "Spaceflow Configuration",
76
+ description: "Spaceflow 配置文件 schema",
77
+ type: "object",
78
+ properties,
79
+ additionalProperties: true,
80
+ };
81
+
82
+ // 确保目录存在
83
+ const dir = path.dirname(outputPath);
84
+ if (!fs.existsSync(dir)) {
85
+ fs.mkdirSync(dir, { recursive: true });
86
+ }
87
+
88
+ // 写入文件
89
+ fs.writeFileSync(outputPath, JSON.stringify(fullSchema, null, 2), "utf-8");
90
+ console.log(`✅ JSON Schema 已生成: ${outputPath}`);
91
+
92
+ // 自动添加到 .gitignore
93
+ this.addToGitignore(dir, path.basename(outputPath));
94
+ }
95
+
96
+ /**
97
+ * 生成 JSON Schema 到默认路径 (.spaceflow/config-schema.json)
98
+ */
99
+ generate(): void {
100
+ const outputPath = path.join(process.cwd(), ".spaceflow", "config-schema.json");
101
+ this.generateJsonSchema(outputPath);
102
+ }
103
+
104
+ /**
105
+ * 将文件添加到 .gitignore(如果不存在)
106
+ */
107
+ private addToGitignore(dir: string, filename: string): void {
108
+ const gitignorePath = path.join(dir, ".gitignore");
109
+ let content = "";
110
+
111
+ if (fs.existsSync(gitignorePath)) {
112
+ content = fs.readFileSync(gitignorePath, "utf-8");
113
+ // 检查是否已存在
114
+ const lines = content.split("\n").map((line) => line.trim());
115
+ if (lines.includes(filename)) {
116
+ return;
117
+ }
118
+ } else {
119
+ content = "# 自动生成的 .gitignore\n";
120
+ }
121
+
122
+ // 追加文件名
123
+ const newContent = content.endsWith("\n")
124
+ ? `${content}${filename}\n`
125
+ : `${content}\n${filename}\n`;
126
+
127
+ fs.writeFileSync(gitignorePath, newContent, "utf-8");
128
+ }
129
+ }
@@ -0,0 +1,292 @@
1
+ import { registerAs } from "@nestjs/config";
2
+ import { readFileSync, existsSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+ import stringify from "json-stringify-pretty-compact";
6
+ import { z } from "zod";
7
+ import type { LLMMode } from "../shared/llm-proxy";
8
+ import { registerPluginSchema } from "./schema-generator.service";
9
+
10
+ /** 默认编辑器 */
11
+ export const DEFAULT_SUPPORT_EDITOR = "claudeCode";
12
+
13
+ /** 配置文件名 */
14
+ export const CONFIG_FILE_NAME = "spaceflow.json";
15
+
16
+ /** RC 配置文件名(位于 .spaceflow 同级目录) */
17
+ export const RC_FILE_NAME = ".spaceflowrc";
18
+
19
+ /** Spaceflow 核心配置 Schema */
20
+ const SpaceflowCoreConfigSchema = z.object({
21
+ /** 界面语言,如 zh-CN、en */
22
+ lang: z.string().optional().describe("界面语言,如 zh-CN、en"),
23
+ /** 已安装的技能包注册表 */
24
+ dependencies: z.record(z.string(), z.string()).optional().describe("已安装的技能包注册表"),
25
+ /** 支持的编辑器列表,用于安装 skills 和 commands 时关联目录 */
26
+ support: z.array(z.string()).default([DEFAULT_SUPPORT_EDITOR]).describe("支持的编辑器列表"),
27
+ });
28
+
29
+ // 注册 spaceflow 核心配置 schema
30
+ registerPluginSchema({
31
+ configKey: "spaceflow",
32
+ schemaFactory: () => SpaceflowCoreConfigSchema,
33
+ description: "Spaceflow 核心配置",
34
+ });
35
+
36
+ /**
37
+ * SpaceflowConfig - 通用配置
38
+ * 子命令的配置由各自模块定义和管理
39
+ */
40
+ export type SpaceflowConfig = z.infer<typeof SpaceflowCoreConfigSchema> & {
41
+ /** 子命令配置,由各子命令模块自行定义类型 */
42
+ [key: string]: unknown;
43
+ };
44
+
45
+ // ============ 配置文件操作工具函数 ============
46
+
47
+ // 不应该被深度合并的字段,这些字段应该直接覆盖而非合并
48
+ const NO_MERGE_FIELDS = ["dependencies"];
49
+
50
+ /**
51
+ * 深度合并对象
52
+ * 后面的对象会覆盖前面的对象,数组会被替换而非合并
53
+ * NO_MERGE_FIELDS 中的字段不会被深度合并,而是直接覆盖
54
+ */
55
+ function deepMerge<T extends Record<string, unknown>>(...objects: Partial<T>[]): Partial<T> {
56
+ const result: Record<string, unknown> = {};
57
+
58
+ for (const obj of objects) {
59
+ for (const key in obj) {
60
+ const value = obj[key];
61
+ const existing = result[key];
62
+
63
+ // 对于 NO_MERGE_FIELDS 中的字段,直接覆盖而非合并
64
+ if (NO_MERGE_FIELDS.includes(key)) {
65
+ if (value !== undefined) {
66
+ result[key] = value;
67
+ }
68
+ } else if (
69
+ value !== null &&
70
+ typeof value === "object" &&
71
+ !Array.isArray(value) &&
72
+ existing !== null &&
73
+ typeof existing === "object" &&
74
+ !Array.isArray(existing)
75
+ ) {
76
+ result[key] = deepMerge(
77
+ existing as Record<string, unknown>,
78
+ value as Record<string, unknown>,
79
+ );
80
+ } else if (value !== undefined) {
81
+ result[key] = value;
82
+ }
83
+ }
84
+ }
85
+
86
+ return result as Partial<T>;
87
+ }
88
+
89
+ /**
90
+ * 获取主配置文件路径(用于写入)
91
+ * 配置文件统一存放在 .spaceflow/ 目录下
92
+ * @param cwd 工作目录,默认为 process.cwd()
93
+ */
94
+ export function getConfigPath(cwd?: string): string {
95
+ return join(cwd || process.cwd(), ".spaceflow", CONFIG_FILE_NAME);
96
+ }
97
+
98
+ /**
99
+ * 获取所有配置文件路径(按优先级从低到高排列)
100
+ * 优先级: ~/.spaceflow/spaceflow.json < ~/.spaceflowrc < ./.spaceflow/spaceflow.json < ./.spaceflowrc
101
+ * @param cwd 工作目录,默认为 process.cwd()
102
+ */
103
+ export function getConfigPaths(cwd?: string): string[] {
104
+ const workDir = cwd || process.cwd();
105
+ return [
106
+ join(homedir(), ".spaceflow", CONFIG_FILE_NAME),
107
+ join(homedir(), RC_FILE_NAME),
108
+ join(workDir, ".spaceflow", CONFIG_FILE_NAME),
109
+ join(workDir, RC_FILE_NAME),
110
+ ];
111
+ }
112
+
113
+ /** .env 文件名 */
114
+ const ENV_FILE_NAME = ".env";
115
+
116
+ /**
117
+ * 获取所有 .env 文件路径(按优先级从高到低排列,供 ConfigModule.envFilePath 使用)
118
+ *
119
+ * NestJS ConfigModule 中 envFilePath 数组靠前的优先级更高(先读到的变量不会被后面覆盖)
120
+ * 因此返回顺序为从高到低:
121
+ * 1. ./.env (程序启动目录,最高优先级)
122
+ * 2. ./.spaceflow/.env (项目配置目录)
123
+ * 3. ~/.env (全局 home 目录)
124
+ * 4. ~/.spaceflow/.env (全局配置目录,最低优先级)
125
+ *
126
+ * @param cwd 工作目录,默认为 process.cwd()
127
+ */
128
+ export function getEnvFilePaths(cwd?: string): string[] {
129
+ const workDir = cwd || process.cwd();
130
+ return [
131
+ join(workDir, ENV_FILE_NAME),
132
+ join(workDir, ".spaceflow", ENV_FILE_NAME),
133
+ join(homedir(), ENV_FILE_NAME),
134
+ join(homedir(), ".spaceflow", ENV_FILE_NAME),
135
+ ];
136
+ }
137
+
138
+ /**
139
+ * 读取单个配置文件(同步)
140
+ * @param configPath 配置文件路径
141
+ */
142
+ function readSingleConfigSync(configPath: string): Partial<SpaceflowConfig> {
143
+ if (!existsSync(configPath)) {
144
+ return {};
145
+ }
146
+
147
+ try {
148
+ const content = readFileSync(configPath, "utf-8");
149
+ return JSON.parse(content);
150
+ } catch {
151
+ console.warn(`警告: 无法解析配置文件 ${configPath}`);
152
+ return {};
153
+ }
154
+ }
155
+
156
+ /**
157
+ * 读取配置文件(同步)
158
+ * 按优先级从低到高读取并合并配置:
159
+ * 1. ~/.spaceflow/spaceflow.json (全局配置,最低优先级)
160
+ * 2. ~/.spaceflowrc (全局 RC 配置)
161
+ * 3. ./.spaceflow/spaceflow.json (项目配置)
162
+ * 4. ./.spaceflowrc (项目根目录 RC 配置,最高优先级)
163
+ * @param cwd 工作目录,默认为 process.cwd()
164
+ */
165
+ export function readConfigSync(cwd?: string): Partial<SpaceflowConfig> {
166
+ const configPaths = getConfigPaths(cwd);
167
+ const configs = configPaths.map((p) => readSingleConfigSync(p));
168
+ return deepMerge(...configs);
169
+ }
170
+
171
+ /**
172
+ * 写入配置文件(同步)
173
+ * @param config 配置对象
174
+ * @param cwd 工作目录,默认为 process.cwd()
175
+ */
176
+ export function writeConfigSync(config: Partial<SpaceflowConfig>, cwd?: string): void {
177
+ const configPath = getConfigPath(cwd);
178
+ writeFileSync(configPath, stringify(config, { indent: 2 }) + "\n");
179
+ }
180
+
181
+ /**
182
+ * 获取支持的编辑器列表
183
+ * @param cwd 工作目录,默认为 process.cwd()
184
+ */
185
+ export function getSupportedEditors(cwd?: string): string[] {
186
+ const config = readConfigSync(cwd);
187
+ return config.support || [DEFAULT_SUPPORT_EDITOR];
188
+ }
189
+
190
+ /**
191
+ * 获取 dependencies
192
+ * @param cwd 工作目录,默认为 process.cwd()
193
+ */
194
+ export function getDependencies(cwd?: string): Record<string, string> {
195
+ const config = readConfigSync(cwd);
196
+ return (config.dependencies as Record<string, string>) || {};
197
+ }
198
+
199
+ /**
200
+ * 更新单个 dependency
201
+ * @param name 依赖名称
202
+ * @param source 依赖来源
203
+ * @param cwd 工作目录,默认为 process.cwd()
204
+ * @returns 是否有更新(false 表示已存在相同配置)
205
+ */
206
+ export function updateDependency(name: string, source: string, cwd?: string): boolean {
207
+ const config = readConfigSync(cwd) as Record<string, unknown>;
208
+
209
+ if (!config.dependencies) {
210
+ config.dependencies = {};
211
+ }
212
+
213
+ const dependencies = config.dependencies as Record<string, string>;
214
+
215
+ // 检查是否已存在相同配置
216
+ if (dependencies[name] === source) {
217
+ return false;
218
+ }
219
+
220
+ dependencies[name] = source;
221
+ writeConfigSync(config, cwd);
222
+ return true;
223
+ }
224
+
225
+ /**
226
+ * 删除单个 dependency
227
+ * @param name 依赖名称
228
+ * @param cwd 工作目录,默认为 process.cwd()
229
+ * @returns 是否有删除(false 表示不存在)
230
+ */
231
+ export function removeDependency(name: string, cwd?: string): boolean {
232
+ const config = readConfigSync(cwd) as Record<string, unknown>;
233
+
234
+ if (!config.dependencies) {
235
+ return false;
236
+ }
237
+
238
+ const dependencies = config.dependencies as Record<string, string>;
239
+
240
+ if (!(name in dependencies)) {
241
+ return false;
242
+ }
243
+
244
+ delete dependencies[name];
245
+ writeConfigSync(config, cwd);
246
+ return true;
247
+ }
248
+
249
+ export const spaceflowConfig = registerAs("spaceflow", (): SpaceflowConfig => {
250
+ const fileConfig = readConfigSync();
251
+
252
+ // 使用 zod 验证核心配置
253
+ const result = SpaceflowCoreConfigSchema.safeParse(fileConfig);
254
+
255
+ if (!result.success) {
256
+ const errors = result.error.issues
257
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
258
+ .join("\n");
259
+ throw new Error(`Spaceflow 配置验证失败:\n${errors}`);
260
+ }
261
+
262
+ // 返回验证后的核心配置 + 其他插件配置
263
+ return {
264
+ ...fileConfig,
265
+ ...result.data,
266
+ } as SpaceflowConfig;
267
+ });
268
+
269
+ /**
270
+ * 加载 spaceflow.json 配置(用于 CLI 启动时)
271
+ * 使用 zod 验证配置
272
+ */
273
+ export function loadSpaceflowConfig(): SpaceflowConfig {
274
+ const fileConfig = readConfigSync();
275
+
276
+ // 使用 zod 验证核心配置
277
+ const result = SpaceflowCoreConfigSchema.safeParse(fileConfig);
278
+
279
+ if (!result.success) {
280
+ const errors = result.error.issues
281
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
282
+ .join("\n");
283
+ throw new Error(`Spaceflow 配置验证失败:\n${errors}`);
284
+ }
285
+
286
+ return {
287
+ ...fileConfig,
288
+ ...result.data,
289
+ } as SpaceflowConfig;
290
+ }
291
+
292
+ export type { LLMMode };
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { createConfigLoader } from "./config-loader";
3
+
4
+ /** Storage 配置 Schema */
5
+ const schemaFactory = () =>
6
+ z.object({
7
+ /** 适配器类型:memory 或 file */
8
+ adapter: z
9
+ .enum(["memory", "file"])
10
+ .default((process.env.STORAGE_ADAPTER as "memory" | "file") || "memory")
11
+ .describe("适配器类型"),
12
+ /** 文件适配器的存储路径(仅 file 适配器需要) */
13
+ filePath: z.string().optional().describe("文件存储路径"),
14
+ /** 默认过期时间(毫秒),0 表示永不过期 */
15
+ defaultTtl: z
16
+ .number()
17
+ .default(process.env.STORAGE_DEFAULT_TTL ? parseInt(process.env.STORAGE_DEFAULT_TTL, 10) : 0)
18
+ .describe("默认过期时间(毫秒)"),
19
+ /** 最大 key 数量,0 表示不限制 */
20
+ maxKeys: z
21
+ .number()
22
+ .default(process.env.STORAGE_MAX_KEYS ? parseInt(process.env.STORAGE_MAX_KEYS, 10) : 0)
23
+ .describe("最大 key 数量"),
24
+ });
25
+
26
+ /** Storage 配置类型 */
27
+ export type StorageConfig = z.infer<ReturnType<typeof schemaFactory>>;
28
+
29
+ export const storageConfig = createConfigLoader({
30
+ configKey: "storage",
31
+ schemaFactory,
32
+ description: "存储服务配置",
33
+ });