@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,107 @@
1
+ /**
2
+ * @fileoverview Tools List Command
3
+ *
4
+ * 命令:roy tools 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 { ToolComponent, Tool } from "@ai-setting/roy-agent-core";
12
+
13
+ interface ListOptions {
14
+ json?: boolean;
15
+ quiet?: boolean;
16
+ config?: string;
17
+ }
18
+
19
+ export const ListCommand: CommandModule<object, ListOptions> = {
20
+ command: "list",
21
+ aliases: ["ls"],
22
+ describe: "列出所有可用工具",
23
+
24
+ builder: (yargs) =>
25
+ yargs
26
+ .option("json", {
27
+ alias: "j",
28
+ type: "boolean",
29
+ default: false,
30
+ description: "JSON 输出",
31
+ })
32
+ .option("quiet", {
33
+ alias: "q",
34
+ type: "boolean",
35
+ default: false,
36
+ description: "简洁输出(仅名称)",
37
+ }),
38
+
39
+ async handler(args) {
40
+ const output = new OutputService();
41
+ const envService = new EnvironmentService(output);
42
+
43
+ try {
44
+ await envService.create({ configPath: args.config });
45
+ const env = envService.getEnvironment();
46
+ if (!env) {
47
+ output.error("Failed to create environment");
48
+ process.exit(1);
49
+ }
50
+ const toolComponent = env.getComponent("tool") as ToolComponent | undefined;
51
+
52
+ if (!toolComponent) {
53
+ output.error("ToolComponent not available");
54
+ process.exit(1);
55
+ }
56
+
57
+ const tools = toolComponent.listTools();
58
+
59
+ if (args.json) {
60
+ output.json({
61
+ tools: tools.map((t: Tool) => ({
62
+ name: t.name,
63
+ description: t.description,
64
+ category: t.metadata?.category,
65
+ })),
66
+ count: tools.length,
67
+ });
68
+ } else if (args.quiet) {
69
+ tools.forEach((t: Tool) => output.log(t.name));
70
+ } else {
71
+ // 表格输出
72
+ const header = [
73
+ chalk.bold("Name"),
74
+ chalk.bold("Description"),
75
+ chalk.bold("Category"),
76
+ ].join(" | ");
77
+
78
+ const rows = tools.map((t: Tool) => {
79
+ const desc =
80
+ t.description.length > 40
81
+ ? t.description.slice(0, 37) + "..."
82
+ : t.description;
83
+ return [
84
+ chalk.cyan(t.name),
85
+ desc,
86
+ t.metadata?.category ? chalk.gray(`[${t.metadata.category}]`) : "-",
87
+ ].join(" | ");
88
+ });
89
+
90
+ output.log([
91
+ `┌─ Tools ${"─".repeat(48)}┐`,
92
+ `│${header}│`,
93
+ "├" + "─".repeat(header.length + 2) + "┤",
94
+ ...rows.map((r) => `│${r}│`),
95
+ "└" + "─".repeat(header.length + 2) + "┘",
96
+ "",
97
+ chalk.gray(`Total: ${tools.length} tools`),
98
+ ].join("\n"));
99
+ }
100
+ } catch (error) {
101
+ output.error(`Failed to list tools: ${error}`);
102
+ process.exit(1);
103
+ } finally {
104
+ await envService.dispose();
105
+ }
106
+ },
107
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Tools Shared Module
3
+ *
4
+ * 导出共享的工具函数
5
+ */
6
+
7
+ export * from "./schema-helper";
@@ -0,0 +1,111 @@
1
+ /**
2
+ * @fileoverview Schema Helper
3
+ *
4
+ * 提供 Zod schema 解析和工具函数,用于从 tool 的参数 schema
5
+ * 提取参数信息、验证必需参数、生成 yargs 选项等。
6
+ */
7
+
8
+ import { z, ZodType } from "zod";
9
+ import { zodToJsonSchema } from "zod-to-json-schema";
10
+
11
+ /**
12
+ * 参数信息
13
+ */
14
+ export interface ParameterInfo {
15
+ /** 参数名称 */
16
+ name: string;
17
+ /** 参数类型 */
18
+ type: string;
19
+ /** 参数描述 */
20
+ description: string;
21
+ /** 是否必需 */
22
+ required: boolean;
23
+ /** 默认值 */
24
+ default?: unknown;
25
+ }
26
+
27
+ /**
28
+ * 验证结果
29
+ */
30
+ export interface ValidateResult {
31
+ /** 是否有效 */
32
+ valid: boolean;
33
+ /** 缺少的必需参数 */
34
+ missing: string[];
35
+ }
36
+
37
+ /**
38
+ * 从 Zod schema 提取参数信息
39
+ *
40
+ * @param schema - Zod schema
41
+ * @returns 参数信息数组
42
+ */
43
+ export function extractParameters(schema: any): ParameterInfo[] {
44
+ const jsonSchema = zodToJsonSchema(schema) as Record<string, unknown>;
45
+ const properties = (jsonSchema.properties || {}) as Record<string, unknown>;
46
+ const required = ((jsonSchema.required as string[]) || []);
47
+
48
+ return Object.entries(properties).map(([name, prop]) => ({
49
+ name,
50
+ type: (prop as any).type || "any",
51
+ description: (prop as any).description || "",
52
+ required: required.includes(name),
53
+ default: (prop as any).default,
54
+ }));
55
+ }
56
+
57
+ /**
58
+ * 检查参数是否完整
59
+ *
60
+ * @param params - 参数信息数组
61
+ * @param args - 用户提供的参数
62
+ * @returns 验证结果
63
+ */
64
+ export function validateRequiredParams(
65
+ params: ParameterInfo[],
66
+ args: Record<string, unknown>
67
+ ): ValidateResult {
68
+ const missing = params
69
+ .filter((p) => p.required)
70
+ .filter((p) => args[p.name] === undefined)
71
+ .map((p) => p.name);
72
+
73
+ return {
74
+ valid: missing.length === 0,
75
+ missing,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * 格式化参数为 yargs 选项
81
+ *
82
+ * @param params - 参数信息数组
83
+ * @returns yargs 选项配置
84
+ */
85
+ export function formatYargsOptions(params: ParameterInfo[]): Record<string, any> {
86
+ const options: Record<string, any> = {};
87
+
88
+ for (const param of params) {
89
+ // 映射类型到 yargs 类型
90
+ let yargsType: "string" | "number" | "boolean";
91
+ if (param.type === "number" || param.type === "integer") {
92
+ yargsType = "number";
93
+ } else if (param.type === "boolean") {
94
+ yargsType = "boolean";
95
+ } else {
96
+ yargsType = "string";
97
+ }
98
+
99
+ options[param.name] = {
100
+ type: yargsType,
101
+ description: param.description || param.name,
102
+ demandOption: param.required,
103
+ };
104
+
105
+ if (param.default !== undefined) {
106
+ options[param.name].default = param.default;
107
+ }
108
+ }
109
+
110
+ return options;
111
+ }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * @fileoverview Workflow Add Command
3
+ *
4
+ * Add a new workflow from file or natural language description
5
+ */
6
+
7
+ import { CommandModule } from 'yargs';
8
+ import { EnvironmentService } from '../../../services/environment.service';
9
+ import { OutputService } from '../../../services/output.service';
10
+ import { renderWorkflowAdded, renderNodesList } from '../renderers';
11
+ import { WorkflowValidator } from '../validators/workflow-validator';
12
+ import chalk from 'chalk';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import type { WorkflowService, WorkflowDefinition } from '@ai-setting/roy-agent-core/env/workflow/service';
16
+ import type { AgentComponent } from '@ai-setting/roy-agent-core';
17
+ import { createWorkflowExtractorAgent } from '@ai-setting/roy-agent-core/env/task/plugins';
18
+
19
+ /**
20
+ * Parse workflow file content (YAML or JSON)
21
+ * Uses WorkflowDefinitionSchema to ensure default values are applied
22
+ */
23
+ async function parseWorkflowContent(content: string, filename: string): Promise<WorkflowDefinition> {
24
+ const isYaml = filename.endsWith('.yaml') || filename.endsWith('.yml');
25
+ const isJson = filename.endsWith('.json');
26
+
27
+ let definition: WorkflowDefinition;
28
+
29
+ if (isYaml) {
30
+ // Dynamic import for YAML parser
31
+ const yaml = await import('yaml');
32
+ const parsed = yaml.parse(content);
33
+ // Use WorkflowDefinitionSchema to apply defaults
34
+ const { WorkflowDefinitionSchema } = await import('@ai-setting/roy-agent-core/env/workflow/types');
35
+ definition = WorkflowDefinitionSchema.parse(parsed);
36
+ } else if (isJson) {
37
+ const parsed = JSON.parse(content);
38
+ const { WorkflowDefinitionSchema } = await import('@ai-setting/roy-agent-core/env/workflow/types');
39
+ definition = WorkflowDefinitionSchema.parse(parsed);
40
+ } else {
41
+ // Auto-detect: try JSON first, then YAML
42
+ try {
43
+ const parsed = JSON.parse(content);
44
+ const { WorkflowDefinitionSchema } = await import('@ai-setting/roy-agent-core/env/workflow/types');
45
+ definition = WorkflowDefinitionSchema.parse(parsed);
46
+ } catch {
47
+ const yaml = await import('yaml');
48
+ const parsed = yaml.parse(content);
49
+ const { WorkflowDefinitionSchema } = await import('@ai-setting/roy-agent-core/env/workflow/types');
50
+ definition = WorkflowDefinitionSchema.parse(parsed);
51
+ }
52
+ }
53
+
54
+ return definition;
55
+ }
56
+
57
+ /**
58
+ * Parse YAML from agent output
59
+ */
60
+ function parseYamlFromAgentOutput(output: string): WorkflowDefinition | null {
61
+ // 提取 YAML 代码块
62
+ const yamlMatch = output.match(/```yaml\n?([\s\S]*?)\n?```/) ||
63
+ output.match(/```\n?([\s\S]*?)\n?```/) ||
64
+ output.match(/(\|[\s\S]*?)(?:\n\n|$)/);
65
+
66
+ const yamlContent = yamlMatch?.[1] || output;
67
+
68
+ try {
69
+ const YAML = require('yaml');
70
+ const parsed = YAML.parse(yamlContent);
71
+
72
+ // 验证必要字段
73
+ if (!parsed.name || !parsed.nodes || !Array.isArray(parsed.nodes)) {
74
+ return null;
75
+ }
76
+
77
+ // Use WorkflowDefinitionSchema to apply defaults
78
+ const { WorkflowDefinitionSchema } = require('@ai-setting/roy-agent-core/env/workflow/types');
79
+ const definition = WorkflowDefinitionSchema.parse(parsed);
80
+
81
+ return definition;
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ export interface WorkflowAddOptions {
88
+ name?: string;
89
+ file?: string;
90
+ desc?: string;
91
+ description?: string;
92
+ tags?: string[];
93
+ taskId?: number;
94
+ force?: boolean;
95
+ validate?: boolean;
96
+ config?: string;
97
+ }
98
+
99
+ /**
100
+ * WorkflowAddCommand - Add a new workflow
101
+ */
102
+ export const WorkflowAddCommand: CommandModule<object, object> = {
103
+ command: 'add',
104
+ describe: '添加新的 Workflow(支持文件或自然语言描述)',
105
+
106
+ builder: (yargs) =>
107
+ yargs
108
+ .option('name', {
109
+ alias: 'n',
110
+ describe: 'Workflow 名称',
111
+ type: 'string',
112
+ })
113
+ .option('file', {
114
+ alias: 'f',
115
+ describe: 'Workflow 定义文件 (YAML/JSON)',
116
+ type: 'string',
117
+ })
118
+ .option('desc', {
119
+ alias: 'd',
120
+ describe: '自然语言描述(将使用 AI 生成 Workflow)',
121
+ type: 'string',
122
+ })
123
+ .option('description', {
124
+ describe: 'Workflow 描述(仅用于 --file 模式)',
125
+ type: 'string',
126
+ })
127
+ .option('tags', {
128
+ alias: 't',
129
+ describe: '标签(逗号分隔)',
130
+ type: 'string',
131
+ })
132
+ .option('task-id', {
133
+ alias: 'i',
134
+ describe: '关联任务 ID',
135
+ type: 'number',
136
+ })
137
+ .option('force', {
138
+ describe: '覆盖已存在的 Workflow',
139
+ type: 'boolean',
140
+ default: false,
141
+ })
142
+ .option('validate', {
143
+ describe: '保存前验证 Workflow',
144
+ type: 'boolean',
145
+ default: true,
146
+ })
147
+ .option('config', {
148
+ describe: '配置文件路径',
149
+ type: 'string',
150
+ }),
151
+
152
+ async handler(args) {
153
+ const a = args as any;
154
+ const output = new OutputService();
155
+ const envService = new EnvironmentService(output);
156
+
157
+ try {
158
+ await envService.create({ configPath: a.config });
159
+ const env = envService.getEnvironment();
160
+ if (!env) {
161
+ output.error("Failed to create environment");
162
+ process.exit(1);
163
+ }
164
+ const workflowComponent = env.getComponent('workflow') as any;
165
+
166
+ if (!workflowComponent) {
167
+ output.error('WorkflowComponent not available');
168
+ process.exit(1);
169
+ }
170
+
171
+ const service = workflowComponent.getService() as WorkflowService;
172
+
173
+ let definition: WorkflowDefinition | null;
174
+ let workflowName: string;
175
+
176
+ // Mode 1: File input
177
+ if (a.file) {
178
+ const filePath = path.resolve(a.file);
179
+ if (!fs.existsSync(filePath)) {
180
+ output.error(`File not found: ${filePath}`);
181
+ process.exit(1);
182
+ }
183
+
184
+ const content = fs.readFileSync(filePath, 'utf-8');
185
+ definition = await parseWorkflowContent(content, filePath);
186
+ workflowName = a.name || definition.name as string || path.basename(filePath);
187
+
188
+ // Validate if requested
189
+ if (a.validate) {
190
+ output.log(chalk.blue('🔍 Validating workflow...'));
191
+ const validator = new WorkflowValidator();
192
+ const result = validator.validate(definition);
193
+
194
+ if (!result.valid) {
195
+ output.error(`\n❌ Workflow validation FAILED (${result.errors.length} errors found)`);
196
+ result.errors.forEach((error, i) => {
197
+ output.error(`[Error ${i + 1}/${result.errors.length}]${error.nodeId ? ` Node "${error.nodeId}"` : ''}`);
198
+ output.error(` Type: ${error.type}`);
199
+ output.error(` Description: ${error.description}`);
200
+ output.error(` Fix: ${error.fix}\n`);
201
+ });
202
+ process.exit(1);
203
+ }
204
+ output.log(chalk.green('✅ Workflow validation passed\n'));
205
+ }
206
+ }
207
+ // Mode 2: Natural language description
208
+ else if (a.desc) {
209
+ output.log(chalk.blue('🤖 Generating workflow from description...\n'));
210
+
211
+ // Get AgentComponent
212
+ const agentComponent = env.getComponent('agent') as AgentComponent;
213
+ if (!agentComponent) {
214
+ output.error('AgentComponent not available');
215
+ process.exit(1);
216
+ }
217
+
218
+ // Auto-register workflow-extractor agent if not already registered
219
+ if (!agentComponent.getAgent('workflow-extractor')) {
220
+ output.log(chalk.gray('Auto-registering workflow-extractor agent...'));
221
+ const extractorConfig = createWorkflowExtractorAgent();
222
+ agentComponent.registerAgent(extractorConfig.name!, extractorConfig);
223
+ output.log(chalk.green('✅ workflow-extractor agent registered\n'));
224
+ }
225
+
226
+ // Build prompt for workflow-extractor
227
+ const prompt = `## 任务
228
+ 请根据以下描述,构建一个可复用的 Workflow。
229
+ 使用迭代式流程:生成 YAML → validate 验证 → 修正错误 → 重复直到通过验证。
230
+
231
+ ## Workflow 描述
232
+ ${a.desc}
233
+
234
+ ## 重要提醒
235
+ 1. 先用 \`roy workflow nodes\` 查看可用的节点类型
236
+ 2. 生成的 YAML 必须通过 \`roy workflow validate --yaml "<yaml>"\` 验证
237
+ 3. 验证通过后才输出最终 YAML`;
238
+
239
+ // Run workflow-extractor agent
240
+ output.log(chalk.gray('Running workflow-extractor agent...'));
241
+ const result = await agentComponent.run('workflow-extractor', prompt);
242
+ const agentOutput = result.finalText || '';
243
+
244
+ // Parse YAML from agent output
245
+ definition = parseYamlFromAgentOutput(agentOutput);
246
+ if (definition === null) {
247
+ output.error('Failed to parse workflow from agent output');
248
+ output.log(chalk.gray('\nAgent output:\n' + agentOutput.slice(0, 500)));
249
+ process.exit(1);
250
+ }
251
+
252
+ workflowName = a.name || definition.name as string;
253
+
254
+ // Validate the generated workflow
255
+ if (a.validate) {
256
+ output.log(chalk.blue('\n🔍 Validating generated workflow...'));
257
+ const validator = new WorkflowValidator();
258
+ const result = validator.validate(definition);
259
+
260
+ if (!result.valid) {
261
+ output.error(`\n❌ Generated workflow validation FAILED (${result.errors.length} errors found)`);
262
+ result.errors.forEach((error, i) => {
263
+ output.error(`[Error ${i + 1}/${result.errors.length}]${error.nodeId ? ` Node "${error.nodeId}"` : ''}`);
264
+ output.error(` Type: ${error.type}`);
265
+ output.error(` Description: ${error.description}`);
266
+ output.error(` Fix: ${error.fix}\n`);
267
+ });
268
+ output.log(chalk.yellow('请修正描述后重新尝试,或使用 --file 选项直接提供 YAML'));
269
+ process.exit(1);
270
+ }
271
+ output.log(chalk.green('✅ Generated workflow validation passed\n'));
272
+ }
273
+
274
+ // Show generated YAML
275
+ const yaml = await import('yaml');
276
+ output.log(chalk.gray('--- Generated YAML ---'));
277
+ output.log(yaml.stringify(definition));
278
+ output.log(chalk.gray('------------------------\n'));
279
+ }
280
+ else {
281
+ output.error('Please provide --file or --desc option');
282
+ output.error('Usage: roy workflow add --file <path> | --desc "<description>"');
283
+ process.exit(1);
284
+ }
285
+
286
+ // Parse tags
287
+ const tags = a.tags ? a.tags.split(',').map((t: string) => t.trim()) : undefined;
288
+
289
+ // Create workflow
290
+ const workflow = await service.createWorkflow(definition, {
291
+ tags,
292
+ taskId: a.taskId,
293
+ force: a.force,
294
+ });
295
+
296
+ output.log(renderWorkflowAdded(workflow));
297
+
298
+ // Show nodes summary
299
+ output.log(chalk.bold('\n📊 Nodes Summary:'));
300
+ output.log(renderNodesList(definition.nodes));
301
+
302
+ output.log(chalk.green(`\n✅ Workflow '${workflowName}' added successfully`));
303
+ } catch (error: any) {
304
+ if (error.message?.includes('already exists') && !a.force) {
305
+ output.error(`Workflow already exists. Use --force to overwrite.`);
306
+ output.error(`Error: ${error.message}`);
307
+ } else {
308
+ output.error(`Failed to add workflow: ${error}`);
309
+ }
310
+ process.exit(1);
311
+ } finally {
312
+ await envService.dispose();
313
+ }
314
+ },
315
+ };