@fieldwangai/agentflow 0.1.85 → 0.1.86

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.
@@ -10,7 +10,7 @@ import path from "path";
10
10
  import { getAgentflowDataRoot, getAgentflowUserDataRoot, sanitizeAgentflowUserId } from "./paths.mjs";
11
11
  import { readMergedEnvObject } from "./user-env.mjs";
12
12
  import { resolveCliAndModel } from "./model-config.mjs";
13
- import { runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
13
+ import { runCodexAgentWithPrompt, runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
14
14
  import { planComposerTasks, hasPlannerApiAvailable, shouldUsePhased, classifyComplexity, classifyTaskComplexity, PHASED_DEFINITIONS } from "./composer-planner.mjs";
15
15
  import { executeScriptOp, isSupportedScriptOp } from "./composer-script-ops.mjs";
16
16
  import { routeModel } from "./composer-model-router.mjs";
@@ -152,12 +152,149 @@ function materializeWorkspaceCursorMcpPrivateConfig(workspaceRoot, userId) {
152
152
  };
153
153
  }
154
154
 
155
+ function tomlString(value) {
156
+ return JSON.stringify(String(value ?? ""));
157
+ }
158
+
159
+ function tomlArray(values) {
160
+ return `[${(Array.isArray(values) ? values : []).map((value) => tomlString(value)).join(", ")}]`;
161
+ }
162
+
163
+ function tomlInlineTable(obj) {
164
+ const entries = Object.entries(obj && typeof obj === "object" && !Array.isArray(obj) ? obj : {})
165
+ .filter(([key]) => String(key || "").trim())
166
+ .map(([key, value]) => `${tomlString(String(key).trim())} = ${tomlString(value)}`);
167
+ return `{ ${entries.join(", ")} }`;
168
+ }
169
+
170
+ function codexMcpName(name, used) {
171
+ const base = String(name || "mcp")
172
+ .trim()
173
+ .replace(/[^A-Za-z0-9_-]+/g, "_")
174
+ .replace(/^_+|_+$/g, "") || "mcp";
175
+ let out = base;
176
+ let i = 2;
177
+ while (used.has(out)) out = `${base}_${i++}`;
178
+ used.add(out);
179
+ return out;
180
+ }
181
+
182
+ function bearerFromHeaders(headers) {
183
+ const obj = headers && typeof headers === "object" && !Array.isArray(headers) ? headers : {};
184
+ for (const [key, value] of Object.entries(obj)) {
185
+ if (String(key).toLowerCase() !== "authorization") continue;
186
+ const match = String(value ?? "").match(/^Bearer\s+(.+)$/i);
187
+ if (match?.[1]) return match[1].trim();
188
+ }
189
+ return "";
190
+ }
191
+
192
+ function mergedCursorMcpServersForCodex(workspaceRoot, userId) {
193
+ const safe = sanitizeAgentflowUserId(userId);
194
+ const workspace = path.resolve(workspaceRoot || process.cwd());
195
+ const localServers = cursorMcpServersFromFile(path.join(workspace, ".cursor", "mcp.json"));
196
+ const globalServers = cursorMcpServersFromFile(path.join(os.homedir(), ".cursor", "mcp.json"));
197
+ const privateServers = safe ? readUserMcpPrivateServers(safe) : {};
198
+ const merged = { ...globalServers, ...localServers };
199
+
200
+ for (const [name, privateServer] of Object.entries(privateServers)) {
201
+ const current = merged[name];
202
+ if (!current || typeof current !== "object" || Array.isArray(current)) continue;
203
+ const privateEnv = privateServer?.env && typeof privateServer.env === "object" && !Array.isArray(privateServer.env) ? privateServer.env : {};
204
+ const privateHeaders = privateServer?.headers && typeof privateServer.headers === "object" && !Array.isArray(privateServer.headers) ? privateServer.headers : {};
205
+ const currentEnv = current.env && typeof current.env === "object" && !Array.isArray(current.env) ? current.env : {};
206
+ const currentHeaders = current.headers && typeof current.headers === "object" && !Array.isArray(current.headers) ? current.headers : {};
207
+ merged[name] = {
208
+ ...current,
209
+ ...(Object.keys(privateEnv).length ? { env: { ...currentEnv, ...privateEnv } } : {}),
210
+ ...(Object.keys(privateHeaders).length ? { headers: { ...currentHeaders, ...privateHeaders } } : {}),
211
+ };
212
+ }
213
+
214
+ return merged;
215
+ }
216
+
217
+ function codexMcpOverridesFromCursorConfig(workspaceRoot, userId) {
218
+ const safe = sanitizeAgentflowUserId(userId);
219
+ const privateServers = safe ? readUserMcpPrivateServers(safe) : {};
220
+ const servers = mergedCursorMcpServersForCodex(workspaceRoot, userId);
221
+ const used = new Set();
222
+ const codexConfigArgs = [];
223
+ const env = {};
224
+
225
+ for (const [rawName, rawServer] of Object.entries(servers)) {
226
+ if (!rawServer || typeof rawServer !== "object" || Array.isArray(rawServer)) continue;
227
+ if (rawServer.disabled === true) continue;
228
+ const name = codexMcpName(rawName, used);
229
+ const prefix = `mcp_servers.${name}`;
230
+ const envObj = rawServer.env && typeof rawServer.env === "object" && !Array.isArray(rawServer.env) ? rawServer.env : {};
231
+ const headers = rawServer.headers && typeof rawServer.headers === "object" && !Array.isArray(rawServer.headers) ? rawServer.headers : {};
232
+ const privateServer = privateServers[rawName];
233
+ const privateEnv = privateServer?.env && typeof privateServer.env === "object" && !Array.isArray(privateServer.env) ? privateServer.env : {};
234
+ const url = String(rawServer.url || "").trim();
235
+ const command = String(rawServer.command || "").trim();
236
+
237
+ if (url) {
238
+ codexConfigArgs.push(`${prefix}.url=${tomlString(url)}`);
239
+ if (rawServer.bearer_token_env_var && String(rawServer.bearer_token_env_var).trim()) {
240
+ codexConfigArgs.push(`${prefix}.bearer_token_env_var=${tomlString(rawServer.bearer_token_env_var)}`);
241
+ }
242
+ if (rawServer.oauth_client_id && String(rawServer.oauth_client_id).trim()) {
243
+ codexConfigArgs.push(`${prefix}.oauth_client_id=${tomlString(rawServer.oauth_client_id)}`);
244
+ }
245
+ if (rawServer.oauth_resource && String(rawServer.oauth_resource).trim()) {
246
+ codexConfigArgs.push(`${prefix}.oauth_resource=${tomlString(rawServer.oauth_resource)}`);
247
+ }
248
+ const bearer = bearerFromHeaders(headers);
249
+ if (bearer) {
250
+ const envKey = `AGENTFLOW_CODEX_MCP_${name.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}_BEARER`;
251
+ env[envKey] = bearer;
252
+ codexConfigArgs.push(`${prefix}.bearer_token_env_var=${tomlString(envKey)}`);
253
+ }
254
+ continue;
255
+ }
256
+
257
+ if (!command) continue;
258
+ codexConfigArgs.push(`${prefix}.command=${tomlString(command)}`);
259
+ if (Array.isArray(rawServer.args) && rawServer.args.length) {
260
+ codexConfigArgs.push(`${prefix}.args=${tomlArray(rawServer.args)}`);
261
+ }
262
+ if (rawServer.cwd && String(rawServer.cwd).trim()) {
263
+ codexConfigArgs.push(`${prefix}.cwd=${tomlString(rawServer.cwd)}`);
264
+ }
265
+ const publicEnvForConfig = {};
266
+ for (const [key, value] of Object.entries(envObj)) {
267
+ const envKey = String(key || "").trim();
268
+ if (!envKey) continue;
269
+ if (Object.prototype.hasOwnProperty.call(privateEnv, envKey)) {
270
+ env[envKey] = String(privateEnv[envKey] ?? "");
271
+ } else {
272
+ publicEnvForConfig[envKey] = value;
273
+ }
274
+ }
275
+ if (Object.keys(publicEnvForConfig).length) {
276
+ codexConfigArgs.push(`${prefix}.env=${tomlInlineTable(publicEnvForConfig)}`);
277
+ }
278
+ }
279
+
280
+ return { codexConfigArgs, env };
281
+ }
282
+
155
283
  function agentflowUserEnv(userId) {
156
284
  const safe = sanitizeAgentflowUserId(userId);
157
285
  pruneCursorMcpPrivateEnvPlaceholders();
158
286
  return { ...readMergedEnvObject(safe), ...(safe ? readUserMcpPrivateEnvObject(safe) : {}), AGENTFLOW_USER_ID: safe };
159
287
  }
160
288
 
289
+ function runCodexAgentWithPrivateMcp(cliWorkspace, prompt, options, userId) {
290
+ const { codexConfigArgs, env } = codexMcpOverridesFromCursorConfig(cliWorkspace, userId);
291
+ return runCodexAgentWithPrompt(cliWorkspace, prompt, {
292
+ ...options,
293
+ codexConfigArgs: [...(Array.isArray(options?.codexConfigArgs) ? options.codexConfigArgs : []), ...codexConfigArgs],
294
+ env: { ...(options?.env || {}), ...env },
295
+ });
296
+ }
297
+
161
298
  function runCursorAgentWithPrivateMcp(cliWorkspace, prompt, options, userId) {
162
299
  const restore = materializeWorkspaceCursorMcpPrivateConfig(cliWorkspace, userId);
163
300
  let handle;
@@ -291,6 +428,13 @@ export function startComposerAgent(opts) {
291
428
  });
292
429
  }
293
430
 
431
+ if (cli === "codex") {
432
+ return runCodexAgentWithPrivateMcp(cliWs, prompt, {
433
+ ...common,
434
+ model: model || undefined,
435
+ }, opts.agentflowUserId);
436
+ }
437
+
294
438
  if (cli === "claude-code") {
295
439
  return runClaudeCodeAgentWithPrompt(cliWs, prompt, {
296
440
  ...common,
@@ -581,6 +725,15 @@ export async function runComposerPostFlowValidationAndRepair(opts) {
581
725
  });
582
726
  setChild(handle.child);
583
727
  await handle.finished;
728
+ } else if (cli === "codex") {
729
+ const handle = runCodexAgentWithPrivateMcp(cliWs, agentPrompt, {
730
+ onStreamEvent: stepEmit,
731
+ model: model || undefined,
732
+ force: Boolean(opts.force),
733
+ env,
734
+ }, opts.agentflowUserId || opts.flowContext?.userId);
735
+ setChild(handle.child);
736
+ await handle.finished;
584
737
  } else if (cli === "claude-code") {
585
738
  const handle = runClaudeCodeAgentWithPrompt(cliWs, agentPrompt, {
586
739
  onStreamEvent: stepEmit,
@@ -854,6 +1007,15 @@ export function startComposerMultiStep(opts) {
854
1007
  });
855
1008
  currentChild = handle.child;
856
1009
  await handle.finished;
1010
+ } else if (cli === "codex") {
1011
+ const handle = runCodexAgentWithPrivateMcp(cliWs, agentPrompt, {
1012
+ onStreamEvent: stepEmit,
1013
+ model: model || undefined,
1014
+ force: Boolean(opts.force),
1015
+ env,
1016
+ }, opts.agentflowUserId || opts.flowContext?.userId);
1017
+ currentChild = handle.child;
1018
+ await handle.finished;
857
1019
  } else if (cli === "claude-code") {
858
1020
  const handle = runClaudeCodeAgentWithPrompt(cliWs, agentPrompt, {
859
1021
  onStreamEvent: stepEmit,
@@ -61,14 +61,16 @@ function classifyModel(modelId) {
61
61
  function loadModelLists() {
62
62
  try {
63
63
  const p = getModelListsAbs();
64
- if (!fs.existsSync(p)) return { cursor: [], opencode: [] };
64
+ if (!fs.existsSync(p)) return { cursor: [], opencode: [], claudeCode: [], codex: [] };
65
65
  const data = JSON.parse(fs.readFileSync(p, "utf-8"));
66
66
  return {
67
67
  cursor: Array.isArray(data.cursor) ? data.cursor.map(String) : [],
68
68
  opencode: Array.isArray(data.opencode) ? data.opencode.map(String) : [],
69
+ claudeCode: Array.isArray(data.claudeCode) ? data.claudeCode.map(String) : [],
70
+ codex: Array.isArray(data.codex) ? data.codex.map(String) : [],
69
71
  };
70
72
  } catch {
71
- return { cursor: [], opencode: [] };
73
+ return { cursor: [], opencode: [], claudeCode: [], codex: [] };
72
74
  }
73
75
  }
74
76
 
@@ -87,6 +89,18 @@ function buildModelTiers(modelList) {
87
89
  return tiers;
88
90
  }
89
91
 
92
+ function modelEntryId(entry) {
93
+ const idx = String(entry || "").indexOf(" - ");
94
+ return idx >= 0 ? entry.slice(0, idx).trim() : String(entry || "").trim();
95
+ }
96
+
97
+ function prefixedModels(models, prefix) {
98
+ return (Array.isArray(models) ? models : [])
99
+ .map((m) => modelEntryId(m))
100
+ .filter(Boolean)
101
+ .map((m) => `${prefix}:${m}`);
102
+ }
103
+
90
104
  // ─── 公开接口 ──────────────────────────────────────────────────────────────
91
105
 
92
106
  /**
@@ -105,7 +119,12 @@ export function routeModel(complexity, opts = {}) {
105
119
  }
106
120
 
107
121
  const lists = loadModelLists();
108
- const allModels = [...lists.cursor];
122
+ const allModels = [
123
+ ...lists.cursor,
124
+ ...prefixedModels(lists.codex, "codex"),
125
+ ...prefixedModels(lists.claudeCode, "claude-code"),
126
+ ...prefixedModels(lists.opencode, "opencode"),
127
+ ];
109
128
  if (allModels.length === 0) {
110
129
  return { model: null, tier: complexity === "complex" ? "capable" : complexity === "simple" ? "fast" : "balanced", source: "no-models-available" };
111
130
  }
@@ -146,11 +165,17 @@ export function getModelTierInfo() {
146
165
  const lists = loadModelLists();
147
166
  const cursorTiers = buildModelTiers(lists.cursor);
148
167
  const opencodeTiers = buildModelTiers(lists.opencode);
168
+ const claudeCodeTiers = buildModelTiers(lists.claudeCode);
169
+ const codexTiers = buildModelTiers(lists.codex);
149
170
  return {
150
171
  cursor: cursorTiers,
151
172
  opencode: opencodeTiers,
173
+ claudeCode: claudeCodeTiers,
174
+ codex: codexTiers,
152
175
  totalCursor: lists.cursor.length,
153
176
  totalOpencode: lists.opencode.length,
177
+ totalClaudeCode: lists.claudeCode.length,
178
+ totalCodex: lists.codex.length,
154
179
  };
155
180
  }
156
181
 
@@ -12,7 +12,7 @@ import { buildPlannerSkillContext } from "./composer-skill-router.mjs";
12
12
  import { buildNodeSchemaPromptSection } from "./composer-node-schema.mjs";
13
13
  import { formatInstancePlannerHint } from "./composer-flow-instances.mjs";
14
14
  import { resolveCliAndModel } from "./model-config.mjs";
15
- import { runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
15
+ import { runCodexAgentWithPrompt, runClaudeCodeAgentWithPrompt, runCursorAgentWithPrompt, runOpenCodeAgentWithPrompt } from "./agent-runners.mjs";
16
16
  import { t } from "./i18n.mjs";
17
17
 
18
18
  const PLANNER_MAX_TOKENS = 2048;
@@ -428,6 +428,8 @@ async function classifyViaCli(userPrompt, cliWorkspace) {
428
428
 
429
429
  const runner = cli === "opencode"
430
430
  ? runOpenCodeAgentWithPrompt(cliWorkspace, prompt, { onStreamEvent, model: model || undefined })
431
+ : cli === "codex"
432
+ ? runCodexAgentWithPrompt(cliWorkspace, prompt, { onStreamEvent, model: model || undefined })
431
433
  : cli === "claude-code"
432
434
  ? runClaudeCodeAgentWithPrompt(cliWorkspace, prompt, { onStreamEvent, model: model || undefined })
433
435
  : runCursorAgentWithPrompt(cliWorkspace, prompt, { onStreamEvent, model: model || undefined, force: true });
package/bin/lib/help.mjs CHANGED
@@ -8,7 +8,7 @@ export function printHelp() {
8
8
  // 根据语言输出不同的帮助文本
9
9
  if (isZh) {
10
10
  log.info(`
11
- AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code CLI 流式输出驱动 apply/replay。
11
+ AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code / Codex CLI 流式输出驱动 apply/replay。
12
12
 
13
13
  用法:
14
14
  agentflow login [--provider github|google] 登录 AgentFlow Hub(默认 GitHub)
@@ -18,6 +18,7 @@ AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code CLI 流式输出驱动
18
18
  agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] 从 Hub 下载流程(默认 --user 安装到 ~/agentflow/pipelines/<id>;--workspace 安装到当前工程 .workspace/agentflow/pipelines/<id>;--raw 仅保留压缩包)
19
19
  agentflow list 列出所有流水线
20
20
  agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] 本地 HTTP:流水线列表 + React Flow 节点流程图编辑保存(默认 127.0.0.1:8765;可用 AGENTFLOW_UI_HOST)
21
+ agentflow mcp 启动 AgentFlow MCP stdio server,供 Cursor/Codex 运行流程并读取 display 输出
21
22
  agentflow scheduler start [--poll-ms <ms>] 启动定时执行调度器(读取各流水线 schedule.json)
22
23
  agentflow scheduler status [--json] 查看定时执行配置与状态
23
24
  agentflow scheduler cancel <FlowName> <uuid> 取消某次等待中的 watch/run
@@ -31,19 +32,19 @@ AgentFlow CLI — 使用 Cursor / OpenCode / Claude Code CLI 流式输出驱动
31
32
  agentflow run-status <flowName> <uuid> 输出该次运行的节点状态 JSON(供 UI 展示 success/pending 等角标)
32
33
  agentflow extract-thinking <flowName> <uuid> 从该次 run 的 logs/log.txt 提取 thinking,写入 logs/thinking_by_session_and_nodes.md
33
34
  agentflow extract-thinking -list 列出所有存在 logs/log.txt 的 run(可接 --json)
34
- agentflow update-model-lists 拉取 Cursor / OpenCode 模型列表并写入 ~/agentflow/model-lists.json;--json 时输出 { cursor, opencode }
35
+ agentflow update-model-lists 拉取 Cursor / OpenCode / Claude Code / Codex 模型列表并写入 ~/agentflow/model-lists.json
35
36
  agentflow write-flow <flowId> --json --flow-source <user|workspace> 从 stdin 读入 YAML 写入用户目录或工作区(builtin 已弃用,将视为 workspace)
36
37
  agentflow --help
37
38
 
38
39
  选项:
39
40
  --workspace-root <path> 工作区根目录(默认:当前目录)
40
- --dry-run (仅 apply)打印就绪节点后退出,不执行 Cursor agent
41
- --model <name> 后端模型。默认走 Cursor;前缀 opencode:<model>、claude-code:<model>、api:<provider>/<model> 可切换后端。覆盖 CURSOR_AGENT_MODEL。
41
+ --dry-run (仅 apply)打印就绪节点后退出,不执行 agent 后端
42
+ --model <name> 后端模型。默认走 Cursor;前缀 opencode:<model>、claude-code:<model>、codex:<model>、api:<provider>/<model> 可切换后端。覆盖 CURSOR_AGENT_MODEL。
42
43
  --input <name>=<value> (仅 apply)覆盖 flow 中 provide 节点的值。value 前缀 file: 表示文件路径。可多次使用。
43
44
  --debug 显示调试日志(灰色,低优先级)
44
45
  --force 传递 --force/--trust 给 Cursor;设置 OPENCODE_PERMISSION 允许 OpenCode 的 external_directory(默认开启)。使用 --no-force 禁用。
45
- --parallel 并行运行同轮就绪节点(默认关闭)。多个 Cursor CLI 进程可能竞争 ~/.cursor/cli-config.json。
46
- --machine-readable 向 stdout 每行输出一个 JSON 事件(apply-start/node-start/node-done/node-failed/apply-done/apply-paused)。供 UI 运行按钮使用:解析 stdout 显示当前节点;Cursor agent 输出转到 stderr。
46
+ --parallel 并行运行同轮就绪节点(默认关闭)。多个 CLI 进程可能竞争后端自身的本地配置。
47
+ --machine-readable 向 stdout 每行输出一个 JSON 事件(apply-start/node-start/node-done/node-failed/apply-done/apply-paused)。供 UI 运行按钮使用:解析 stdout 显示当前节点;agent 后端输出转到 stderr。
47
48
  --lang <code> 设置语言:en、zh(默认:zh,或从 LANG 环境变量检测)
48
49
 
49
50
  路径说明:
@@ -71,13 +72,13 @@ Apply:构建运行目录,解析流程,循环运行就绪节点。
71
72
  Resume:将 pending 和 failed 节点标记为成功(例如 UserCheck 确认后或重试失败后),然后继续 apply。
72
73
  Replay:运行单个节点(pre-process → execute → post-process)。
73
74
 
74
- 需要:Node >=18,以下任一 CLI 在 PATH 中用于节点执行:Cursor CLI('agent',默认)、OpenCode CLI('opencode',env 覆盖 OPENCODE_CMD)、Claude Code CLI('claude',env 覆盖 CLAUDE_CODE_CMD,需先 'claude /login')。
75
+ 需要:Node >=18,以下任一 CLI 在 PATH 中用于节点执行:Cursor CLI('agent',默认)、OpenCode CLI('opencode',env 覆盖 OPENCODE_CMD)、Claude Code CLI('claude',env 覆盖 CLAUDE_CODE_CMD,需先 'claude /login')、Codex CLI('codex',env 覆盖 CODEX_CMD,需先 'codex login')。
75
76
  Apply/replay 脚本已打包在 agentflow 包中(bin/pipeline/)。
76
77
  `);
77
78
  } else {
78
79
  // 英文版本
79
80
  log.info(`
80
- AgentFlow CLI — drive apply/replay with Cursor / OpenCode / Claude Code CLI streaming.
81
+ AgentFlow CLI — drive apply/replay with Cursor / OpenCode / Claude Code / Codex CLI streaming.
81
82
 
82
83
  Usage:
83
84
  agentflow login [--provider github|google] Login to AgentFlow Hub (default: GitHub)
@@ -87,6 +88,7 @@ Usage:
87
88
  agentflow download <slug|title> [--user|--workspace] [--as <id>] [--raw [--output <dir>]] Download flow (default --user → ~/agentflow/pipelines/<id>; --workspace → current project's .workspace/agentflow/pipelines/<id>; --raw keeps the archive)
88
89
  agentflow list List all pipelines
89
90
  agentflow ui [--host <addr>] [--port <n>] [--scheduler] [--no-open] [--hide-community-links] Local HTTP: pipeline list + React Flow node diagram editor (default 127.0.0.1:8765; AGENTFLOW_UI_HOST supported)
91
+ agentflow mcp Start the AgentFlow MCP stdio server for Cursor/Codex to run flows and read display outputs
90
92
  agentflow scheduler start [--poll-ms <ms>] Start the scheduled-run scheduler (reads each pipeline schedule.json)
91
93
  agentflow scheduler status [--json] Show scheduled-run configuration and state
92
94
  agentflow scheduler cancel <FlowName> <uuid> Cancel a waiting watch/run
@@ -100,19 +102,19 @@ Usage:
100
102
  agentflow run-status <flowName> <uuid> Output node status JSON for this run (for UI success/pending badges)
101
103
  agentflow extract-thinking <flowName> <uuid> Extract thinking from run logs/log.txt, write to logs/thinking_by_session_and_nodes.md
102
104
  agentflow extract-thinking -list List all runs with logs/log.txt (use --json)
103
- agentflow update-model-lists Fetch Cursor / OpenCode model lists to ~/agentflow/model-lists.json; --json outputs { cursor, opencode }
105
+ agentflow update-model-lists Fetch Cursor / OpenCode / Claude Code / Codex model lists to ~/agentflow/model-lists.json
104
106
  agentflow write-flow <flowId> --json --flow-source <user|workspace> Read YAML from stdin and write to user dir or workspace (builtin deprecated, treated as workspace)
105
107
  agentflow --help
106
108
 
107
109
  Options:
108
110
  --workspace-root <path> Workspace root (default: cwd)
109
- --dry-run (apply only) Print ready nodes and exit without running Cursor agent
110
- --model <name> Backend model. Default routes to Cursor; prefixes opencode:<model>, claude-code:<model>, api:<provider>/<model> switch backend. Overrides CURSOR_AGENT_MODEL.
111
+ --dry-run (apply only) Print ready nodes and exit without running an agent backend
112
+ --model <name> Backend model. Default routes to Cursor; prefixes opencode:<model>, claude-code:<model>, codex:<model>, api:<provider>/<model> switch backend. Overrides CURSOR_AGENT_MODEL.
111
113
  --input <name>=<value> (apply only) Override provide node values in flow. Prefix value with file: for file paths. Can be used multiple times.
112
114
  --debug Show debug logs (gray, low priority)
113
115
  --force Pass --force/--trust to Cursor; set OPENCODE_PERMISSION to allow external_directory for OpenCode (default: on). Use --no-force to disable.
114
- --parallel Run same-round ready nodes in parallel (default: off). Multiple Cursor CLI processes may race on ~/.cursor/cli-config.json.
115
- --machine-readable Emit one JSON event per line to stdout (apply-start/node-start/node-done/node-failed/apply-done/apply-paused). For UI run button: parse stdout to show current node; Cursor agent output goes to stderr.
116
+ --parallel Run same-round ready nodes in parallel (default: off). Multiple CLI processes may race on backend-local config.
117
+ --machine-readable Emit one JSON event per line to stdout (apply-start/node-start/node-done/node-failed/apply-done/apply-paused). For UI run button: parse stdout to show current node; agent backend output goes to stderr.
116
118
  --lang <code> Set language: en, zh (default: en, or auto-detect from LANG env)
117
119
 
118
120
  Path notes:
@@ -140,7 +142,7 @@ Apply: builds run dir, parses flow, runs ready nodes in a loop.
140
142
  Resume: marks pending and failed node(s) as success (e.g. after UserCheck confirm or retry failed), then continues apply.
141
143
  Replay: runs a single node (pre-process → execute → post-process).
142
144
 
143
- Requires: Node >=18, any of Cursor CLI ('agent', default), OpenCode CLI ('opencode', env OPENCODE_CMD), or Claude Code CLI ('claude', env CLAUDE_CODE_CMD, run 'claude /login' first) in PATH for node execution.
145
+ Requires: Node >=18, any of Cursor CLI ('agent', default), OpenCode CLI ('opencode', env OPENCODE_CMD), Claude Code CLI ('claude', env CLAUDE_CODE_CMD, run 'claude /login' first), or Codex CLI ('codex', env CODEX_CMD, run 'codex login' first) in PATH for node execution.
144
146
  Apply/replay scripts are bundled in the agentflow package (bin/pipeline/).
145
147
  `);
146
148
  }
@@ -35,7 +35,7 @@
35
35
  "default": "Default"
36
36
  },
37
37
  "cli": {
38
- "description": "AgentFlow CLI — drive apply/replay with Cursor or OpenCode CLI streaming",
38
+ "description": "AgentFlow CLI — drive apply/replay with Cursor, OpenCode, Claude Code, or Codex CLI streaming",
39
39
  "usage": "Usage",
40
40
  "commands": "Commands",
41
41
  "options": "Options",
@@ -341,6 +341,10 @@
341
341
  "displayName": "HTML Display",
342
342
  "description": "Render HTML in a sandboxed iframe on the Workspace canvas and pass the source downstream"
343
343
  },
344
+ "display_react_app": {
345
+ "displayName": "React App",
346
+ "description": "Preview a React project in a sandboxed iframe on the Workspace canvas and pass the project JSON downstream"
347
+ },
344
348
  "display_image": {
345
349
  "displayName": "Image Display",
346
350
  "description": "Preview an image URL, data URL, or image path on the Workspace canvas and pass the source downstream"
@@ -35,7 +35,7 @@
35
35
  "default": "默认"
36
36
  },
37
37
  "cli": {
38
- "description": "AgentFlow CLI — 使用 Cursor 或 OpenCode CLI 流式输出驱动 apply/replay",
38
+ "description": "AgentFlow CLI — 使用 Cursor、OpenCode、Claude Code Codex CLI 流式输出驱动 apply/replay",
39
39
  "usage": "用法",
40
40
  "commands": "命令",
41
41
  "options": "选项",
@@ -341,6 +341,10 @@
341
341
  "displayName": "HTML 展示",
342
342
  "description": "在 Workspace 画布中用 sandbox iframe 渲染 HTML,并将源码继续传给下游"
343
343
  },
344
+ "display_react_app": {
345
+ "displayName": "React 工程",
346
+ "description": "在 Workspace 画布中用 sandbox iframe 预览 React 工程,并将工程 JSON 继续传给下游"
347
+ },
344
348
  "display_image": {
345
349
  "displayName": "图片展示",
346
350
  "description": "在 Workspace 画布中预览图片 URL、data URL 或图片路径,并将来源继续传给下游"
package/bin/lib/main.mjs CHANGED
@@ -32,6 +32,7 @@ import { hubPublish } from "./hub-publish.mjs";
32
32
  import { hubListRemote, hubDownload } from "./hub-remote.mjs";
33
33
  import { cancelScheduledRun, listScheduleStatuses, startScheduler } from "./scheduler.mjs";
34
34
  import { installFlowDependency, listMarketplacePackages, publishNodePackage } from "./marketplace.mjs";
35
+ import { startMcpServer } from "./mcp-server.mjs";
35
36
 
36
37
  async function readStdin() {
37
38
  const chunks = [];
@@ -116,6 +117,10 @@ export async function main() {
116
117
  if (jsonMode) process.stdout.write(JSON.stringify(result) + "\n");
117
118
  process.exit(0);
118
119
  }
120
+ if (sub === "mcp") {
121
+ await startMcpServer();
122
+ return;
123
+ }
119
124
  const jsonOnlySubs = [
120
125
  "list-flows",
121
126
  "list-nodes",