@josephyan/qingflow-cli 0.2.0-beta.58 → 0.2.0-beta.60
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.
- package/README.md +3 -2
- package/docs/local-agent-install.md +9 -0
- package/npm/bin/qingflow.mjs +1 -1
- package/npm/lib/runtime.mjs +156 -21
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/qingflow_mcp/builder_facade/service.py +670 -191
- package/src/qingflow_mcp/cli/commands/app.py +16 -16
- package/src/qingflow_mcp/cli/commands/auth.py +19 -16
- package/src/qingflow_mcp/cli/commands/builder.py +124 -162
- package/src/qingflow_mcp/cli/commands/common.py +21 -95
- package/src/qingflow_mcp/cli/commands/imports.py +42 -34
- package/src/qingflow_mcp/cli/commands/record.py +131 -133
- package/src/qingflow_mcp/cli/commands/task.py +43 -44
- package/src/qingflow_mcp/cli/commands/workspace.py +10 -10
- package/src/qingflow_mcp/cli/context.py +35 -32
- package/src/qingflow_mcp/cli/formatters.py +124 -121
- package/src/qingflow_mcp/cli/main.py +52 -17
- package/src/qingflow_mcp/server_app_builder.py +122 -190
- package/src/qingflow_mcp/server_app_user.py +63 -662
- package/src/qingflow_mcp/solution/executor.py +63 -4
- package/src/qingflow_mcp/tools/solution_tools.py +115 -3
- package/src/qingflow_mcp/ops/__init__.py +0 -3
- package/src/qingflow_mcp/ops/apps.py +0 -64
- package/src/qingflow_mcp/ops/auth.py +0 -121
- package/src/qingflow_mcp/ops/base.py +0 -290
- package/src/qingflow_mcp/ops/builder.py +0 -357
- package/src/qingflow_mcp/ops/context.py +0 -120
- package/src/qingflow_mcp/ops/directory.py +0 -171
- package/src/qingflow_mcp/ops/feedback.py +0 -49
- package/src/qingflow_mcp/ops/files.py +0 -78
- package/src/qingflow_mcp/ops/imports.py +0 -140
- package/src/qingflow_mcp/ops/records.py +0 -415
- package/src/qingflow_mcp/ops/tasks.py +0 -171
- package/src/qingflow_mcp/ops/workspace.py +0 -76
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install:
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npm install @josephyan/qingflow-cli@0.2.0-beta.
|
|
6
|
+
npm install @josephyan/qingflow-cli@0.2.0-beta.60
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
Run:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.
|
|
12
|
+
npx -y -p @josephyan/qingflow-cli@0.2.0-beta.60 qingflow
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Environment:
|
|
@@ -28,3 +28,4 @@ Note:
|
|
|
28
28
|
|
|
29
29
|
- The skill files are included in the npm package.
|
|
30
30
|
- On install, the package copies them to `$CODEX_HOME/skills` (or `~/.codex/skills` if `CODEX_HOME` is unset).
|
|
31
|
+
- If a stdio MCP client reports `Transport closed`, delete `.npm-python`, reinstall the package, and make sure CLI/user/builder packages are on the same version. The stdio entrypoints refuse runtime bootstrap so install logs never corrupt MCP stdout.
|
|
@@ -233,3 +233,12 @@ rm -rf .npm-python
|
|
|
233
233
|
```bash
|
|
234
234
|
npm install
|
|
235
235
|
```
|
|
236
|
+
|
|
237
|
+
如果 MCP 客户端一调用工具就报 `Transport closed`,优先检查这几件事:
|
|
238
|
+
|
|
239
|
+
1. 不要混用不同版本的 `@josephyan/qingflow-cli`、`@josephyan/qingflow-app-user-mcp`、`@josephyan/qingflow-app-builder-mcp`
|
|
240
|
+
2. 删除安装目录下的 `.npm-python`
|
|
241
|
+
3. 重新执行 `npm install` 或重新安装对应 tgz/npm 包
|
|
242
|
+
4. 再启动 MCP 客户端
|
|
243
|
+
|
|
244
|
+
现在 stdio MCP 入口会拒绝在启动瞬间“边启动边重建 Python 运行时”,因为安装日志一旦写进 stdout,就会破坏 MCP 握手并表现成 `Transport closed`。如果运行时缺失或版本不一致,入口会直接报错并提示重装,而不是静默自修复。
|
package/npm/bin/qingflow.mjs
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
import { spawnServer, getPackageRoot } from "../lib/runtime.mjs";
|
|
3
3
|
|
|
4
4
|
const packageRoot = getPackageRoot(import.meta.url);
|
|
5
|
-
spawnServer(packageRoot, process.argv.slice(2), "qingflow");
|
|
5
|
+
spawnServer(packageRoot, process.argv.slice(2), "qingflow", { allowRuntimeBootstrap: true });
|
package/npm/lib/runtime.mjs
CHANGED
|
@@ -7,14 +7,16 @@ const WINDOWS = process.platform === "win32";
|
|
|
7
7
|
|
|
8
8
|
function runChecked(command, args, options = {}) {
|
|
9
9
|
const result = spawnSync(command, args, {
|
|
10
|
-
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11
12
|
...options,
|
|
12
13
|
});
|
|
13
14
|
if (result.error) {
|
|
14
15
|
throw result.error;
|
|
15
16
|
}
|
|
16
17
|
if (result.status !== 0) {
|
|
17
|
-
|
|
18
|
+
const details = [result.stderr, result.stdout].filter((value) => typeof value === "string" && value.trim()).join("\n");
|
|
19
|
+
throw new Error(details ? `Command failed: ${command} ${args.join(" ")}\n${details}` : `Command failed: ${command} ${args.join(" ")}`);
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -92,6 +94,95 @@ function readPackageVersion(packageRoot) {
|
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
function readBootstrapStamp(stampPath) {
|
|
98
|
+
if (!fs.existsSync(stampPath)) {
|
|
99
|
+
return { exists: false, version: null };
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const payload = JSON.parse(fs.readFileSync(stampPath, "utf8"));
|
|
103
|
+
const version = typeof payload.package_version === "string" && payload.package_version.trim() ? payload.package_version.trim() : null;
|
|
104
|
+
return { exists: true, version };
|
|
105
|
+
} catch {
|
|
106
|
+
return { exists: true, version: null };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function inspectPythonEnv(packageRoot, commandName = "qingflow-mcp") {
|
|
111
|
+
const venvDir = getVenvDir(packageRoot);
|
|
112
|
+
const serverCommand = getVenvServerCommand(packageRoot, commandName);
|
|
113
|
+
const stampPath = path.join(venvDir, ".bootstrap.json");
|
|
114
|
+
const packageVersion = readPackageVersion(packageRoot);
|
|
115
|
+
const stamp = readBootstrapStamp(stampPath);
|
|
116
|
+
const problems = [];
|
|
117
|
+
|
|
118
|
+
if (!packageVersion) {
|
|
119
|
+
problems.push("package-version-missing");
|
|
120
|
+
}
|
|
121
|
+
if (!fs.existsSync(serverCommand)) {
|
|
122
|
+
problems.push("server-command-missing");
|
|
123
|
+
}
|
|
124
|
+
if (!stamp.exists) {
|
|
125
|
+
problems.push("bootstrap-stamp-missing");
|
|
126
|
+
} else if (!stamp.version) {
|
|
127
|
+
problems.push("bootstrap-stamp-invalid");
|
|
128
|
+
} else if (packageVersion && stamp.version !== packageVersion) {
|
|
129
|
+
problems.push("bootstrap-version-mismatch");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
ready: problems.length === 0,
|
|
134
|
+
packageVersion,
|
|
135
|
+
stampVersion: stamp.version,
|
|
136
|
+
stampExists: stamp.exists,
|
|
137
|
+
stampPath,
|
|
138
|
+
serverCommand,
|
|
139
|
+
venvDir,
|
|
140
|
+
problems,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap = false } = {}) {
|
|
145
|
+
const problemLines = [];
|
|
146
|
+
for (const problem of runtime.problems) {
|
|
147
|
+
switch (problem) {
|
|
148
|
+
case "package-version-missing":
|
|
149
|
+
problemLines.push("- package.json is missing a valid version field");
|
|
150
|
+
break;
|
|
151
|
+
case "server-command-missing":
|
|
152
|
+
problemLines.push(`- missing Python entrypoint: ${runtime.serverCommand}`);
|
|
153
|
+
break;
|
|
154
|
+
case "bootstrap-stamp-missing":
|
|
155
|
+
problemLines.push(`- missing bootstrap stamp: ${runtime.stampPath}`);
|
|
156
|
+
break;
|
|
157
|
+
case "bootstrap-stamp-invalid":
|
|
158
|
+
problemLines.push(`- bootstrap stamp is unreadable or invalid: ${runtime.stampPath}`);
|
|
159
|
+
break;
|
|
160
|
+
case "bootstrap-version-mismatch":
|
|
161
|
+
problemLines.push(
|
|
162
|
+
`- bootstrap version mismatch: package=${runtime.packageVersion ?? "unknown"}, runtime=${runtime.stampVersion ?? "unknown"}`
|
|
163
|
+
);
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
problemLines.push(`- ${problem}`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const action = allowRuntimeBootstrap
|
|
172
|
+
? "Delete .npm-python and retry, or rerun npm install to rebuild the embedded Python runtime."
|
|
173
|
+
: "Delete .npm-python and rerun npm install, or reinstall the npm package before starting the MCP server.";
|
|
174
|
+
|
|
175
|
+
const bootstrapNote = allowRuntimeBootstrap
|
|
176
|
+
? ""
|
|
177
|
+
: "\nRuntime bootstrap is disabled for stdio MCP entrypoints so install logs can never corrupt the MCP stdout transport.";
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
`[qingflow-mcp] Python runtime for ${commandName} is not ready.`,
|
|
181
|
+
...problemLines,
|
|
182
|
+
action + bootstrapNote,
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
185
|
+
|
|
95
186
|
function getVenvPip(packageRoot) {
|
|
96
187
|
return WINDOWS
|
|
97
188
|
? path.join(getVenvDir(packageRoot), "Scripts", "pip.exe")
|
|
@@ -129,20 +220,9 @@ export function findPython() {
|
|
|
129
220
|
|
|
130
221
|
export function ensurePythonEnv(packageRoot, { force = false, commandName = "qingflow-mcp" } = {}) {
|
|
131
222
|
const python = findPython();
|
|
132
|
-
const
|
|
223
|
+
const runtime = inspectPythonEnv(packageRoot, commandName);
|
|
133
224
|
const venvPython = getVenvPython(packageRoot);
|
|
134
|
-
const serverCommand
|
|
135
|
-
const stampPath = path.join(venvDir, ".bootstrap.json");
|
|
136
|
-
const packageVersion = readPackageVersion(packageRoot);
|
|
137
|
-
let stampVersion = null;
|
|
138
|
-
if (fs.existsSync(stampPath)) {
|
|
139
|
-
try {
|
|
140
|
-
const payload = JSON.parse(fs.readFileSync(stampPath, "utf8"));
|
|
141
|
-
stampVersion = typeof payload.package_version === "string" && payload.package_version.trim() ? payload.package_version.trim() : null;
|
|
142
|
-
} catch {
|
|
143
|
-
stampVersion = null;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
225
|
+
const { packageVersion, serverCommand, stampPath, venvDir, stampVersion } = runtime;
|
|
146
226
|
|
|
147
227
|
if (!force && fs.existsSync(serverCommand) && fs.existsSync(stampPath) && stampVersion && stampVersion === packageVersion) {
|
|
148
228
|
return serverCommand;
|
|
@@ -179,17 +259,72 @@ export function ensurePythonEnv(packageRoot, { force = false, commandName = "qin
|
|
|
179
259
|
return serverCommand;
|
|
180
260
|
}
|
|
181
261
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
262
|
+
function proxyStreams(child) {
|
|
263
|
+
if (process.stdin.readable && child.stdin) {
|
|
264
|
+
process.stdin.pipe(child.stdin);
|
|
265
|
+
child.stdin.on("error", (error) => {
|
|
266
|
+
if (error.code !== "EPIPE") {
|
|
267
|
+
console.error(`[qingflow-mcp] Failed to forward stdin: ${error.message}`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
} else if (child.stdin) {
|
|
271
|
+
child.stdin.end();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (child.stdout) {
|
|
275
|
+
child.stdout.pipe(process.stdout);
|
|
276
|
+
}
|
|
277
|
+
if (child.stderr) {
|
|
278
|
+
child.stderr.pipe(process.stderr);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function forwardSignal(child, signal) {
|
|
283
|
+
process.on(signal, () => {
|
|
284
|
+
if (!child.killed) {
|
|
285
|
+
child.kill(signal);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function spawnServer(packageRoot, args, commandName = "qingflow-mcp", { allowRuntimeBootstrap = false } = {}) {
|
|
291
|
+
let runtime = inspectPythonEnv(packageRoot, commandName);
|
|
292
|
+
let serverCommand = runtime.serverCommand;
|
|
293
|
+
|
|
294
|
+
if (!runtime.ready) {
|
|
295
|
+
if (!allowRuntimeBootstrap) {
|
|
296
|
+
console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
serverCommand = ensurePythonEnv(packageRoot, { commandName });
|
|
303
|
+
runtime = inspectPythonEnv(packageRoot, commandName);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(`[qingflow-mcp] Failed to prepare Python runtime for ${commandName}: ${error.message}`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!runtime.ready) {
|
|
311
|
+
console.error(formatRuntimeProblem(runtime, commandName, { allowRuntimeBootstrap }));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
186
316
|
|
|
187
317
|
const child = spawn(serverCommand, args, {
|
|
188
|
-
stdio: "
|
|
318
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
189
319
|
env: process.env,
|
|
320
|
+
windowsHide: true,
|
|
190
321
|
});
|
|
191
322
|
|
|
192
|
-
child
|
|
323
|
+
proxyStreams(child);
|
|
324
|
+
forwardSignal(child, "SIGINT");
|
|
325
|
+
forwardSignal(child, "SIGTERM");
|
|
326
|
+
|
|
327
|
+
child.on("close", (code, signal) => {
|
|
193
328
|
if (signal) {
|
|
194
329
|
process.kill(process.pid, signal);
|
|
195
330
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@josephyan/qingflow-cli",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.60",
|
|
4
4
|
"description": "Human-friendly Qingflow command line interface for auth, record operations, import, tasks, and stable builder flows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|