@ai-setting/roy-agent-cli 1.0.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.
Files changed (158) hide show
  1. package/README.md +126 -0
  2. package/dist/bin/roy.js +127297 -0
  3. package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
  4. package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
  5. package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
  6. package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
  7. package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
  8. package/package.json +91 -0
  9. package/src/bin/roy.ts +12 -0
  10. package/src/cli.ts +101 -0
  11. package/src/commands/act.ts +480 -0
  12. package/src/commands/commands-add.ts +110 -0
  13. package/src/commands/commands-dirs.ts +70 -0
  14. package/src/commands/commands-info.ts +90 -0
  15. package/src/commands/commands-list.ts +161 -0
  16. package/src/commands/commands-remove.ts +147 -0
  17. package/src/commands/commands.ts +55 -0
  18. package/src/commands/config/config-service.test.ts +449 -0
  19. package/src/commands/config/config-service.ts +312 -0
  20. package/src/commands/config/deep-merge.test.ts +168 -0
  21. package/src/commands/config/deep-merge.ts +63 -0
  22. package/src/commands/config/export.ts +97 -0
  23. package/src/commands/config/filter-history-e2e.test.ts +141 -0
  24. package/src/commands/config/import-preserve-refs.test.ts +212 -0
  25. package/src/commands/config/import.ts +119 -0
  26. package/src/commands/config/index.ts +35 -0
  27. package/src/commands/config/list.ts +281 -0
  28. package/src/commands/config/roy-config-e2e.test.ts +297 -0
  29. package/src/commands/config/types.ts +54 -0
  30. package/src/commands/debug/index.ts +38 -0
  31. package/src/commands/debug/log.test.ts +233 -0
  32. package/src/commands/debug/log.ts +123 -0
  33. package/src/commands/debug/span.test.ts +297 -0
  34. package/src/commands/debug/span.ts +211 -0
  35. package/src/commands/debug/trace.test.ts +254 -0
  36. package/src/commands/debug/trace.ts +140 -0
  37. package/src/commands/eventsource/add.ts +133 -0
  38. package/src/commands/eventsource/index.ts +48 -0
  39. package/src/commands/eventsource/list.ts +194 -0
  40. package/src/commands/eventsource/remove.ts +95 -0
  41. package/src/commands/eventsource/start.ts +103 -0
  42. package/src/commands/eventsource/status.ts +185 -0
  43. package/src/commands/eventsource/stop.ts +89 -0
  44. package/src/commands/index.ts +22 -0
  45. package/src/commands/input-handler.test.ts +76 -0
  46. package/src/commands/input-handler.ts +43 -0
  47. package/src/commands/interactive-esc.test.ts +254 -0
  48. package/src/commands/interactive.shutdown.test.ts +122 -0
  49. package/src/commands/interactive.test.ts +221 -0
  50. package/src/commands/interactive.ts +1015 -0
  51. package/src/commands/lsp/check.ts +92 -0
  52. package/src/commands/lsp/index.ts +32 -0
  53. package/src/commands/lsp/install.ts +126 -0
  54. package/src/commands/lsp/list.ts +64 -0
  55. package/src/commands/mcp/index.ts +27 -0
  56. package/src/commands/mcp/list.ts +116 -0
  57. package/src/commands/mcp/reload.ts +70 -0
  58. package/src/commands/mcp/tools.ts +121 -0
  59. package/src/commands/memory/extract-e2e.test.ts +388 -0
  60. package/src/commands/memory/index.ts +11 -0
  61. package/src/commands/memory/memory-simplified.test.ts +58 -0
  62. package/src/commands/memory/memory.ts +25 -0
  63. package/src/commands/memory/organize.ts +300 -0
  64. package/src/commands/memory/recall.test.ts +120 -0
  65. package/src/commands/memory/recall.ts +88 -0
  66. package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
  67. package/src/commands/memory/record-prompt-component.test.ts +343 -0
  68. package/src/commands/memory/record.test.ts +92 -0
  69. package/src/commands/memory/record.ts +332 -0
  70. package/src/commands/plugin.test.ts +292 -0
  71. package/src/commands/plugin.ts +267 -0
  72. package/src/commands/sessions/active.ts +96 -0
  73. package/src/commands/sessions/add-message.ts +96 -0
  74. package/src/commands/sessions/checkpoints.ts +154 -0
  75. package/src/commands/sessions/compact.test.ts +215 -0
  76. package/src/commands/sessions/compact.ts +269 -0
  77. package/src/commands/sessions/delete.ts +236 -0
  78. package/src/commands/sessions/get.ts +165 -0
  79. package/src/commands/sessions/grep.ts +233 -0
  80. package/src/commands/sessions/index.ts +95 -0
  81. package/src/commands/sessions/list.ts +210 -0
  82. package/src/commands/sessions/messages.test.ts +333 -0
  83. package/src/commands/sessions/messages.ts +248 -0
  84. package/src/commands/sessions/mock.ts +194 -0
  85. package/src/commands/sessions/new.ts +82 -0
  86. package/src/commands/sessions/rename.ts +98 -0
  87. package/src/commands/shared/event-handler.ts +213 -0
  88. package/src/commands/shared/event-message-formatter.ts +295 -0
  89. package/src/commands/shared/index.ts +11 -0
  90. package/src/commands/shared/query-executor.test.ts +434 -0
  91. package/src/commands/shared/query-executor.ts +324 -0
  92. package/src/commands/shared/repl-engine.test.ts +354 -0
  93. package/src/commands/shared/session-manager.test.ts +212 -0
  94. package/src/commands/shared/session-manager.ts +114 -0
  95. package/src/commands/skills/get.ts +90 -0
  96. package/src/commands/skills/index.ts +39 -0
  97. package/src/commands/skills/list.ts +129 -0
  98. package/src/commands/skills/reload.ts +59 -0
  99. package/src/commands/skills/search.ts +132 -0
  100. package/src/commands/skills/show-config.ts +93 -0
  101. package/src/commands/tasks/complete.ts +92 -0
  102. package/src/commands/tasks/create.ts +118 -0
  103. package/src/commands/tasks/delete.ts +86 -0
  104. package/src/commands/tasks/get.ts +116 -0
  105. package/src/commands/tasks/index.ts +53 -0
  106. package/src/commands/tasks/list.ts +140 -0
  107. package/src/commands/tasks/operations.ts +120 -0
  108. package/src/commands/tasks/update.ts +122 -0
  109. package/src/commands/tools/exec-tool.ts +128 -0
  110. package/src/commands/tools/get.ts +114 -0
  111. package/src/commands/tools/index.ts +35 -0
  112. package/src/commands/tools/list.ts +107 -0
  113. package/src/commands/tools/shared/index.ts +7 -0
  114. package/src/commands/tools/shared/schema-helper.ts +111 -0
  115. package/src/commands/workflow/commands/add.ts +315 -0
  116. package/src/commands/workflow/commands/get.ts +193 -0
  117. package/src/commands/workflow/commands/list.ts +137 -0
  118. package/src/commands/workflow/commands/nodes.ts +528 -0
  119. package/src/commands/workflow/commands/remove.ts +94 -0
  120. package/src/commands/workflow/commands/run.ts +398 -0
  121. package/src/commands/workflow/commands/status.ts +147 -0
  122. package/src/commands/workflow/commands/stop.ts +91 -0
  123. package/src/commands/workflow/commands/update.ts +130 -0
  124. package/src/commands/workflow/commands/validate.ts +139 -0
  125. package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
  126. package/src/commands/workflow/index.ts +65 -0
  127. package/src/commands/workflow/renderers.ts +358 -0
  128. package/src/commands/workflow/validators/index.ts +8 -0
  129. package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
  130. package/src/commands/workflow/validators/node-validator.ts +125 -0
  131. package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
  132. package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
  133. package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
  134. package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
  135. package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
  136. package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
  137. package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
  138. package/src/commands/workflow/validators/types.ts +78 -0
  139. package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
  140. package/src/commands/workflow/validators/workflow-validator.ts +320 -0
  141. package/src/index.ts +19 -0
  142. package/src/plugin/apply.ts +103 -0
  143. package/src/plugin/discover.ts +219 -0
  144. package/src/plugin/index.ts +45 -0
  145. package/src/plugin/registry.ts +272 -0
  146. package/src/plugin/types.ts +165 -0
  147. package/src/services/context-handler.service.test.ts +501 -0
  148. package/src/services/context-handler.service.ts +372 -0
  149. package/src/services/environment.service.commands-prompt.test.ts +167 -0
  150. package/src/services/environment.service.ts +656 -0
  151. package/src/services/output.service.test.ts +92 -0
  152. package/src/services/output.service.ts +122 -0
  153. package/src/services/quiet-mode.service.test.ts +114 -0
  154. package/src/services/quiet-mode.service.ts +81 -0
  155. package/src/services/stream-output.service.test.ts +214 -0
  156. package/src/services/stream-output.service.ts +323 -0
  157. package/src/util/which.test.ts +101 -0
  158. package/src/util/which.ts +55 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * 测试 filterHistory 默认值导出问题
3
+ *
4
+ * RED: 期望 filterHistory=false(用户反馈导出的值不对)
5
+ * 但根据代码,filterHistory 的默认值应该是 true
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
9
+ import * as fsSync from "fs";
10
+ import * as path from "path";
11
+ import * as os from "os";
12
+ import { EnvironmentService } from "../../services/environment.service";
13
+ import { OutputService } from "../../services/output.service";
14
+ import { ConfigService } from "./config-service";
15
+
16
+ describe("filterHistory default value test", () => {
17
+ let tempDir: string;
18
+ let configFilePath: string;
19
+
20
+ beforeEach(() => {
21
+ tempDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "filter-history-test-"));
22
+ configFilePath = path.join(tempDir, "config.jsonc");
23
+ });
24
+
25
+ afterEach(() => {
26
+ if (fsSync.existsSync(tempDir)) {
27
+ fsSync.rmSync(tempDir, { recursive: true });
28
+ }
29
+ });
30
+
31
+ it("should export filterHistory=false when user explicitly sets it", async () => {
32
+ // 创建 config.jsonc,明确设置 filterHistory=false
33
+ const configContent = `{
34
+ "agent": {
35
+ "defaultAgent": {
36
+ "filterHistory": false
37
+ }
38
+ }
39
+ }`;
40
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
41
+
42
+ const output = new OutputService();
43
+ const envService = new EnvironmentService(output);
44
+
45
+ try {
46
+ await envService.create({
47
+ configPath: configFilePath,
48
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
49
+ });
50
+ const env = envService.getEnvironment();
51
+
52
+ const configComponent = env!.getComponent("config");
53
+ const configService = new ConfigService(configComponent as any, output);
54
+
55
+ // 获取 agent 配置
56
+ const agentConfig = configService.getComponentConfig("agent");
57
+
58
+ console.log("Agent config (filterHistory=false):", JSON.stringify(agentConfig, null, 2));
59
+
60
+ // 验证:filterHistory 应该是 false
61
+ expect(agentConfig.defaultAgent).toBeDefined();
62
+ expect(agentConfig.defaultAgent.filterHistory).toBe(false);
63
+ } finally {
64
+ await envService.dispose();
65
+ }
66
+ });
67
+
68
+ it("should export filterHistory=false (default) when not in config file", async () => {
69
+ // 创建 config.jsonc,只包含部分 agent 配置(不含 filterHistory)
70
+ const configContent = `{
71
+ "agent": {
72
+ "defaultAgent": {
73
+ "maxIterations": 100
74
+ }
75
+ }
76
+ }`;
77
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
78
+
79
+ const output = new OutputService();
80
+ const envService = new EnvironmentService(output);
81
+
82
+ try {
83
+ await envService.create({
84
+ configPath: configFilePath,
85
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
86
+ });
87
+ const env = envService.getEnvironment();
88
+
89
+ const configComponent = env!.getComponent("config");
90
+ const configService = new ConfigService(configComponent as any, output);
91
+
92
+ // 获取 agent 配置
93
+ const agentConfig = configService.getComponentConfig("agent");
94
+
95
+ console.log("Agent config (no filterHistory):", JSON.stringify(agentConfig, null, 2));
96
+
97
+ // 验证:filterHistory 应该有默认值 false
98
+ expect(agentConfig.defaultAgent).toBeDefined();
99
+ expect(agentConfig.defaultAgent.filterHistory).toBe(false);
100
+ } finally {
101
+ await envService.dispose();
102
+ }
103
+ });
104
+
105
+ it("should export all key default values when config file is empty", async () => {
106
+ // 创建空的 config.jsonc
107
+ const configContent = `{}`;
108
+ fsSync.writeFileSync(configFilePath, configContent, "utf-8");
109
+
110
+ const output = new OutputService();
111
+ const envService = new EnvironmentService(output);
112
+
113
+ try {
114
+ await envService.create({
115
+ configPath: configFilePath,
116
+ sessionStorage: { type: "memory" } // 使用内存存储,避免污染正式数据库
117
+ });
118
+ const env = envService.getEnvironment();
119
+
120
+ const configComponent = env!.getComponent("config");
121
+ const configService = new ConfigService(configComponent as any, output);
122
+
123
+ // 获取 agent 配置
124
+ const agentConfig = configService.getComponentConfig("agent");
125
+
126
+ console.log("Agent config (empty file):", JSON.stringify(agentConfig, null, 2));
127
+
128
+ // 验证:agent 配置应该有默认值
129
+ expect(agentConfig.defaultAgent).toBeDefined();
130
+
131
+ // 检查关键默认值(根据 AGENT_DEFAULTS)
132
+ expect(agentConfig.defaultAgent.maxIterations).toBe(200);
133
+ expect(agentConfig.defaultAgent.temperature).toBe(0);
134
+ expect(agentConfig.defaultAgent.timeout).toBe(300000);
135
+ expect(agentConfig.defaultAgent.streaming).toBe(false);
136
+ expect(agentConfig.defaultAgent.filterHistory).toBe(false);
137
+ } finally {
138
+ await envService.dispose();
139
+ }
140
+ });
141
+ });
@@ -0,0 +1,212 @@
1
+ /**
2
+ * @fileoverview 验证 config import 保留 JSONC $ 引用语法的测试
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
6
+ import * as fsSync from "fs";
7
+ import * as fsPromises from "fs/promises";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+
11
+ describe("config import 保留 JSONC $ 引用", () => {
12
+ let tempDir: string;
13
+ let configFilePath: string;
14
+
15
+ beforeEach(() => {
16
+ tempDir = fsSync.mkdtempSync(path.join(os.tmpdir(), "roy-import-test-"));
17
+ configFilePath = path.join(tempDir, "config.jsonc");
18
+ });
19
+
20
+ afterEach(() => {
21
+ if (fsSync.existsSync(tempDir)) {
22
+ fsSync.rmSync(tempDir, { recursive: true });
23
+ }
24
+ });
25
+
26
+ it("保留 $file:// 协议引用不被清空", async () => {
27
+ // 1. 创建包含 $ 引用的原始配置文件
28
+ const originalContent = `{
29
+ "agent": {
30
+ "defaultAgent": {
31
+ "apiKey": "\${file://auth.json#providers.minimax.apiKey}",
32
+ "model": "minimax"
33
+ }
34
+ }
35
+ }`;
36
+ await fsPromises.writeFile(configFilePath, originalContent, "utf-8");
37
+
38
+ // 2. 创建导入源文件(只包含部分配置)
39
+ const sourceFilePath = path.join(tempDir, "source.json");
40
+ const sourceContent = `{
41
+ "agent": {
42
+ "defaultAgent": {
43
+ "filterHistory": true
44
+ }
45
+ }
46
+ }`;
47
+ await fsPromises.writeFile(sourceFilePath, sourceContent, "utf-8");
48
+
49
+ // 3. 执行导入
50
+ const { ConfigService } = await import("./config-service");
51
+ const mockConfigComponent = createMockConfigComponent(configFilePath);
52
+ const mockOutput = createMockOutput();
53
+ const configService = new ConfigService(mockConfigComponent, mockOutput as any);
54
+
55
+ const result = await configService.importFromFile("agent", {
56
+ sourceFile: sourceFilePath,
57
+ verbose: false,
58
+ });
59
+
60
+ expect(result.success).toBe(true);
61
+
62
+ // 4. 验证 $ 引用被保留
63
+ const finalContent = await fsPromises.readFile(configFilePath, "utf-8");
64
+ const finalData = JSON.parse(finalContent);
65
+
66
+ // apiKey 应该保留 $ 引用,而不是空字符串
67
+ expect(finalData.agent.defaultAgent.apiKey).toBe(
68
+ "\${file://auth.json#providers.minimax.apiKey}"
69
+ );
70
+ // filterHistory 应该是导入的新值
71
+ expect(finalData.agent.defaultAgent.filterHistory).toBe(true);
72
+ // model 也应该保留
73
+ expect(finalData.agent.defaultAgent.model).toBe("minimax");
74
+ });
75
+
76
+ it("保留 $env:// 协议引用不被清空", async () => {
77
+ // 1. 创建包含 $ 引用和 $env 混合的配置文件
78
+ const originalContent = `{
79
+ "llm": {
80
+ "defaultLlm": {
81
+ "apiKey": "\${env://MINIMAX_API_KEY:-default_key}",
82
+ "baseURL": "\${file://config.json#llm.baseURL}",
83
+ "model": "minimax"
84
+ }
85
+ }
86
+ }`;
87
+ await fsPromises.writeFile(configFilePath, originalContent, "utf-8");
88
+
89
+ // 2. 创建导入源文件
90
+ const sourceFilePath = path.join(tempDir, "source.json");
91
+ const sourceContent = `{
92
+ "llm": {
93
+ "defaultLlm": {
94
+ "temperature": 0.7
95
+ }
96
+ }
97
+ }`;
98
+ await fsPromises.writeFile(sourceFilePath, sourceContent, "utf-8");
99
+
100
+ // 3. 执行导入
101
+ const { ConfigService } = await import("./config-service");
102
+ const mockConfigComponent = createMockConfigComponent(configFilePath);
103
+ const mockOutput = createMockOutput();
104
+ const configService = new ConfigService(mockConfigComponent, mockOutput as any);
105
+
106
+ const result = await configService.importFromFile("llm", {
107
+ sourceFile: sourceFilePath,
108
+ verbose: false,
109
+ });
110
+
111
+ expect(result.success).toBe(true);
112
+
113
+ // 4. 验证 $ 引用被保留
114
+ const finalContent = await fsPromises.readFile(configFilePath, "utf-8");
115
+ const finalData = JSON.parse(finalContent);
116
+
117
+ // apiKey 应该保留 $env:// 引用
118
+ expect(finalData.llm.defaultLlm.apiKey).toBe(
119
+ "\${env://MINIMAX_API_KEY:-default_key}"
120
+ );
121
+ // baseURL 应该保留 $file:// 引用
122
+ expect(finalData.llm.defaultLlm.baseURL).toBe(
123
+ "\${file://config.json#llm.baseURL}"
124
+ );
125
+ // temperature 应该是导入的新值
126
+ expect(finalData.llm.defaultLlm.temperature).toBe(0.7);
127
+ });
128
+
129
+ it("保留带默认值的 $ 引用", async () => {
130
+ // 1. 创建包含带默认值 $ 引用的配置文件
131
+ const originalContent = `{
132
+ "agent": {
133
+ "defaultAgent": {
134
+ "apiKey": "\${file://auth.json#apiKey:-fallback_key}",
135
+ "timeout": 30
136
+ }
137
+ }
138
+ }`;
139
+ await fsPromises.writeFile(configFilePath, originalContent, "utf-8");
140
+
141
+ // 2. 创建导入源文件
142
+ const sourceFilePath = path.join(tempDir, "source.json");
143
+ const sourceContent = `{
144
+ "agent": {
145
+ "defaultAgent": {
146
+ "maxRetries": 3
147
+ }
148
+ }
149
+ }`;
150
+ await fsPromises.writeFile(sourceFilePath, sourceContent, "utf-8");
151
+
152
+ // 3. 执行导入
153
+ const { ConfigService } = await import("./config-service");
154
+ const mockConfigComponent = createMockConfigComponent(configFilePath);
155
+ const mockOutput = createMockOutput();
156
+ const configService = new ConfigService(mockConfigComponent, mockOutput as any);
157
+
158
+ const result = await configService.importFromFile("agent", {
159
+ sourceFile: sourceFilePath,
160
+ verbose: false,
161
+ });
162
+
163
+ expect(result.success).toBe(true);
164
+
165
+ // 4. 验证带默认值的 $ 引用被保留
166
+ const finalContent = await fsPromises.readFile(configFilePath, "utf-8");
167
+ const finalData = JSON.parse(finalContent);
168
+
169
+ expect(finalData.agent.defaultAgent.apiKey).toBe(
170
+ "\${file://auth.json#apiKey:-fallback_key}"
171
+ );
172
+ expect(finalData.agent.defaultAgent.timeout).toBe(30);
173
+ expect(finalData.agent.defaultAgent.maxRetries).toBe(3);
174
+ });
175
+ });
176
+
177
+ /**
178
+ * 创建模拟 ConfigComponent
179
+ */
180
+ function createMockConfigComponent(filePath: string) {
181
+ return {
182
+ name: "config",
183
+ getSources: () => [
184
+ {
185
+ name: "memory",
186
+ read: () => undefined,
187
+ write: () => {},
188
+ list: () => [],
189
+ },
190
+ {
191
+ name: "file",
192
+ filePath,
193
+ read: () => undefined,
194
+ write: () => {},
195
+ list: () => [],
196
+ },
197
+ ],
198
+ } as any;
199
+ }
200
+
201
+ /**
202
+ * 创建模拟 OutputService
203
+ */
204
+ function createMockOutput() {
205
+ return {
206
+ log: () => {},
207
+ info: () => {},
208
+ warn: () => {},
209
+ error: () => {},
210
+ success: () => {},
211
+ };
212
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * @fileoverview config import 子命令
3
+ */
4
+
5
+ import { CommandModule } from "yargs";
6
+ import chalk from "chalk";
7
+ import { EnvironmentService } from "../../services/environment.service";
8
+ import { OutputService } from "../../services/output.service";
9
+ import { ConfigService } from "./config-service";
10
+ import { SUPPORTED_COMPONENTS, resolveComponentName } from "./types";
11
+
12
+ export interface ConfigImportOptions {
13
+ component: string;
14
+ file: string;
15
+ dryRun?: boolean;
16
+ verbose?: boolean;
17
+ config?: string;
18
+ }
19
+
20
+ /**
21
+ * ConfigImportCommand - config import 子命令
22
+ */
23
+ export const ConfigImportCommand: CommandModule<object, ConfigImportOptions> = {
24
+ command: "import <component>",
25
+ describe: "从文件导入配置到组件的 file source",
26
+
27
+ builder: (yargs) =>
28
+ yargs
29
+ .positional("component", {
30
+ type: "string",
31
+ describe: "Component 名称",
32
+ choices: [...SUPPORTED_COMPONENTS, "sessions"],
33
+ demandOption: true,
34
+ })
35
+ .option("file", {
36
+ type: "string",
37
+ describe: "导入源配置文件",
38
+ alias: "f",
39
+ demandOption: true,
40
+ })
41
+ .option("dry-run", {
42
+ type: "boolean",
43
+ describe: "预览变更,不实际写入",
44
+ default: false,
45
+ })
46
+ .option("verbose", {
47
+ type: "boolean",
48
+ describe: "显示详细操作信息",
49
+ alias: "v",
50
+ default: false,
51
+ })
52
+ .option("config", {
53
+ type: "string",
54
+ describe: "配置文件路径",
55
+ alias: "c",
56
+ })
57
+ .example("roy config import session --file session-config.json", "导入 session 配置")
58
+ .example("roy config import session --file session-config.json --dry-run", "预览变更")
59
+ .example("roy config import session --file session-config.json --verbose", "显示详细信息"),
60
+
61
+ async handler(args) {
62
+ const output = new OutputService();
63
+ const envService = new EnvironmentService(output);
64
+
65
+ try {
66
+ await envService.create({ configPath: args.config });
67
+ const env = envService.getEnvironment();
68
+ if (!env) {
69
+ output.error("Failed to get environment");
70
+ process.exit(1);
71
+ }
72
+ const configComponent = env.getComponent("config") as any;
73
+ if (!configComponent) {
74
+ output.error("ConfigComponent not available");
75
+ process.exit(1);
76
+ }
77
+ const configService = new ConfigService(configComponent, output);
78
+
79
+ // 解析 component 名称(处理别名)
80
+ const componentName = resolveComponentName(args.component);
81
+
82
+ // 验证 component
83
+ if (!SUPPORTED_COMPONENTS.includes(componentName as any)) {
84
+ output.error(`Unknown component: ${args.component}`);
85
+ output.log("");
86
+ output.log("Supported components:");
87
+ for (const comp of SUPPORTED_COMPONENTS) {
88
+ output.log(` ${chalk.cyan(comp)}`);
89
+ }
90
+ process.exit(1);
91
+ }
92
+
93
+ // 导入配置
94
+ const result = await configService.importFromFile(componentName, {
95
+ sourceFile: args.file,
96
+ dryRun: args.dryRun,
97
+ verbose: args.verbose,
98
+ });
99
+
100
+ if (!result.success) {
101
+ output.error(result.message || "导入失败");
102
+ process.exit(1);
103
+ }
104
+
105
+ if (result.merged && result.changes.length > 0 && args.verbose) {
106
+ output.log("");
107
+ output.log(chalk.bold("变更详情:"));
108
+ for (const change of result.changes) {
109
+ output.log(` ${change.key}: ${JSON.stringify(change.oldValue)} → ${JSON.stringify(change.newValue)}`);
110
+ }
111
+ }
112
+ } catch (error) {
113
+ output.error(`Failed to import config: ${error}`);
114
+ process.exit(1);
115
+ } finally {
116
+ await envService.dispose();
117
+ }
118
+ },
119
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @fileoverview Config Command - 配置管理主入口
3
+ */
4
+
5
+ import { CommandModule } from "yargs";
6
+ import { ConfigListCommand } from "./list";
7
+ import { ConfigExportCommand } from "./export";
8
+ import { ConfigImportCommand } from "./import";
9
+
10
+ /**
11
+ * ConfigCommand - 配置管理命令
12
+ */
13
+ export const ConfigCommand: CommandModule = {
14
+ command: "config",
15
+ describe: "配置管理 - 查看、导出、导入配置",
16
+ aliases: ["cfg"],
17
+
18
+ builder: (yargs) =>
19
+ yargs
20
+ .command(ConfigListCommand)
21
+ .command(ConfigExportCommand)
22
+ .command(ConfigImportCommand)
23
+ .demandCommand()
24
+ .help(),
25
+
26
+ handler: () => {
27
+ // 默认显示帮助(由 yargs demandCommand 处理)
28
+ },
29
+ };
30
+
31
+ // 导出子命令
32
+ export { ConfigListCommand } from "./list";
33
+ export { ConfigExportCommand } from "./export";
34
+ export { ConfigImportCommand } from "./import";
35
+ export * from "./types";