@spaceflow/core 0.7.0 → 0.9.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/dist/index.js +5621 -1027
- package/dist/index.js.map +1 -1
- package/package.json +9 -3
- package/src/cli-runtime/di/config.ts +157 -0
- package/src/cli-runtime/di/container.ts +120 -0
- package/src/cli-runtime/di/index.ts +3 -0
- package/src/cli-runtime/di/services.ts +53 -0
- package/src/cli-runtime/extension-loader.ts +74 -0
- package/src/cli-runtime/i18n/i18n.ts +89 -0
- package/src/cli-runtime/i18n/index.ts +20 -0
- package/src/cli-runtime/i18n/init.ts +117 -0
- package/src/cli-runtime/index.ts +131 -0
- package/src/cli-runtime/internal-extensions.ts +33 -0
- package/src/commands/build/build.service.ts +323 -0
- package/src/commands/build/index.ts +49 -0
- package/src/commands/clear/clear.service.ts +159 -0
- package/src/commands/clear/index.ts +35 -0
- package/src/commands/commit/commit.config.ts +168 -0
- package/src/commands/commit/commit.service.ts +950 -0
- package/src/commands/commit/index.ts +58 -0
- package/src/commands/create/create.service.ts +318 -0
- package/src/commands/create/index.ts +42 -0
- package/src/commands/dev/index.ts +30 -0
- package/src/commands/install/index.ts +65 -0
- package/src/commands/install/install.service.ts +1539 -0
- package/src/commands/list/index.ts +33 -0
- package/src/commands/list/list.service.ts +127 -0
- package/src/commands/mcp/index.ts +37 -0
- package/src/commands/mcp/mcp.service.ts +246 -0
- package/src/commands/runx/index.ts +47 -0
- package/src/commands/runx/runx.service.ts +142 -0
- package/src/commands/runx/runx.utils.ts +83 -0
- package/src/commands/schema/index.ts +30 -0
- package/src/commands/setup/index.ts +34 -0
- package/src/commands/setup/setup.service.ts +234 -0
- package/src/commands/uninstall/index.ts +42 -0
- package/src/commands/uninstall/uninstall.service.ts +166 -0
- package/src/commands/update/index.ts +42 -0
- package/src/commands/update/update.service.ts +373 -0
- package/src/config/index.ts +1 -30
- package/src/config/spaceflow.config.ts +226 -278
- package/src/index.ts +11 -1
- package/src/locales/en/build.json +22 -0
- package/src/locales/en/clear.json +16 -0
- package/src/locales/en/commit.json +45 -0
- package/src/locales/en/create.json +27 -0
- package/src/locales/en/dev.json +5 -0
- package/src/locales/en/install.json +71 -0
- package/src/locales/en/list.json +8 -0
- package/src/locales/en/mcp.json +19 -0
- package/src/locales/en/runx.json +13 -0
- package/src/locales/en/schema.json +4 -0
- package/src/locales/en/setup.json +14 -0
- package/src/locales/en/uninstall.json +18 -0
- package/src/locales/en/update.json +28 -0
- package/src/locales/zh-cn/build.json +22 -0
- package/src/locales/zh-cn/clear.json +16 -0
- package/src/locales/zh-cn/commit.json +45 -0
- package/src/locales/zh-cn/create.json +27 -0
- package/src/locales/zh-cn/dev.json +5 -0
- package/src/locales/zh-cn/install.json +71 -0
- package/src/locales/zh-cn/list.json +8 -0
- package/src/locales/zh-cn/mcp.json +19 -0
- package/src/locales/zh-cn/runx.json +13 -0
- package/src/locales/zh-cn/schema.json +4 -0
- package/src/locales/zh-cn/setup.json +14 -0
- package/src/locales/zh-cn/uninstall.json +18 -0
- package/src/locales/zh-cn/update.json +28 -0
- package/src/shared/editor-config/index.ts +2 -21
- package/src/shared/llm-proxy/adapters/openai.adapter.ts +3 -1
- package/src/shared/package-manager/index.ts +5 -76
- package/src/shared/source-utils/index.ts +12 -130
- package/src/shared/spaceflow-dir/index.ts +13 -135
- package/src/shared/verbose/index.ts +10 -87
- package/dist/524.js +0 -9
- package/src/config/ci.config.ts +0 -29
- package/src/config/config-loader.ts +0 -100
- package/src/config/config-reader.service.ts +0 -128
- package/src/config/config-reader.ts +0 -75
- package/src/config/feishu.config.ts +0 -35
- package/src/config/git-provider.config.ts +0 -29
- package/src/config/llm.config.ts +0 -110
- package/src/config/load-env.ts +0 -15
- package/src/config/storage.config.ts +0 -33
- package/src/shared/i18n/i18n.ts +0 -112
- package/src/shared/i18n/index.ts +0 -1
- /package/src/{shared → cli-runtime}/i18n/i18n.spec.ts +0 -0
- /package/src/{shared → cli-runtime}/i18n/locale-detect.ts +0 -0
|
@@ -1,316 +1,256 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import stringify from "json-stringify-pretty-compact";
|
|
5
1
|
import { z } from "zod";
|
|
6
2
|
import type { LLMMode } from "../shared/llm-proxy";
|
|
7
3
|
import { registerPluginSchema } from "./schema-generator.service";
|
|
4
|
+
import { detectProvider } from "../shared/git-provider/detect-provider";
|
|
5
|
+
|
|
6
|
+
// 从 @spaceflow/shared 重导出配置工具函数
|
|
7
|
+
export {
|
|
8
|
+
DEFAULT_SUPPORT_EDITOR,
|
|
9
|
+
CONFIG_FILE_NAME,
|
|
10
|
+
RC_FILE_NAME,
|
|
11
|
+
deepMerge,
|
|
12
|
+
getConfigPath,
|
|
13
|
+
getConfigPaths,
|
|
14
|
+
getEnvFilePaths,
|
|
15
|
+
readConfigSync,
|
|
16
|
+
writeConfigSync,
|
|
17
|
+
getSupportedEditors,
|
|
18
|
+
getDependencies,
|
|
19
|
+
findConfigFileWithField,
|
|
20
|
+
updateDependency,
|
|
21
|
+
removeDependency,
|
|
22
|
+
} from "@spaceflow/shared";
|
|
23
|
+
|
|
24
|
+
import { DEFAULT_SUPPORT_EDITOR, readConfigSync } from "@spaceflow/shared";
|
|
25
|
+
|
|
26
|
+
// ============ 子模块配置 Schema ============
|
|
27
|
+
|
|
28
|
+
/** 从环境自动检测的默认值 */
|
|
29
|
+
const detected = detectProvider();
|
|
30
|
+
|
|
31
|
+
/** Git Provider 配置 Schema */
|
|
32
|
+
const GitProviderConfigSchema = z.object({
|
|
33
|
+
provider: z
|
|
34
|
+
.enum(["gitea", "github", "gitlab"])
|
|
35
|
+
.default(detected.provider)
|
|
36
|
+
.describe("Git Provider 类型 (github | gitea | gitlab),未指定时自动检测"),
|
|
37
|
+
serverUrl: z.string().default(detected.serverUrl).describe("Git Provider 服务器 URL"),
|
|
38
|
+
token: z.string().default(detected.token).describe("Git Provider API Token"),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
/** CI 配置 Schema */
|
|
42
|
+
const CiConfigSchema = z.object({
|
|
43
|
+
repository: z
|
|
44
|
+
.string()
|
|
45
|
+
.default(process.env.GITHUB_REPOSITORY || "")
|
|
46
|
+
.describe("仓库名称 (owner/repo 格式)"),
|
|
47
|
+
refName: z
|
|
48
|
+
.string()
|
|
49
|
+
.default(process.env.GITHUB_REF_NAME || "")
|
|
50
|
+
.describe("当前分支名称"),
|
|
51
|
+
actor: z
|
|
52
|
+
.string()
|
|
53
|
+
.default(process.env.GITHUB_ACTOR || "")
|
|
54
|
+
.describe("当前操作者"),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** Claude Code 适配器配置 Schema */
|
|
58
|
+
const ClaudeCodeConfigSchema = z.object({
|
|
59
|
+
baseUrl: z
|
|
60
|
+
.string()
|
|
61
|
+
.default(process.env.CLAUDE_CODE_BASE_URL || "")
|
|
62
|
+
.describe("API 基础 URL"),
|
|
63
|
+
authToken: z
|
|
64
|
+
.string()
|
|
65
|
+
.default(process.env.CLAUDE_CODE_AUTH_TOKEN || "")
|
|
66
|
+
.describe("认证令牌"),
|
|
67
|
+
model: z
|
|
68
|
+
.string()
|
|
69
|
+
.default(process.env.CLAUDE_CODE_MODEL || "claude-sonnet-4-5")
|
|
70
|
+
.describe("模型名称"),
|
|
71
|
+
hasCompletedOnboarding: z.boolean().optional().describe("是否已完成 Claude Code 引导流程"),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
/** OpenAI 适配器配置 Schema */
|
|
75
|
+
const OpenAIConfigSchema = z.object({
|
|
76
|
+
baseUrl: z
|
|
77
|
+
.string()
|
|
78
|
+
.default(process.env.OPENAI_BASE_URL || "")
|
|
79
|
+
.describe("API 基础 URL"),
|
|
80
|
+
apiKey: z
|
|
81
|
+
.string()
|
|
82
|
+
.default(process.env.OPENAI_API_KEY || "")
|
|
83
|
+
.describe("API Key"),
|
|
84
|
+
model: z
|
|
85
|
+
.string()
|
|
86
|
+
.default(process.env.OPENAI_MODEL || "gpt-4o")
|
|
87
|
+
.describe("模型名称"),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** OpenCode 适配器配置 Schema */
|
|
91
|
+
const OpenCodeConfigSchema = z.object({
|
|
92
|
+
serverUrl: z
|
|
93
|
+
.string()
|
|
94
|
+
.default(process.env.OPENCODE_SERVER_URL || "http://localhost:4096")
|
|
95
|
+
.describe("服务器 URL"),
|
|
96
|
+
baseUrl: z
|
|
97
|
+
.string()
|
|
98
|
+
.default(process.env.OPENCODE_BASE_URL || "")
|
|
99
|
+
.describe("API 基础 URL"),
|
|
100
|
+
apiKey: z
|
|
101
|
+
.string()
|
|
102
|
+
.default(process.env.OPENCODE_API_KEY || "")
|
|
103
|
+
.describe("API Key"),
|
|
104
|
+
providerID: z
|
|
105
|
+
.string()
|
|
106
|
+
.default(process.env.OPENCODE_PROVIDER_ID || "openai")
|
|
107
|
+
.describe("Provider ID"),
|
|
108
|
+
model: z
|
|
109
|
+
.string()
|
|
110
|
+
.default(process.env.OPENCODE_MODEL || "")
|
|
111
|
+
.describe("模型名称"),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/** Gemini 适配器配置 Schema */
|
|
115
|
+
const GeminiConfigSchema = z.object({
|
|
116
|
+
baseUrl: z
|
|
117
|
+
.string()
|
|
118
|
+
.default(process.env.GEMINI_BASE_URL || "")
|
|
119
|
+
.describe("API 基础 URL"),
|
|
120
|
+
apiKey: z
|
|
121
|
+
.string()
|
|
122
|
+
.default(process.env.GEMINI_API_KEY || "")
|
|
123
|
+
.describe("API Key"),
|
|
124
|
+
model: z
|
|
125
|
+
.string()
|
|
126
|
+
.default(process.env.GEMINI_MODEL || "")
|
|
127
|
+
.describe("模型名称"),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
/** LLM 配置 Schema */
|
|
131
|
+
const LlmConfigSchema = z.object({
|
|
132
|
+
claudeCode: z
|
|
133
|
+
.preprocess((v) => v ?? {}, ClaudeCodeConfigSchema)
|
|
134
|
+
.describe("Claude Code 适配器配置"),
|
|
135
|
+
openai: z.preprocess((v) => v ?? {}, OpenAIConfigSchema).describe("OpenAI 适配器配置"),
|
|
136
|
+
openCode: z.preprocess((v) => v ?? {}, OpenCodeConfigSchema).describe("OpenCode 适配器配置"),
|
|
137
|
+
gemini: z.preprocess((v) => v ?? {}, GeminiConfigSchema).describe("Gemini 适配器配置"),
|
|
138
|
+
});
|
|
8
139
|
|
|
9
|
-
/**
|
|
10
|
-
|
|
140
|
+
/** 飞书配置 Schema */
|
|
141
|
+
const FeishuConfigSchema = z.object({
|
|
142
|
+
appId: z
|
|
143
|
+
.string()
|
|
144
|
+
.default(process.env.FEISHU_APP_ID || "")
|
|
145
|
+
.describe("飞书应用 ID"),
|
|
146
|
+
appSecret: z
|
|
147
|
+
.string()
|
|
148
|
+
.default(process.env.FEISHU_APP_SECRET || "")
|
|
149
|
+
.describe("飞书应用密钥"),
|
|
150
|
+
appType: z
|
|
151
|
+
.enum(["self_build", "store"])
|
|
152
|
+
.default((process.env.FEISHU_APP_TYPE as "self_build" | "store") || "self_build")
|
|
153
|
+
.describe("应用类型"),
|
|
154
|
+
domain: z
|
|
155
|
+
.enum(["feishu", "lark"])
|
|
156
|
+
.default((process.env.FEISHU_DOMAIN as "feishu" | "lark") || "feishu")
|
|
157
|
+
.describe("域名"),
|
|
158
|
+
});
|
|
11
159
|
|
|
12
|
-
/**
|
|
13
|
-
|
|
160
|
+
/** Storage 配置 Schema */
|
|
161
|
+
const StorageConfigSchema = z.object({
|
|
162
|
+
adapter: z
|
|
163
|
+
.enum(["memory", "file"])
|
|
164
|
+
.default((process.env.STORAGE_ADAPTER as "memory" | "file") || "memory")
|
|
165
|
+
.describe("适配器类型"),
|
|
166
|
+
filePath: z.string().optional().describe("文件存储路径"),
|
|
167
|
+
defaultTtl: z
|
|
168
|
+
.number()
|
|
169
|
+
.default(process.env.STORAGE_DEFAULT_TTL ? parseInt(process.env.STORAGE_DEFAULT_TTL, 10) : 0)
|
|
170
|
+
.describe("默认过期时间(毫秒)"),
|
|
171
|
+
maxKeys: z
|
|
172
|
+
.number()
|
|
173
|
+
.default(process.env.STORAGE_MAX_KEYS ? parseInt(process.env.STORAGE_MAX_KEYS, 10) : 0)
|
|
174
|
+
.describe("最大 key 数量"),
|
|
175
|
+
});
|
|
14
176
|
|
|
15
|
-
|
|
16
|
-
export const RC_FILE_NAME = ".spaceflowrc";
|
|
177
|
+
// ============ 统一配置 Schema ============
|
|
17
178
|
|
|
18
|
-
/** Spaceflow
|
|
19
|
-
const
|
|
179
|
+
/** Spaceflow 完整配置 Schema */
|
|
180
|
+
const SpaceflowConfigSchema = z.object({
|
|
20
181
|
/** 界面语言,如 zh-CN、en */
|
|
21
182
|
lang: z.string().optional().describe("界面语言,如 zh-CN、en"),
|
|
22
183
|
/** 已安装的技能包注册表 */
|
|
23
184
|
dependencies: z.record(z.string(), z.string()).optional().describe("已安装的技能包注册表"),
|
|
24
|
-
/**
|
|
185
|
+
/** 支持的编辑器列表 */
|
|
25
186
|
support: z.array(z.string()).default([DEFAULT_SUPPORT_EDITOR]).describe("支持的编辑器列表"),
|
|
187
|
+
/** Git Provider 配置 */
|
|
188
|
+
gitProvider: z
|
|
189
|
+
.preprocess((v) => v ?? {}, GitProviderConfigSchema)
|
|
190
|
+
.describe("Git Provider 服务配置"),
|
|
191
|
+
/** CI 配置 */
|
|
192
|
+
ci: z.preprocess((v) => v ?? {}, CiConfigSchema).describe("CI 环境配置"),
|
|
193
|
+
/** LLM 配置 */
|
|
194
|
+
llm: z.preprocess((v) => v ?? {}, LlmConfigSchema).describe("LLM 服务配置"),
|
|
195
|
+
/** 飞书配置 */
|
|
196
|
+
feishu: z.preprocess((v) => v ?? {}, FeishuConfigSchema).describe("飞书 SDK 配置"),
|
|
197
|
+
/** Storage 配置 */
|
|
198
|
+
storage: z.preprocess((v) => v ?? {}, StorageConfigSchema).describe("存储服务配置"),
|
|
26
199
|
});
|
|
27
200
|
|
|
28
|
-
//
|
|
201
|
+
// 注册完整 schema(供 JSON Schema 生成使用)
|
|
29
202
|
registerPluginSchema({
|
|
30
203
|
configKey: "spaceflow",
|
|
31
|
-
schemaFactory: () =>
|
|
32
|
-
description: "Spaceflow
|
|
204
|
+
schemaFactory: () => SpaceflowConfigSchema,
|
|
205
|
+
description: "Spaceflow 配置",
|
|
33
206
|
});
|
|
34
207
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/** 子命令配置,由各子命令模块自行定义类型 */
|
|
208
|
+
// ============ 类型导出 ============
|
|
209
|
+
|
|
210
|
+
/** Spaceflow 完整配置类型 */
|
|
211
|
+
export type SpaceflowConfig = z.infer<typeof SpaceflowConfigSchema> & {
|
|
212
|
+
/** 扩展插件配置,由各插件自行定义类型 */
|
|
41
213
|
[key: string]: unknown;
|
|
42
214
|
};
|
|
43
215
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// 不应该被深度合并的字段,这些字段应该直接覆盖而非合并
|
|
47
|
-
const NO_MERGE_FIELDS = ["dependencies"];
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 深度合并对象
|
|
51
|
-
* 后面的对象会覆盖前面的对象,数组会被替换而非合并
|
|
52
|
-
* NO_MERGE_FIELDS 中的字段不会被深度合并,而是直接覆盖
|
|
53
|
-
*/
|
|
54
|
-
function deepMerge<T extends Record<string, unknown>>(...objects: Partial<T>[]): Partial<T> {
|
|
55
|
-
const result: Record<string, unknown> = {};
|
|
56
|
-
|
|
57
|
-
for (const obj of objects) {
|
|
58
|
-
for (const key in obj) {
|
|
59
|
-
const value = obj[key];
|
|
60
|
-
const existing = result[key];
|
|
61
|
-
|
|
62
|
-
// 对于 NO_MERGE_FIELDS 中的字段,直接覆盖而非合并
|
|
63
|
-
if (NO_MERGE_FIELDS.includes(key)) {
|
|
64
|
-
if (value !== undefined) {
|
|
65
|
-
result[key] = value;
|
|
66
|
-
}
|
|
67
|
-
} else if (
|
|
68
|
-
value !== null &&
|
|
69
|
-
typeof value === "object" &&
|
|
70
|
-
!Array.isArray(value) &&
|
|
71
|
-
existing !== null &&
|
|
72
|
-
typeof existing === "object" &&
|
|
73
|
-
!Array.isArray(existing)
|
|
74
|
-
) {
|
|
75
|
-
result[key] = deepMerge(
|
|
76
|
-
existing as Record<string, unknown>,
|
|
77
|
-
value as Record<string, unknown>,
|
|
78
|
-
);
|
|
79
|
-
} else if (value !== undefined) {
|
|
80
|
-
result[key] = value;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return result as Partial<T>;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* 获取主配置文件路径(用于写入)
|
|
90
|
-
* 配置文件统一存放在 .spaceflow/ 目录下
|
|
91
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
92
|
-
*/
|
|
93
|
-
export function getConfigPath(cwd?: string): string {
|
|
94
|
-
return join(cwd || process.cwd(), ".spaceflow", CONFIG_FILE_NAME);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 获取所有配置文件路径(按优先级从低到高排列)
|
|
99
|
-
* 优先级: ~/.spaceflow/spaceflow.json < ~/.spaceflowrc < ./.spaceflow/spaceflow.json < ./.spaceflowrc
|
|
100
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
101
|
-
*/
|
|
102
|
-
export function getConfigPaths(cwd?: string): string[] {
|
|
103
|
-
const workDir = cwd || process.cwd();
|
|
104
|
-
return [
|
|
105
|
-
join(homedir(), ".spaceflow", CONFIG_FILE_NAME),
|
|
106
|
-
join(homedir(), RC_FILE_NAME),
|
|
107
|
-
join(workDir, ".spaceflow", CONFIG_FILE_NAME),
|
|
108
|
-
join(workDir, RC_FILE_NAME),
|
|
109
|
-
];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** .env 文件名 */
|
|
113
|
-
const ENV_FILE_NAME = ".env";
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 获取所有 .env 文件路径(按优先级从高到低排列,供 ConfigModule.envFilePath 使用)
|
|
117
|
-
*
|
|
118
|
-
* NestJS ConfigModule 中 envFilePath 数组靠前的优先级更高(先读到的变量不会被后面覆盖)
|
|
119
|
-
* 因此返回顺序为从高到低:
|
|
120
|
-
* 1. ./.env (程序启动目录,最高优先级)
|
|
121
|
-
* 2. ./.spaceflow/.env (项目配置目录)
|
|
122
|
-
* 3. ~/.env (全局 home 目录)
|
|
123
|
-
* 4. ~/.spaceflow/.env (全局配置目录,最低优先级)
|
|
124
|
-
*
|
|
125
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
126
|
-
*/
|
|
127
|
-
export function getEnvFilePaths(cwd?: string): string[] {
|
|
128
|
-
const workDir = cwd || process.cwd();
|
|
129
|
-
return [
|
|
130
|
-
join(workDir, ENV_FILE_NAME),
|
|
131
|
-
join(workDir, ".spaceflow", ENV_FILE_NAME),
|
|
132
|
-
join(homedir(), ENV_FILE_NAME),
|
|
133
|
-
join(homedir(), ".spaceflow", ENV_FILE_NAME),
|
|
134
|
-
];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 读取单个配置文件(同步)
|
|
139
|
-
* @param configPath 配置文件路径
|
|
140
|
-
*/
|
|
141
|
-
function readSingleConfigSync(configPath: string): Partial<SpaceflowConfig> {
|
|
142
|
-
if (!existsSync(configPath)) {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const content = readFileSync(configPath, "utf-8");
|
|
148
|
-
return JSON.parse(content);
|
|
149
|
-
} catch {
|
|
150
|
-
console.warn(`警告: 无法解析配置文件 ${configPath}`);
|
|
151
|
-
return {};
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* 读取配置文件(同步)
|
|
157
|
-
* 按优先级从低到高读取并合并配置:
|
|
158
|
-
* 1. ~/.spaceflow/spaceflow.json (全局配置,最低优先级)
|
|
159
|
-
* 2. ~/.spaceflowrc (全局 RC 配置)
|
|
160
|
-
* 3. ./.spaceflow/spaceflow.json (项目配置)
|
|
161
|
-
* 4. ./.spaceflowrc (项目根目录 RC 配置,最高优先级)
|
|
162
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
163
|
-
*/
|
|
164
|
-
export function readConfigSync(cwd?: string): Partial<SpaceflowConfig> {
|
|
165
|
-
const configPaths = getConfigPaths(cwd);
|
|
166
|
-
const configs = configPaths.map((p) => readSingleConfigSync(p));
|
|
167
|
-
return deepMerge(...configs);
|
|
168
|
-
}
|
|
216
|
+
/** Git Provider 配置类型 */
|
|
217
|
+
export type GitProviderConfig = z.infer<typeof GitProviderConfigSchema>;
|
|
169
218
|
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
* @param config 配置对象
|
|
173
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
174
|
-
*/
|
|
175
|
-
export function writeConfigSync(config: Partial<SpaceflowConfig>, cwd?: string): void {
|
|
176
|
-
const configPath = getConfigPath(cwd);
|
|
177
|
-
writeFileSync(configPath, stringify(config, { indent: 2 }) + "\n");
|
|
178
|
-
}
|
|
219
|
+
/** CI 配置类型 */
|
|
220
|
+
export type CiConfig = z.infer<typeof CiConfigSchema>;
|
|
179
221
|
|
|
180
|
-
/**
|
|
181
|
-
|
|
182
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
183
|
-
*/
|
|
184
|
-
export function getSupportedEditors(cwd?: string): string[] {
|
|
185
|
-
const config = readConfigSync(cwd);
|
|
186
|
-
return config.support || [DEFAULT_SUPPORT_EDITOR];
|
|
187
|
-
}
|
|
222
|
+
/** LLM 系统配置类型 */
|
|
223
|
+
export type LlmConfig = z.infer<typeof LlmConfigSchema>;
|
|
188
224
|
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
192
|
-
*/
|
|
193
|
-
export function getDependencies(cwd?: string): Record<string, string> {
|
|
194
|
-
const config = readConfigSync(cwd);
|
|
195
|
-
return (config.dependencies as Record<string, string>) || {};
|
|
196
|
-
}
|
|
225
|
+
/** Claude Code 适配器配置类型 */
|
|
226
|
+
export type ClaudeCodeConfig = z.infer<typeof ClaudeCodeConfigSchema>;
|
|
197
227
|
|
|
198
|
-
/**
|
|
199
|
-
|
|
200
|
-
* 如果没有找到,返回项目级 .spaceflowrc 路径(默认写入位置)
|
|
201
|
-
* @param field 要查找的字段名
|
|
202
|
-
* @param cwd 工作目录
|
|
203
|
-
*/
|
|
204
|
-
export function findConfigFileWithField(field: string, cwd?: string): string {
|
|
205
|
-
const workDir = cwd || process.cwd();
|
|
206
|
-
// 按优先级从高到低查找,找到第一个包含该字段的文件
|
|
207
|
-
const candidates = [
|
|
208
|
-
join(workDir, RC_FILE_NAME),
|
|
209
|
-
join(workDir, ".spaceflow", CONFIG_FILE_NAME),
|
|
210
|
-
join(homedir(), RC_FILE_NAME),
|
|
211
|
-
join(homedir(), ".spaceflow", CONFIG_FILE_NAME),
|
|
212
|
-
];
|
|
213
|
-
|
|
214
|
-
for (const filePath of candidates) {
|
|
215
|
-
if (existsSync(filePath)) {
|
|
216
|
-
try {
|
|
217
|
-
const content = readFileSync(filePath, "utf-8");
|
|
218
|
-
const config = JSON.parse(content);
|
|
219
|
-
if (config[field] !== undefined) {
|
|
220
|
-
return filePath;
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
// 解析失败,跳过
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
228
|
+
/** OpenAI 适配器配置类型 */
|
|
229
|
+
export type OpenAIConfig = z.infer<typeof OpenAIConfigSchema>;
|
|
227
230
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
+
/** OpenCode 适配器配置类型 */
|
|
232
|
+
export type OpenCodeConfig = z.infer<typeof OpenCodeConfigSchema>;
|
|
231
233
|
|
|
232
|
-
/**
|
|
233
|
-
|
|
234
|
-
* 找到 dependencies 所在的配置文件并原地更新,默认写入 .spaceflowrc
|
|
235
|
-
* @param name 依赖名称
|
|
236
|
-
* @param source 依赖来源
|
|
237
|
-
* @param cwd 工作目录,默认为 process.cwd()
|
|
238
|
-
* @returns 是否有更新(false 表示已存在相同配置)
|
|
239
|
-
*/
|
|
240
|
-
export function updateDependency(name: string, source: string, cwd?: string): boolean {
|
|
241
|
-
const targetFile = findConfigFileWithField("dependencies", cwd);
|
|
242
|
-
const config = existsSync(targetFile)
|
|
243
|
-
? (JSON.parse(readFileSync(targetFile, "utf-8")) as Record<string, unknown>)
|
|
244
|
-
: ({} as Record<string, unknown>);
|
|
245
|
-
|
|
246
|
-
if (!config.dependencies) {
|
|
247
|
-
config.dependencies = {};
|
|
248
|
-
}
|
|
234
|
+
/** Gemini 适配器配置类型 */
|
|
235
|
+
export type GeminiConfig = z.infer<typeof GeminiConfigSchema>;
|
|
249
236
|
|
|
250
|
-
|
|
237
|
+
/** 飞书配置类型 */
|
|
238
|
+
export type FeishuConfig = z.infer<typeof FeishuConfigSchema>;
|
|
251
239
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
240
|
+
/** Storage 配置类型 */
|
|
241
|
+
export type StorageConfig = z.infer<typeof StorageConfigSchema>;
|
|
256
242
|
|
|
257
|
-
|
|
258
|
-
writeFileSync(targetFile, stringify(config, { indent: 2 }) + "\n");
|
|
259
|
-
return true;
|
|
260
|
-
}
|
|
243
|
+
// ============ 配置加载 ============
|
|
261
244
|
|
|
262
245
|
/**
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* @param name 依赖名称
|
|
246
|
+
* 加载 spaceflow.json 配置
|
|
247
|
+
* 从多级配置文件读取并合并,使用 zod 验证和填充默认值
|
|
266
248
|
* @param cwd 工作目录,默认为 process.cwd()
|
|
267
|
-
* @returns 是否有删除(false 表示不存在)
|
|
268
|
-
*/
|
|
269
|
-
export function removeDependency(name: string, cwd?: string): boolean {
|
|
270
|
-
const targetFile = findConfigFileWithField("dependencies", cwd);
|
|
271
|
-
if (!existsSync(targetFile)) {
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
let config: Record<string, unknown>;
|
|
276
|
-
try {
|
|
277
|
-
config = JSON.parse(readFileSync(targetFile, "utf-8"));
|
|
278
|
-
} catch {
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (!config.dependencies) {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const dependencies = config.dependencies as Record<string, string>;
|
|
287
|
-
|
|
288
|
-
if (!(name in dependencies)) {
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
delete dependencies[name];
|
|
293
|
-
writeFileSync(targetFile, stringify(config, { indent: 2 }) + "\n");
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* 获取 spaceflow 配置(兼容旧 API)
|
|
299
|
-
* @deprecated 请使用 loadSpaceflowConfig()
|
|
300
|
-
*/
|
|
301
|
-
export function spaceflowConfig(): SpaceflowConfig {
|
|
302
|
-
return loadSpaceflowConfig();
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* 加载 spaceflow.json 配置(用于 CLI 启动时)
|
|
307
|
-
* 使用 zod 验证配置
|
|
308
249
|
*/
|
|
309
|
-
export function loadSpaceflowConfig(): SpaceflowConfig {
|
|
310
|
-
const fileConfig = readConfigSync();
|
|
250
|
+
export function loadSpaceflowConfig(cwd?: string): SpaceflowConfig {
|
|
251
|
+
const fileConfig = readConfigSync(cwd);
|
|
311
252
|
|
|
312
|
-
|
|
313
|
-
const result = SpaceflowCoreConfigSchema.safeParse(fileConfig);
|
|
253
|
+
const result = SpaceflowConfigSchema.safeParse(fileConfig);
|
|
314
254
|
|
|
315
255
|
if (!result.success) {
|
|
316
256
|
const errors = result.error.issues
|
|
@@ -325,4 +265,12 @@ export function loadSpaceflowConfig(): SpaceflowConfig {
|
|
|
325
265
|
} as SpaceflowConfig;
|
|
326
266
|
}
|
|
327
267
|
|
|
268
|
+
/**
|
|
269
|
+
* 获取 spaceflow 配置(兼容旧 API)
|
|
270
|
+
* @deprecated 请使用 loadSpaceflowConfig()
|
|
271
|
+
*/
|
|
272
|
+
export function spaceflowConfig(): SpaceflowConfig {
|
|
273
|
+
return loadSpaceflowConfig();
|
|
274
|
+
}
|
|
275
|
+
|
|
328
276
|
export type { LLMMode };
|
package/src/index.ts
CHANGED
|
@@ -65,7 +65,7 @@ export {
|
|
|
65
65
|
} from "./shared/mcp";
|
|
66
66
|
|
|
67
67
|
// I18n - 国际化
|
|
68
|
-
export * from "./
|
|
68
|
+
export * from "./cli-runtime/i18n";
|
|
69
69
|
|
|
70
70
|
// Logger - 全局日志工具
|
|
71
71
|
export * from "./shared/logger";
|
|
@@ -73,5 +73,15 @@ export * from "./shared/logger";
|
|
|
73
73
|
// ============ 配置相关 ============
|
|
74
74
|
export * from "./config";
|
|
75
75
|
|
|
76
|
+
// ============ CLI Runtime ============
|
|
77
|
+
// exec 入口、DI 容器、扩展加载器、i18n 初始化
|
|
78
|
+
export {
|
|
79
|
+
exec,
|
|
80
|
+
ServiceContainer,
|
|
81
|
+
ExtensionLoader,
|
|
82
|
+
initCliI18n,
|
|
83
|
+
internalExtensions,
|
|
84
|
+
} from "./cli-runtime";
|
|
85
|
+
|
|
76
86
|
// ============ Zod 重导出 ============
|
|
77
87
|
export { z } from "zod";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Build skill packages in the skills directory",
|
|
3
|
+
"options.watch": "Watch for file changes and rebuild automatically",
|
|
4
|
+
"buildFailed": "❌ Build failed: {{error}}",
|
|
5
|
+
"extensionDescription": "Build plugins/skills",
|
|
6
|
+
"noPlugins": "📦 No buildable plugins found",
|
|
7
|
+
"startBuilding": "📦 Building {{count}} plugins...",
|
|
8
|
+
"buildComplete": "✅ Build complete: {{success}} succeeded, {{fail}} failed",
|
|
9
|
+
"startWatching": "👀 Watching {{count}} plugins...",
|
|
10
|
+
"stopWatching": "🛑 Stop watching: {{name}}",
|
|
11
|
+
"building": "🔨 Building: {{name}}",
|
|
12
|
+
"buildSuccess": " ✅ Done ({{duration}}ms)",
|
|
13
|
+
"buildFailedWithDuration": " ❌ Failed ({{duration}}ms)",
|
|
14
|
+
"buildFailedWithMessage": " ❌ Failed ({{duration}}ms): {{message}}",
|
|
15
|
+
"buildWarnings": " ⚠️ Done ({{duration}}ms) - {{count}} warnings",
|
|
16
|
+
"watching": "👀 Watching: {{name}}",
|
|
17
|
+
"watchError": " ❌ [{{name}}] Error: {{message}}",
|
|
18
|
+
"watchBuildFailed": " ❌ [{{name}}] Build failed",
|
|
19
|
+
"watchBuildWarnings": " ⚠️ [{{name}}] Build complete - {{count}} warnings",
|
|
20
|
+
"watchBuildSuccess": " ✅ [{{name}}] Build complete",
|
|
21
|
+
"watchInitFailed": " ❌ [{{name}}] Init failed: {{message}}"
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Clear all installed skill packages",
|
|
3
|
+
"options.global": "Clear installed content in global directory (~/.spaceflow)",
|
|
4
|
+
"clearFailed": "❌ Clear failed: {{error}}",
|
|
5
|
+
"extensionDescription": "Clear cache and temporary files",
|
|
6
|
+
"clearingGlobal": "🧹 Clearing all skill packages (global)",
|
|
7
|
+
"clearing": "🧹 Clearing all skill packages",
|
|
8
|
+
"clearDone": "✅ Clear complete",
|
|
9
|
+
"spaceflowNotExist": " .spaceflow does not exist, skipping",
|
|
10
|
+
"spaceflowNoClean": " .spaceflow has nothing to clean",
|
|
11
|
+
"clearingSpaceflow": " Clearing .spaceflow ({{count}} items, preserving config files)",
|
|
12
|
+
"deleted": " Deleted: {{entry}}",
|
|
13
|
+
"deleteFailed": " Warning: Failed to delete {{entry}}:",
|
|
14
|
+
"clearingSkills": " Clearing {{editor}}/skills ({{count}} items)",
|
|
15
|
+
"clearingCommands": " Clearing {{editor}}/commands ({{count}} .md files)"
|
|
16
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Auto-generate conventional commit messages from staged changes",
|
|
3
|
+
"options.dryRun": "Only generate commit message without committing",
|
|
4
|
+
"options.noVerify": "Skip pre-commit and commit-msg hooks",
|
|
5
|
+
"options.split": "Split into multiple commits by package/logic/domain",
|
|
6
|
+
"dryRunMode": "\n🔍 Dry Run mode",
|
|
7
|
+
"splitSuccess": "\n✅ Successfully split into {{count}} commits",
|
|
8
|
+
"commitSuccess": "\n✅ Commit successful",
|
|
9
|
+
"commitFailed": "\n❌ Commit failed: {{error}}",
|
|
10
|
+
"extensionDescription": "Smart commit command using LLM to generate commit messages",
|
|
11
|
+
"getDiffFailed": "Failed to get staged diff, make sure you are in a git repository",
|
|
12
|
+
"noFilesToCommit": "No files to commit",
|
|
13
|
+
"noChanges": "No changes found",
|
|
14
|
+
"generatingMessage": "📝 Generating commit message with AI...",
|
|
15
|
+
"strategyRules": "custom rules",
|
|
16
|
+
"strategyRulesFirst": "rules-first",
|
|
17
|
+
"strategyPackage": "package directory",
|
|
18
|
+
"groupingByStrategy": "🔍 Grouping files by {{strategy}} strategy...",
|
|
19
|
+
"detectedGroups": "📦 Detected {{count}} groups of changes",
|
|
20
|
+
"scopeChanges": "{{scope}} changes",
|
|
21
|
+
"rootChanges": "Root directory changes",
|
|
22
|
+
"otherChanges": "Other changes",
|
|
23
|
+
"allChanges": "All changes",
|
|
24
|
+
"analyzingSplit": "🔍 Analyzing how to split commits within package...",
|
|
25
|
+
"dryRunMessage": "[Dry Run] Will commit the following message:\n{{message}}",
|
|
26
|
+
"commitFail": "Commit failed",
|
|
27
|
+
"stageFilesFailed": "Failed to stage files: {{error}}",
|
|
28
|
+
"noWorkingChanges": "No working directory changes",
|
|
29
|
+
"noStagedFiles": "No staged files",
|
|
30
|
+
"singleCommit": "📦 No split needed, committing as a single commit",
|
|
31
|
+
"generatedMessage": "\n📋 Generated commit message:",
|
|
32
|
+
"splitIntoCommits": "\n📦 Splitting into {{count}} commits",
|
|
33
|
+
"groupItem": " {{index}}. {{reason}}{{pkg}} ({{count}} files)",
|
|
34
|
+
"parallelGenerating": "\n🚀 Generating {{count}} commit messages in parallel...",
|
|
35
|
+
"allMessagesGenerated": "✅ All commit messages generated",
|
|
36
|
+
"generateMessageFailed": "Failed to generate commit messages: {{errors}}",
|
|
37
|
+
"resetStagingFailed": "Failed to reset staging area",
|
|
38
|
+
"committingGroup": "\n📝 Committing group {{current}}/{{total}}: {{reason}}{{pkg}}",
|
|
39
|
+
"skippingNoChanges": "⏭️ Skipping: no actual changes",
|
|
40
|
+
"commitMessage": "📋 Commit message:",
|
|
41
|
+
"commitItemFailed": "❌ Commit {{index}} failed: {{error}}",
|
|
42
|
+
"commitItemFailedDetail": "Commit {{index}} failed: {{error}}\n\nCompleted commits:\n{{committed}}",
|
|
43
|
+
"noChangesAll": "No changes found, please modify files or use git add first",
|
|
44
|
+
"noStagedFilesHint": "No staged files, please use git add first"
|
|
45
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Create a new plugin from template",
|
|
3
|
+
"options.directory": "Specify target directory",
|
|
4
|
+
"options.list": "List available templates",
|
|
5
|
+
"options.from": "Use a remote Git repository as template source",
|
|
6
|
+
"options.ref": "Specify Git branch or tag (default: main)",
|
|
7
|
+
"noName": "❌ Please specify a name",
|
|
8
|
+
"usage": "Usage: spaceflow create <template> <name>",
|
|
9
|
+
"createFailed": "❌ Creation failed: {{error}}",
|
|
10
|
+
"availableTemplates": "Available templates:",
|
|
11
|
+
"extensionDescription": "Create new plugin/skill from template",
|
|
12
|
+
"updatingRepo": "🔄 Updating template repository: {{url}}",
|
|
13
|
+
"updateFailed": "⚠️ Update failed, using cached version",
|
|
14
|
+
"cloningRepo": "📥 Cloning template repository: {{url}}",
|
|
15
|
+
"cloneFailed": "Failed to clone repository: {{error}}",
|
|
16
|
+
"repoReady": "✅ Template repository ready: {{dir}}",
|
|
17
|
+
"templatesDirNotFound": "Templates directory not found",
|
|
18
|
+
"creatingPlugin": "📦 Creating {{template}} plugin: {{name}}",
|
|
19
|
+
"targetDir": " Directory: {{dir}}",
|
|
20
|
+
"dirExists": "Directory already exists: {{dir}}",
|
|
21
|
+
"templateNotFound": "Template \"{{template}}\" not found. Available templates: {{available}}",
|
|
22
|
+
"noTemplatesAvailable": "none",
|
|
23
|
+
"pluginCreated": "✅ {{template}} plugin created: {{name}}",
|
|
24
|
+
"nextSteps": "Next steps:",
|
|
25
|
+
"fileCreated": " ✓ Created {{file}}",
|
|
26
|
+
"fileCopied": " ✓ Copied {{file}}"
|
|
27
|
+
}
|