@fieldwangai/agentflow 0.1.25

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 (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -0
  3. package/README.zh-CN.md +201 -0
  4. package/agents/agentflow-node-executor-code.md +32 -0
  5. package/agents/agentflow-node-executor-planning.md +32 -0
  6. package/agents/agentflow-node-executor-requirement.md +32 -0
  7. package/agents/agentflow-node-executor-test.md +32 -0
  8. package/agents/agentflow-node-executor-ui.md +32 -0
  9. package/agents/agentflow-node-executor.md +32 -0
  10. package/agents/agents.json +8 -0
  11. package/agents/en/agentflow-node-executor.md +32 -0
  12. package/agents/zh/agentflow-node-executor.md +32 -0
  13. package/bin/agentflow.mjs +52 -0
  14. package/bin/ensure-workspace-reference.mjs +35 -0
  15. package/bin/lib/agent-runners.mjs +1199 -0
  16. package/bin/lib/agents-path.mjs +61 -0
  17. package/bin/lib/api-runner.mjs +361 -0
  18. package/bin/lib/apply.mjs +852 -0
  19. package/bin/lib/catalog-agents.mjs +300 -0
  20. package/bin/lib/catalog-flows.mjs +532 -0
  21. package/bin/lib/composer-agent.mjs +884 -0
  22. package/bin/lib/composer-flow-instances.mjs +68 -0
  23. package/bin/lib/composer-flow-skeleton.mjs +334 -0
  24. package/bin/lib/composer-flow-validate.mjs +47 -0
  25. package/bin/lib/composer-log.mjs +197 -0
  26. package/bin/lib/composer-model-router.mjs +160 -0
  27. package/bin/lib/composer-node-schema.mjs +299 -0
  28. package/bin/lib/composer-planner.mjs +749 -0
  29. package/bin/lib/composer-script-ops.mjs +233 -0
  30. package/bin/lib/composer-skill-router.mjs +384 -0
  31. package/bin/lib/flow-import.mjs +305 -0
  32. package/bin/lib/flow-normalize.mjs +71 -0
  33. package/bin/lib/flow-write.mjs +395 -0
  34. package/bin/lib/help.mjs +139 -0
  35. package/bin/lib/hub-login.mjs +54 -0
  36. package/bin/lib/hub-publish.mjs +159 -0
  37. package/bin/lib/hub-remote.mjs +189 -0
  38. package/bin/lib/hub.mjs +299 -0
  39. package/bin/lib/i18n.mjs +233 -0
  40. package/bin/lib/locales/en.json +344 -0
  41. package/bin/lib/locales/zh.json +344 -0
  42. package/bin/lib/log.mjs +37 -0
  43. package/bin/lib/main.mjs +611 -0
  44. package/bin/lib/model-config.mjs +118 -0
  45. package/bin/lib/model-lists.mjs +188 -0
  46. package/bin/lib/node-exec-context.mjs +336 -0
  47. package/bin/lib/node-execute.mjs +513 -0
  48. package/bin/lib/normalize-node-tool-command.mjs +97 -0
  49. package/bin/lib/paths.mjs +216 -0
  50. package/bin/lib/pipeline-scripts.mjs +41 -0
  51. package/bin/lib/recent-runs.mjs +173 -0
  52. package/bin/lib/run-apply-active-lock.mjs +82 -0
  53. package/bin/lib/run-events.mjs +85 -0
  54. package/bin/lib/run-node-statuses-from-disk.mjs +85 -0
  55. package/bin/lib/schedule-config.mjs +227 -0
  56. package/bin/lib/scheduler.mjs +312 -0
  57. package/bin/lib/table.mjs +4 -0
  58. package/bin/lib/terminal.mjs +42 -0
  59. package/bin/lib/ui-print.mjs +94 -0
  60. package/bin/lib/ui-server.mjs +2113 -0
  61. package/bin/lib/workspace-tree.mjs +266 -0
  62. package/bin/lib/workspace.mjs +180 -0
  63. package/bin/pipeline/build-node-prompt.mjs +179 -0
  64. package/bin/pipeline/check-cache.mjs +191 -0
  65. package/bin/pipeline/check-flow.mjs +543 -0
  66. package/bin/pipeline/collect-nodes.mjs +212 -0
  67. package/bin/pipeline/compute-cache-md5.mjs +177 -0
  68. package/bin/pipeline/ensure-run-dir.mjs +71 -0
  69. package/bin/pipeline/extract-thinking.mjs +308 -0
  70. package/bin/pipeline/gc.mjs +129 -0
  71. package/bin/pipeline/get-env.mjs +83 -0
  72. package/bin/pipeline/get-exec-id.mjs +145 -0
  73. package/bin/pipeline/get-ready-nodes.mjs +435 -0
  74. package/bin/pipeline/get-resolved-values.mjs +337 -0
  75. package/bin/pipeline/load-key.mjs +62 -0
  76. package/bin/pipeline/parse-bool.mjs +33 -0
  77. package/bin/pipeline/parse-flow.mjs +698 -0
  78. package/bin/pipeline/post-process-control-if.mjs +23 -0
  79. package/bin/pipeline/post-process-node.mjs +490 -0
  80. package/bin/pipeline/pre-process-node.mjs +449 -0
  81. package/bin/pipeline/resolve-inputs.mjs +201 -0
  82. package/bin/pipeline/run-log.mjs +34 -0
  83. package/bin/pipeline/run-tool-nodejs.mjs +160 -0
  84. package/bin/pipeline/save-key.mjs +93 -0
  85. package/bin/pipeline/snapshot-prior-round.mjs +70 -0
  86. package/bin/pipeline/validate-flow.mjs +825 -0
  87. package/bin/pipeline/validate-for-ui.mjs +226 -0
  88. package/bin/pipeline/validate-script-output.mjs +130 -0
  89. package/bin/pipeline/write-result.mjs +182 -0
  90. package/builtin/nodes/agent_subAgent.md +14 -0
  91. package/builtin/nodes/control_agent_toBool.md +20 -0
  92. package/builtin/nodes/control_anyOne.md +17 -0
  93. package/builtin/nodes/control_end.md +11 -0
  94. package/builtin/nodes/control_if.md +20 -0
  95. package/builtin/nodes/control_start.md +11 -0
  96. package/builtin/nodes/control_toBool.md +21 -0
  97. package/builtin/nodes/provide_file.md +11 -0
  98. package/builtin/nodes/provide_str.md +11 -0
  99. package/builtin/nodes/tool_get_env.md +14 -0
  100. package/builtin/nodes/tool_load_key.md +20 -0
  101. package/builtin/nodes/tool_nodejs.md +40 -0
  102. package/builtin/nodes/tool_print.md +14 -0
  103. package/builtin/nodes/tool_save_key.md +20 -0
  104. package/builtin/nodes/tool_user_ask.md +23 -0
  105. package/builtin/nodes/tool_user_check.md +22 -0
  106. package/builtin/pipelines/module-migrate/flow.yaml +819 -0
  107. package/builtin/pipelines/module-migrate/scripts/check_imports.mjs +700 -0
  108. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Makefile +362 -0
  109. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
  110. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o.d +17 -0
  111. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o.d +5 -0
  112. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o.d +8 -0
  113. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/.deps/Release/tree_sitter_kotlin_binding.node.d +1 -0
  114. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  115. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/bindings/node/binding.o +0 -0
  116. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/parser.o +0 -0
  117. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/obj.target/tree_sitter_kotlin_binding/src/scanner.o +0 -0
  118. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/Release/tree_sitter_kotlin_binding.node +0 -0
  119. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/binding.Makefile +6 -0
  120. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/gyp-mac-tool +768 -0
  121. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
  122. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
  123. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
  124. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
  125. package/builtin/pipelines/module-migrate/scripts/node_modules/tree-sitter-kotlin/build/tree_sitter_kotlin_binding.target.mk +203 -0
  126. package/builtin/pipelines/new/flow.yaml +545 -0
  127. package/builtin/pipelines/new/scripts/check-flow.mjs +9 -0
  128. package/builtin/pipelines/new/scripts/collect-nodes.mjs +211 -0
  129. package/builtin/pipelines/scripts/adjust-node-positions.mjs +113 -0
  130. package/builtin/web-ui/dist/agentflow-icon.svg +23 -0
  131. package/builtin/web-ui/dist/assets/index-CZkUPcXE.css +1 -0
  132. package/builtin/web-ui/dist/assets/index-DkkhNESc.js +190 -0
  133. package/builtin/web-ui/dist/index.html +24 -0
  134. package/package.json +67 -0
  135. package/reference/flow-control-capabilities.md +274 -0
  136. package/reference/flow-layout.md +84 -0
  137. package/reference/flow-prompt-handler-check.md +12 -0
  138. package/reference/flow-result-semantics.md +14 -0
@@ -0,0 +1,300 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { getAgentPath, readAgentFrontmatter } from "./agents-path.mjs";
5
+ import { collectPipelineNamesFromDir } from "./catalog-flows.mjs";
6
+ import { log } from "./log.mjs";
7
+ import { t } from "./i18n.mjs";
8
+ import {
9
+ PACKAGE_AGENTS_DIR,
10
+ PACKAGE_AGENTS_JSON,
11
+ PACKAGE_BUILTIN_PIPELINES_DIR,
12
+ USER_AGENTS_FILEPATH_PREFIX,
13
+ getUserAgentsDirAbs,
14
+ getUserAgentsJsonAbs,
15
+ getUserPipelinesRoot,
16
+ } from "./paths.mjs";
17
+ import { Table } from "./table.mjs";
18
+
19
+ /** 列出所有角色:包内 agents.json(builtin)+ 用户数据目录 */
20
+ export function listAgentsJson(_workspaceRoot) {
21
+ const out = [];
22
+
23
+ if (fs.existsSync(PACKAGE_AGENTS_JSON) && fs.statSync(PACKAGE_AGENTS_JSON).isFile()) {
24
+ try {
25
+ const raw = fs.readFileSync(PACKAGE_AGENTS_JSON, "utf8");
26
+ const arr = JSON.parse(raw);
27
+ if (Array.isArray(arr)) {
28
+ for (const e of arr) {
29
+ const id = e && typeof e.id === "string" ? e.id.trim() : "";
30
+ if (!id) continue;
31
+ const filepath = e.filepath != null ? String(e.filepath) : `agents/${id}.md`;
32
+ out.push({
33
+ id,
34
+ name: e.name != null ? String(e.name) : id,
35
+ description: e.description != null ? String(e.description) : null,
36
+ source: "builtin",
37
+ filepath,
38
+ });
39
+ }
40
+ }
41
+ } catch (_) {}
42
+ }
43
+ if (out.length === 0 && fs.existsSync(PACKAGE_AGENTS_DIR) && fs.statSync(PACKAGE_AGENTS_DIR).isDirectory()) {
44
+ const names = fs.readdirSync(PACKAGE_AGENTS_DIR);
45
+ for (const n of names) {
46
+ if (!n.endsWith(".md")) continue;
47
+ const id = n.slice(0, -3);
48
+ const fp = path.join(PACKAGE_AGENTS_DIR, n);
49
+ if (!fs.statSync(fp).isFile()) continue;
50
+ const { name, description } = readAgentFrontmatter(fp);
51
+ out.push({
52
+ id,
53
+ name: name || id,
54
+ description: description || null,
55
+ source: "builtin",
56
+ filepath: `agents/${id}.md`,
57
+ });
58
+ }
59
+ }
60
+
61
+ const userAgentsJsonPath = getUserAgentsJsonAbs();
62
+ const userAgentsDir = getUserAgentsDirAbs();
63
+ let userList = [];
64
+ if (fs.existsSync(userAgentsJsonPath) && fs.statSync(userAgentsJsonPath).isFile()) {
65
+ try {
66
+ const raw = fs.readFileSync(userAgentsJsonPath, "utf8");
67
+ const arr = JSON.parse(raw);
68
+ if (Array.isArray(arr)) {
69
+ userList = arr
70
+ .filter((e) => e && typeof e.id === "string" && e.id.trim())
71
+ .map((e) => ({
72
+ id: e.id.trim(),
73
+ name: e.name != null ? String(e.name) : e.id.trim(),
74
+ description: e.description != null ? String(e.description) : null,
75
+ source: "user",
76
+ filepath: e.filepath != null ? String(e.filepath) : `${USER_AGENTS_FILEPATH_PREFIX}/${e.id.trim()}.md`,
77
+ }));
78
+ }
79
+ } catch (_) {}
80
+ }
81
+ if (userList.length === 0 && fs.existsSync(userAgentsDir) && fs.statSync(userAgentsDir).isDirectory()) {
82
+ const names = fs.readdirSync(userAgentsDir);
83
+ for (const n of names) {
84
+ if (!n.endsWith(".md")) continue;
85
+ const id = n.slice(0, -3);
86
+ const fp = path.join(userAgentsDir, n);
87
+ if (!fs.statSync(fp).isFile()) continue;
88
+ const { name, description } = readAgentFrontmatter(fp);
89
+ userList.push({
90
+ id,
91
+ name: name || id,
92
+ description: description || null,
93
+ source: "user",
94
+ filepath: `${USER_AGENTS_FILEPATH_PREFIX}/${id}.md`,
95
+ });
96
+ }
97
+ if (userList.length > 0) {
98
+ try {
99
+ fs.mkdirSync(path.dirname(userAgentsJsonPath), { recursive: true });
100
+ fs.writeFileSync(userAgentsJsonPath, JSON.stringify(userList, null, 2), "utf8");
101
+ } catch (_) {}
102
+ }
103
+ }
104
+ for (const e of userList) out.push(e);
105
+
106
+ out.sort((a, b) => (a.source !== b.source ? (a.source === "builtin" ? -1 : 1) : a.id.localeCompare(b.id)));
107
+ return out;
108
+ }
109
+
110
+ export function listAgentsTable(workspaceRoot) {
111
+ const rows = listAgentsJson(workspaceRoot);
112
+ if (rows.length === 0) {
113
+ log.info("No agents found (builtin: agents/agents.json, user: ~/agentflow/agents.json).");
114
+ return;
115
+ }
116
+ const table = new Table({
117
+ head: [chalk.cyan("id"), chalk.cyan("name"), chalk.cyan("source"), chalk.cyan("description")],
118
+ colWidths: [36, 16, 10, 40],
119
+ style: { head: [], border: ["grey"] },
120
+ });
121
+ for (const row of rows) {
122
+ const sourceLabel = row.source === "builtin" ? "builtin" : "user";
123
+ const desc = row.description != null ? String(row.description).slice(0, 38) + (String(row.description).length > 38 ? "…" : "") : "";
124
+ table.push([row.id, row.name || row.id, sourceLabel, desc]);
125
+ }
126
+ log.info("\n" + chalk.bold("Agents (roles)"));
127
+ log.info(table.toString());
128
+ }
129
+
130
+ export function copyBuiltinAgentJson(workspaceRoot, builtinAgentId, targetId) {
131
+ const destId = (targetId && String(targetId).trim()) || builtinAgentId;
132
+ const srcFile = path.join(PACKAGE_AGENTS_DIR, `${builtinAgentId}.md`);
133
+ const userDir = getUserAgentsDirAbs();
134
+ const destFile = path.join(userDir, `${destId}.md`);
135
+ const userJsonPath = getUserAgentsJsonAbs();
136
+ if (!fs.existsSync(srcFile) || !fs.statSync(srcFile).isFile()) {
137
+ return { success: false, error: t("catalog.builtin_agent_not_found") };
138
+ }
139
+ if (fs.existsSync(destFile)) {
140
+ return { success: false, error: t("catalog.name_already_exists") };
141
+ }
142
+ let name = destId;
143
+ let description = null;
144
+ if (fs.existsSync(PACKAGE_AGENTS_JSON) && fs.statSync(PACKAGE_AGENTS_JSON).isFile()) {
145
+ try {
146
+ const raw = fs.readFileSync(PACKAGE_AGENTS_JSON, "utf8");
147
+ const arr = JSON.parse(raw);
148
+ const built = Array.isArray(arr) ? arr.find((e) => e && e.id === builtinAgentId) : null;
149
+ if (built) {
150
+ if (built.name != null) name = String(built.name);
151
+ if (built.description != null) description = String(built.description);
152
+ }
153
+ } catch (_) {}
154
+ }
155
+ if (name === destId) {
156
+ const { name: fmName, description: fmDesc } = readAgentFrontmatter(srcFile);
157
+ if (fmName) name = fmName;
158
+ if (fmDesc) description = fmDesc;
159
+ }
160
+ try {
161
+ fs.mkdirSync(userDir, { recursive: true });
162
+ fs.copyFileSync(srcFile, destFile);
163
+ const filepath = `${USER_AGENTS_FILEPATH_PREFIX}/${destId}.md`;
164
+ const entry = { id: destId, name, description, filepath };
165
+ let list = [];
166
+ if (fs.existsSync(userJsonPath) && fs.statSync(userJsonPath).isFile()) {
167
+ try {
168
+ const raw = fs.readFileSync(userJsonPath, "utf8");
169
+ const arr = JSON.parse(raw);
170
+ if (Array.isArray(arr)) list = arr.filter((e) => e && e.id !== destId);
171
+ } catch (_) {}
172
+ }
173
+ list.push(entry);
174
+ list.sort((a, b) => a.id.localeCompare(b.id));
175
+ fs.mkdirSync(path.dirname(userJsonPath), { recursive: true });
176
+ fs.writeFileSync(userJsonPath, JSON.stringify(list, null, 2), "utf8");
177
+ return { success: true };
178
+ } catch (e) {
179
+ return { success: false, error: (e && e.message) || String(e) };
180
+ }
181
+ }
182
+
183
+ export function readAgentJson(workspaceRoot, agentId) {
184
+ const agentPath = getAgentPath(workspaceRoot, agentId);
185
+ if (!fs.existsSync(agentPath) || !fs.statSync(agentPath).isFile()) {
186
+ return { error: t("catalog.agent_not_found") };
187
+ }
188
+ try {
189
+ const content = fs.readFileSync(agentPath, "utf8");
190
+ return { content };
191
+ } catch (e) {
192
+ return { error: (e && e.message) || String(e) };
193
+ }
194
+ }
195
+
196
+ export function addRoleJson(workspaceRoot, opts) {
197
+ const { builtin = false, id, name, description, contentPath } = opts;
198
+ const idStr = id && typeof id === "string" ? id.trim() : "";
199
+ if (!idStr || idStr.includes("/") || idStr.includes("\\") || idStr.includes("..")) {
200
+ return { success: false, error: t("catalog.invalid_agent_id") };
201
+ }
202
+ if (builtin) {
203
+ const destFile = path.join(PACKAGE_AGENTS_DIR, `${idStr}.md`);
204
+ const destJson = PACKAGE_AGENTS_JSON;
205
+ if (fs.existsSync(destFile)) {
206
+ return { success: false, error: t("catalog.name_already_exists") };
207
+ }
208
+ let content = `---
209
+ name: ${name != null ? String(name).replace(/\n/g, " ") : idStr}
210
+ description: ${description != null ? String(description).replace(/\n/g, " ") : ""}
211
+ ---
212
+
213
+ ## 角色定义
214
+
215
+ (待编辑)
216
+ `;
217
+ if (contentPath && fs.existsSync(contentPath) && fs.statSync(contentPath).isFile()) {
218
+ content = fs.readFileSync(contentPath, "utf8");
219
+ }
220
+ try {
221
+ fs.writeFileSync(destFile, content, "utf8");
222
+ const filepath = `agents/${idStr}.md`;
223
+ const entry = { id: idStr, name: name != null ? String(name) : idStr, description: description != null ? String(description) : null, filepath };
224
+ let list = [];
225
+ if (fs.existsSync(destJson) && fs.statSync(destJson).isFile()) {
226
+ try {
227
+ const raw = fs.readFileSync(destJson, "utf8");
228
+ const arr = JSON.parse(raw);
229
+ if (Array.isArray(arr)) list = arr.filter((e) => e && e.id !== idStr);
230
+ } catch (_) {}
231
+ }
232
+ list.push(entry);
233
+ list.sort((a, b) => a.id.localeCompare(b.id));
234
+ fs.writeFileSync(destJson, JSON.stringify(list, null, 2), "utf8");
235
+ return { success: true };
236
+ } catch (e) {
237
+ return { success: false, error: (e && e.message) || String(e) };
238
+ }
239
+ }
240
+ const userDir = getUserAgentsDirAbs();
241
+ const destFile = path.join(userDir, `${idStr}.md`);
242
+ const userJsonPath = getUserAgentsJsonAbs();
243
+ if (fs.existsSync(destFile)) {
244
+ return { success: false, error: t("catalog.name_already_exists") };
245
+ }
246
+ let content = `---
247
+ name: ${name != null ? String(name).replace(/\n/g, " ") : idStr}
248
+ description: ${description != null ? String(description).replace(/\n/g, " ") : ""}
249
+ ---
250
+
251
+ ## 角色定义
252
+
253
+ (待编辑)
254
+ `;
255
+ if (contentPath && fs.existsSync(contentPath) && fs.statSync(contentPath).isFile()) {
256
+ content = fs.readFileSync(contentPath, "utf8");
257
+ }
258
+ try {
259
+ fs.mkdirSync(userDir, { recursive: true });
260
+ fs.writeFileSync(destFile, content, "utf8");
261
+ const filepath = `${USER_AGENTS_FILEPATH_PREFIX}/${idStr}.md`;
262
+ const entry = { id: idStr, name: name != null ? String(name) : idStr, description: description != null ? String(description) : null, filepath };
263
+ let list = [];
264
+ if (fs.existsSync(userJsonPath) && fs.statSync(userJsonPath).isFile()) {
265
+ try {
266
+ const raw = fs.readFileSync(userJsonPath, "utf8");
267
+ const arr = JSON.parse(raw);
268
+ if (Array.isArray(arr)) list = arr.filter((e) => e && e.id !== idStr);
269
+ } catch (_) {}
270
+ }
271
+ list.push(entry);
272
+ list.sort((a, b) => a.id.localeCompare(b.id));
273
+ fs.mkdirSync(path.dirname(userJsonPath), { recursive: true });
274
+ fs.writeFileSync(userJsonPath, JSON.stringify(list, null, 2), "utf8");
275
+ return { success: true };
276
+ } catch (e) {
277
+ return { success: false, error: (e && e.message) || String(e) };
278
+ }
279
+ }
280
+
281
+ export function copyBuiltinJson(workspaceRoot, flowId, targetFlowId) {
282
+ const destId = (targetFlowId && targetFlowId.trim()) || flowId;
283
+ const srcDir = path.join(PACKAGE_BUILTIN_PIPELINES_DIR, flowId);
284
+ const pipelinesRoot = getUserPipelinesRoot();
285
+ const destDir = path.join(pipelinesRoot, destId);
286
+ if (!fs.existsSync(srcDir) || !fs.existsSync(path.join(srcDir, "flow.yaml"))) {
287
+ return { success: false, error: t("catalog.builtin_flow_not_found") };
288
+ }
289
+ const existing = collectPipelineNamesFromDir(pipelinesRoot);
290
+ if (existing.includes(destId)) {
291
+ return { success: false, error: t("catalog.name_already_exists") };
292
+ }
293
+ try {
294
+ fs.mkdirSync(path.dirname(destDir), { recursive: true });
295
+ fs.cpSync(srcDir, destDir, { recursive: true });
296
+ return { success: true };
297
+ } catch (e) {
298
+ return { success: false, error: (e && e.message) || String(e) };
299
+ }
300
+ }