@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,212 @@
1
+ /**
2
+ * @fileoverview SessionManager Test
3
+ *
4
+ * TDD: 测试 SessionManager 的核心功能
5
+ */
6
+
7
+ import { describe, test, expect, vi, beforeEach, afterEach } from "bun:test";
8
+ import { SessionManager } from "./session-manager";
9
+ import { OutputService } from "../../services/output.service";
10
+
11
+ // Mock types
12
+ interface MockSession {
13
+ id: string;
14
+ title: string;
15
+ }
16
+
17
+ interface MockSessionComponent {
18
+ getActiveSession: ReturnType<typeof vi.fn>;
19
+ get: ReturnType<typeof vi.fn>;
20
+ setActiveSession: ReturnType<typeof vi.fn>;
21
+ create: ReturnType<typeof vi.fn>;
22
+ }
23
+
24
+ describe("SessionManager", () => {
25
+ let sessionManager: SessionManager;
26
+ let mockSessionComponent: MockSessionComponent;
27
+ let mockOutput: OutputService;
28
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
29
+ let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
30
+
31
+ beforeEach(() => {
32
+ // 创建 mock SessionComponent
33
+ mockSessionComponent = {
34
+ getActiveSession: vi.fn(),
35
+ get: vi.fn(),
36
+ setActiveSession: vi.fn(),
37
+ create: vi.fn(),
38
+ };
39
+
40
+ mockOutput = new OutputService();
41
+ mockOutput.configure({ quiet: true });
42
+
43
+ // Spy on console methods
44
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {});
45
+ consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
46
+
47
+ sessionManager = new SessionManager({
48
+ sessionComponent: mockSessionComponent as any,
49
+ output: mockOutput,
50
+ quiet: true,
51
+ });
52
+ });
53
+
54
+ afterEach(() => {
55
+ vi.restoreAllMocks();
56
+ });
57
+
58
+ describe("init()", () => {
59
+ test("should create new session when no sessionId provided", async () => {
60
+ // Given: no active session and no sessionId
61
+ // First call to getActiveSession returns null (no active session)
62
+ // First call to get() returns the newly created session
63
+ mockSessionComponent.getActiveSession.mockResolvedValue(null);
64
+ mockSessionComponent.create.mockResolvedValue({
65
+ id: "sess_new123",
66
+ title: "New Session",
67
+ });
68
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
69
+ mockSessionComponent.get.mockResolvedValueOnce({
70
+ id: "sess_new123",
71
+ title: "New Session",
72
+ });
73
+
74
+ // When
75
+ const result = await sessionManager.init();
76
+
77
+ // Then
78
+ expect(result.isNewSession).toBe(true);
79
+ expect(result.sessionId).toBe("sess_new123");
80
+ expect(mockSessionComponent.create).toHaveBeenCalledWith({
81
+ title: expect.stringContaining("Session -"),
82
+ });
83
+ });
84
+
85
+ test("should return existing session when sessionId exists", async () => {
86
+ // Given: existing session
87
+ const existingSession: MockSession = {
88
+ id: "sess_existing",
89
+ title: "Existing Session",
90
+ };
91
+ mockSessionComponent.get.mockResolvedValue(existingSession);
92
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
93
+
94
+ // When
95
+ const result = await sessionManager.init("sess_existing");
96
+
97
+ // Then
98
+ expect(result.isNewSession).toBe(false);
99
+ expect(result.sessionId).toBe("sess_existing");
100
+ expect(result.sessionTitle).toBe("Existing Session");
101
+ expect(mockSessionComponent.create).not.toHaveBeenCalled();
102
+ });
103
+
104
+ test("should create new session when specified sessionId does not exist", async () => {
105
+ // Given: sessionId provided but doesn't exist
106
+ // First call to get() returns null (session not found)
107
+ // Second call to get() returns the newly created session
108
+ mockSessionComponent.get
109
+ .mockResolvedValueOnce(null) // first call - session not found
110
+ .mockResolvedValueOnce({ id: "sess_new456", title: "New Session" }); // second call - new session
111
+ mockSessionComponent.create.mockResolvedValue({
112
+ id: "sess_new456",
113
+ title: "New Session",
114
+ });
115
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
116
+
117
+ // When
118
+ const result = await sessionManager.init("sess_nonexistent");
119
+
120
+ // Then
121
+ expect(result.isNewSession).toBe(true);
122
+ expect(result.sessionId).toBe("sess_new456");
123
+ expect(mockSessionComponent.create).toHaveBeenCalled();
124
+ });
125
+
126
+ test("should continue last session when isContinue is true", async () => {
127
+ // Given: active session exists
128
+ // First call to getActiveSession returns the active session
129
+ // First call to get() returns the active session
130
+ const activeSession: MockSession = {
131
+ id: "sess_active",
132
+ title: "Active Session",
133
+ };
134
+ mockSessionComponent.getActiveSession.mockResolvedValue(activeSession);
135
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
136
+ mockSessionComponent.get.mockResolvedValueOnce(activeSession);
137
+
138
+ // When
139
+ const result = await sessionManager.init(undefined, true);
140
+
141
+ // Then
142
+ expect(result.sessionId).toBe("sess_active");
143
+ expect(mockSessionComponent.getActiveSession).toHaveBeenCalled();
144
+ expect(mockSessionComponent.create).not.toHaveBeenCalled();
145
+ });
146
+
147
+ test("should prefer explicit sessionId over continue flag", async () => {
148
+ // Given: both sessionId and isContinue provided
149
+ const existingSession: MockSession = {
150
+ id: "sess_explicit",
151
+ title: "Explicit Session",
152
+ };
153
+ mockSessionComponent.get.mockResolvedValue(existingSession);
154
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
155
+
156
+ // When: sessionId takes precedence
157
+ const result = await sessionManager.init("sess_explicit", true);
158
+
159
+ // Then
160
+ expect(result.sessionId).toBe("sess_explicit");
161
+ expect(mockSessionComponent.getActiveSession).not.toHaveBeenCalled();
162
+ });
163
+ });
164
+
165
+ describe("switchSession()", () => {
166
+ test("should switch to specified session", async () => {
167
+ // Given
168
+ const targetSession: MockSession = {
169
+ id: "sess_target",
170
+ title: "Target Session",
171
+ };
172
+ mockSessionComponent.get.mockResolvedValue(targetSession);
173
+ mockSessionComponent.setActiveSession.mockResolvedValue(undefined);
174
+
175
+ // When
176
+ const result = await sessionManager.switchSession("sess_target");
177
+
178
+ // Then
179
+ expect(result.sessionId).toBe("sess_target");
180
+ expect(result.sessionTitle).toBe("Target Session");
181
+ expect(mockSessionComponent.setActiveSession).toHaveBeenCalledWith("sess_target");
182
+ });
183
+ });
184
+
185
+ describe("getCurrentSession()", () => {
186
+ test("should return current active session", async () => {
187
+ // Given
188
+ const activeSession: MockSession = {
189
+ id: "sess_current",
190
+ title: "Current Session",
191
+ };
192
+ mockSessionComponent.getActiveSession.mockResolvedValue(activeSession);
193
+
194
+ // When
195
+ const result = await sessionManager.getCurrentSession();
196
+
197
+ // Then
198
+ expect(result).toEqual(activeSession);
199
+ });
200
+
201
+ test("should return null when no active session", async () => {
202
+ // Given
203
+ mockSessionComponent.getActiveSession.mockResolvedValue(null);
204
+
205
+ // When
206
+ const result = await sessionManager.getCurrentSession();
207
+
208
+ // Then
209
+ expect(result).toBeNull();
210
+ });
211
+ });
212
+ });
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @fileoverview Session Manager - Session 管理共享逻辑
3
+ *
4
+ * 从 act.ts 抽取,供 interactive 复用
5
+ *
6
+ * 功能:
7
+ * - 获取/创建 Session
8
+ * - 设置活跃会话
9
+ * - 切换会话
10
+ */
11
+
12
+ import { OutputService } from "../../services/output.service";
13
+ import type { Session, SessionComponent } from "@ai-setting/roy-agent-core";
14
+
15
+ export interface SessionInitResult {
16
+ sessionId: string;
17
+ isNewSession: boolean;
18
+ sessionTitle: string;
19
+ }
20
+
21
+ export interface SessionManagerOptions {
22
+ sessionComponent: SessionComponent;
23
+ output: OutputService;
24
+ quiet: boolean;
25
+ }
26
+
27
+ /**
28
+ * SessionManager - 会话管理
29
+ */
30
+ export class SessionManager {
31
+ private sessionComponent: SessionComponent;
32
+ private output: OutputService;
33
+ private quiet: boolean;
34
+
35
+ constructor(options: SessionManagerOptions) {
36
+ this.sessionComponent = options.sessionComponent;
37
+ this.output = options.output;
38
+ this.quiet = options.quiet;
39
+ }
40
+
41
+ /**
42
+ * 初始化会话
43
+ *
44
+ * 优先级:
45
+ * 1. 指定 sessionId -> 验证并使用
46
+ * 2. --continue -> 获取上次会话
47
+ * 3. 无 -> 创建新会话
48
+ */
49
+ async init(sessionId?: string, isContinue?: boolean): Promise<SessionInitResult> {
50
+ let targetSessionId = sessionId;
51
+ let isNewSession = false;
52
+
53
+ // --continue 模式
54
+ if (isContinue && !targetSessionId) {
55
+ const activeSession = await this.sessionComponent.getActiveSession();
56
+ if (activeSession) {
57
+ targetSessionId = activeSession.id;
58
+ if (!this.quiet) {
59
+ this.output.info(`继续会话: ${activeSession.title} (${targetSessionId})`);
60
+ }
61
+ } else {
62
+ this.output.warn("没有找到上次会话,将创建新会话");
63
+ }
64
+ }
65
+
66
+ // 指定 session 验证
67
+ if (targetSessionId) {
68
+ await this.sessionComponent.setActiveSession(targetSessionId);
69
+ const session = await this.sessionComponent.get(targetSessionId);
70
+ if (session) {
71
+ if (!this.quiet) {
72
+ this.output.info(`会话: ${session.title} (${targetSessionId})`);
73
+ }
74
+ } else {
75
+ this.output.warn(`会话不存在: ${targetSessionId},将创建新会话`);
76
+ targetSessionId = undefined;
77
+ }
78
+ }
79
+
80
+ // 创建新会话
81
+ if (!targetSessionId) {
82
+ const newSession = await this.sessionComponent.create({
83
+ title: `Session - ${new Date().toLocaleString("zh-CN")}`,
84
+ });
85
+ targetSessionId = newSession.id;
86
+ isNewSession = true;
87
+ await this.sessionComponent.setActiveSession(targetSessionId);
88
+ if (!this.quiet) {
89
+ this.output.info(`创建新会话: ${newSession.title} (${targetSessionId})`);
90
+ }
91
+ }
92
+
93
+ const session = await this.sessionComponent.get(targetSessionId!);
94
+ return {
95
+ sessionId: targetSessionId!,
96
+ isNewSession,
97
+ sessionTitle: session?.title || "Unknown",
98
+ };
99
+ }
100
+
101
+ /**
102
+ * 切换会话
103
+ */
104
+ async switchSession(sessionId: string): Promise<SessionInitResult> {
105
+ return this.init(sessionId, false);
106
+ }
107
+
108
+ /**
109
+ * 获取当前会话信息
110
+ */
111
+ async getCurrentSession(): Promise<Session | undefined> {
112
+ return this.sessionComponent.getActiveSession();
113
+ }
114
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Skills Get Command
3
+ *
4
+ * 命令:roy skills get <name>
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SkillComponent, SkillEntry } from "@ai-setting/roy-agent-core";
12
+
13
+ interface GetOptions {
14
+ name: string;
15
+ json?: boolean;
16
+ config?: string;
17
+ }
18
+
19
+ export const GetCommand: CommandModule<object, GetOptions> = {
20
+ command: "get <name>",
21
+ describe: "获取指定技能内容",
22
+
23
+ builder: (yargs) =>
24
+ yargs
25
+ .positional("name", {
26
+ type: "string",
27
+ demandOption: true,
28
+ description: "技能名称",
29
+ })
30
+ .option("json", {
31
+ alias: "j",
32
+ type: "boolean",
33
+ default: false,
34
+ description: "JSON 输出",
35
+ }),
36
+
37
+ async handler(args) {
38
+ const output = new OutputService();
39
+ const envService = new EnvironmentService(output);
40
+
41
+ try {
42
+ await envService.create({ configPath: args.config });
43
+ const env = envService.getEnvironment();
44
+ if (!env) {
45
+ output.error("Failed to create environment");
46
+ process.exit(1);
47
+ }
48
+ const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
49
+
50
+ if (!skillComponent) {
51
+ output.error("SkillComponent not available");
52
+ process.exit(1);
53
+ }
54
+
55
+ const skill = skillComponent.getSkill(args.name);
56
+
57
+ if (!skill) {
58
+ output.error(`Skill not found: ${args.name}`);
59
+ output.log("Use 'roy skills list' to see available skills.");
60
+ process.exit(1);
61
+ }
62
+
63
+ if (args.json) {
64
+ output.json({
65
+ name: skill.name,
66
+ description: skill.description,
67
+ source: skill.source,
68
+ filePath: skill.filePath,
69
+ content: skill.content,
70
+ });
71
+ } else {
72
+ // 输出技能信息头
73
+ output.log(chalk.bold.cyan(`# ${skill.name}`));
74
+ output.log("");
75
+ output.log(`${chalk.gray("Description:")} ${skill.description}`);
76
+ output.log(`${chalk.gray("Source:")} ${skill.source}`);
77
+ output.log(`${chalk.gray("File:")} ${skill.filePath}`);
78
+ output.log("");
79
+ output.log(chalk.bold("--- Content ---"));
80
+ output.log("");
81
+ output.log(skill.content);
82
+ }
83
+ } catch (error) {
84
+ output.error(`Failed to get skill: ${error}`);
85
+ process.exit(1);
86
+ } finally {
87
+ await envService.dispose();
88
+ }
89
+ },
90
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @fileoverview Skills Command Entry
3
+ *
4
+ * 命令入口:roy skills [action]
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { ListCommand } from "./list";
9
+ import { GetCommand } from "./get";
10
+ import { SearchCommand } from "./search";
11
+ import { ReloadCommand } from "./reload";
12
+ import { ShowConfigCommand } from "./show-config";
13
+
14
+ /**
15
+ * Skills Command
16
+ *
17
+ * 技能管理命令,提供以下子命令:
18
+ * - list: 列出所有技能
19
+ * - get: 获取指定技能内容
20
+ * - search: 搜索技能
21
+ * - reload: 重新加载技能
22
+ * - show-config: 显示配置信息
23
+ */
24
+ export const SkillsCommand: CommandModule = {
25
+ command: "skills",
26
+ describe: "技能管理 - 列出、获取、搜索、重新加载技能",
27
+ builder: (yargs) =>
28
+ yargs
29
+ .command(ListCommand)
30
+ .command(GetCommand)
31
+ .command(SearchCommand)
32
+ .command(ReloadCommand)
33
+ .command(ShowConfigCommand)
34
+ .demandCommand()
35
+ .help(),
36
+ handler: () => {
37
+ console.log("Use 'roy skills --help' for usage information");
38
+ },
39
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @fileoverview Skills List Command
3
+ *
4
+ * 命令:roy skills list
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SkillComponent, SkillListItem } from "@ai-setting/roy-agent-core";
12
+
13
+ interface ListOptions {
14
+ source?: "built-in" | "user" | "project" | "all";
15
+ json?: boolean;
16
+ quiet?: boolean;
17
+ config?: string;
18
+ }
19
+
20
+ export const ListCommand: CommandModule<object, object> = {
21
+ command: "list",
22
+ aliases: ["ls"],
23
+ describe: "列出所有可用技能",
24
+
25
+ builder: (yargs) =>
26
+ yargs
27
+ .option("source", {
28
+ alias: "s",
29
+ type: "string",
30
+ choices: ["built-in", "user", "project", "all"],
31
+ default: "all",
32
+ description: "按来源筛选",
33
+ })
34
+ .option("json", {
35
+ alias: "j",
36
+ type: "boolean",
37
+ default: false,
38
+ description: "JSON 输出",
39
+ })
40
+ .option("quiet", {
41
+ alias: "q",
42
+ type: "boolean",
43
+ default: false,
44
+ description: "简洁输出",
45
+ }),
46
+
47
+ async handler(args) {
48
+ const a = args as any;
49
+ const output = new OutputService();
50
+ const envService = new EnvironmentService(output);
51
+
52
+ try {
53
+ await envService.create({ configPath: a.config });
54
+ const env = envService.getEnvironment();
55
+ if (!env) {
56
+ output.error("Failed to create environment");
57
+ process.exit(1);
58
+ }
59
+ const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
60
+
61
+ if (!skillComponent) {
62
+ output.error("SkillComponent not available");
63
+ process.exit(1);
64
+ }
65
+
66
+ const skills = skillComponent.getSkillList() as SkillListItem[];
67
+
68
+ // 按来源筛选
69
+ const filtered = a.source && a.source !== "all"
70
+ ? skills.filter((s: SkillListItem) => s.source === a.source)
71
+ : skills;
72
+
73
+ if (a.json) {
74
+ output.json({
75
+ skills: filtered.map((s: SkillListItem) => ({
76
+ name: s.name,
77
+ description: s.description,
78
+ source: s.source,
79
+ })),
80
+ count: filtered.length,
81
+ });
82
+ } else if (a.quiet) {
83
+ filtered.forEach((s: SkillListItem) => output.log(s.name));
84
+ } else {
85
+ // 表格输出
86
+ const header = [
87
+ chalk.bold("Name"),
88
+ chalk.bold("Description"),
89
+ chalk.bold("Source"),
90
+ ].join(" | ");
91
+
92
+ const sourceColor = (source: string) => {
93
+ switch (source) {
94
+ case "project": return chalk.green;
95
+ case "user": return chalk.blue;
96
+ case "built-in": return chalk.gray;
97
+ default: return chalk.white;
98
+ }
99
+ };
100
+
101
+ const rows = filtered.map((s: SkillListItem) => {
102
+ const desc = s.description.length > 50
103
+ ? s.description.slice(0, 47) + "..."
104
+ : s.description;
105
+ return [
106
+ chalk.cyan(s.name),
107
+ desc,
108
+ sourceColor(s.source)(`[${s.source}]`),
109
+ ].join(" | ");
110
+ });
111
+
112
+ output.log([
113
+ `┌─ Skills ${"─".repeat(55)}┐`,
114
+ `│${header}│`,
115
+ "├" + "─".repeat(header.length + 2) + "┤",
116
+ ...rows.map(r => `│${r}│`),
117
+ "└" + "─".repeat(header.length + 2) + "┘",
118
+ "",
119
+ chalk.gray(`Total: ${filtered.length} skills`),
120
+ ].join("\n"));
121
+ }
122
+ } catch (error) {
123
+ output.error(`Failed to list skills: ${error}`);
124
+ process.exit(1);
125
+ } finally {
126
+ await envService.dispose();
127
+ }
128
+ },
129
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @fileoverview Skills Reload Command
3
+ *
4
+ * 命令:roy skills reload
5
+ */
6
+
7
+ import { CommandModule } from "yargs";
8
+ import { EnvironmentService } from "../../services/environment.service";
9
+ import { OutputService } from "../../services/output.service";
10
+ import chalk from "chalk";
11
+ import type { SkillComponent } from "@ai-setting/roy-agent-core";
12
+
13
+ interface ReloadOptions {
14
+ config?: string;
15
+ }
16
+
17
+ export const ReloadCommand: CommandModule<object, ReloadOptions> = {
18
+ command: "reload",
19
+ describe: "重新扫描并更新 SkillTool",
20
+
21
+ builder: (yargs) =>
22
+ yargs
23
+ .option("config", {
24
+ type: "string",
25
+ description: "配置文件路径",
26
+ }),
27
+
28
+ async handler(args) {
29
+ const output = new OutputService();
30
+ const envService = new EnvironmentService(output);
31
+
32
+ try {
33
+ await envService.create({ configPath: args.config });
34
+ const env = envService.getEnvironment();
35
+ if (!env) {
36
+ output.error("Failed to create environment");
37
+ process.exit(1);
38
+ }
39
+ const skillComponent = env.getComponent("skill") as SkillComponent | undefined;
40
+
41
+ if (!skillComponent) {
42
+ output.error("SkillComponent not available");
43
+ process.exit(1);
44
+ }
45
+
46
+ output.log(chalk.blue("Reloading skills..."));
47
+
48
+ await skillComponent.reload();
49
+
50
+ const skills = skillComponent.getSkillList();
51
+ output.log(chalk.green(`Successfully reloaded ${skills.length} skills`));
52
+ } catch (error) {
53
+ output.error(`Failed to reload skills: ${error}`);
54
+ process.exit(1);
55
+ } finally {
56
+ await envService.dispose();
57
+ }
58
+ },
59
+ };