@qubiqlabs/mobiflow 0.3.0 → 0.5.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.
- package/README.md +10 -0
- package/bin/mobiflow.js +124 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,16 @@ npm install -g @qubiqlabs/mobiflow
|
|
|
37
37
|
npx @qubiqlabs/mobiflow --help
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
On Windows, the wrapper prefers `py -3.12` / `py -3` (not the Microsoft Store
|
|
41
|
+
`python` stub). If detection still fails:
|
|
42
|
+
|
|
43
|
+
```bat
|
|
44
|
+
set MOBIFLOW_PYTHON=C:\Path\To\Python312\python.exe
|
|
45
|
+
mobiflow init
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or skip npm and run: `py -3.12 -m pip install mobiflow` then `py -3.12 -m mobiflow init`.
|
|
49
|
+
|
|
40
50
|
See [docs/PUBLISH.md](docs/PUBLISH.md) for maintainers.
|
|
41
51
|
|
|
42
52
|
## Quick start
|
package/bin/mobiflow.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* npm bin wrapper for the Python MobiFlow CLI.
|
|
4
4
|
* Resolves: PATH mobiflow → python -m mobiflow → pip install → retry.
|
|
5
|
+
*
|
|
6
|
+
* Windows note: never run ``python -c "…"`` through ``cmd.exe`` (shell:true) —
|
|
7
|
+
* quoting breaks and a real 3.12 install looks like “no Python 3.11+”.
|
|
5
8
|
*/
|
|
6
9
|
"use strict";
|
|
7
10
|
|
|
@@ -12,50 +15,126 @@ const path = require("path");
|
|
|
12
15
|
const PKG = require("../package.json");
|
|
13
16
|
const VERSION = PKG.version || "0.1.0";
|
|
14
17
|
const REPO = "https://github.com/javed0211/MobiFlow.git";
|
|
18
|
+
const IS_WIN = process.platform === "win32";
|
|
19
|
+
|
|
20
|
+
/** @typedef {{ cmd: string, prefixArgs?: string[], label?: string }} PyCandidate */
|
|
15
21
|
|
|
16
22
|
function pyCandidates() {
|
|
17
|
-
|
|
23
|
+
/** @type {PyCandidate[]} */
|
|
18
24
|
const list = [];
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
list.push(
|
|
25
|
+
const fromEnv = process.env.MOBIFLOW_PYTHON;
|
|
26
|
+
if (fromEnv) {
|
|
27
|
+
list.push({ cmd: fromEnv, label: "MOBIFLOW_PYTHON" });
|
|
28
|
+
}
|
|
29
|
+
if (IS_WIN) {
|
|
30
|
+
// Prefer the Python launcher so we skip the Windows Store stub.
|
|
31
|
+
list.push(
|
|
32
|
+
{ cmd: "py", prefixArgs: ["-3.12"], label: "py -3.12" },
|
|
33
|
+
{ cmd: "py", prefixArgs: ["-3.11"], label: "py -3.11" },
|
|
34
|
+
{ cmd: "py", prefixArgs: ["-3"], label: "py -3" },
|
|
35
|
+
{ cmd: "python3.12", label: "python3.12" },
|
|
36
|
+
{ cmd: "python3.11", label: "python3.11" },
|
|
37
|
+
{ cmd: "python", label: "python" },
|
|
38
|
+
{ cmd: "python3", label: "python3" }
|
|
39
|
+
);
|
|
22
40
|
} else {
|
|
23
|
-
list.push(
|
|
41
|
+
list.push(
|
|
42
|
+
{ cmd: "python3.12", label: "python3.12" },
|
|
43
|
+
{ cmd: "python3.11", label: "python3.11" },
|
|
44
|
+
{ cmd: "python3", label: "python3" },
|
|
45
|
+
{ cmd: "python", label: "python" }
|
|
46
|
+
);
|
|
24
47
|
}
|
|
25
|
-
return
|
|
48
|
+
return list;
|
|
26
49
|
}
|
|
27
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Spawn without a shell so argv (especially ``-c``) is not mangled by cmd.exe.
|
|
53
|
+
* @param {string} cmd
|
|
54
|
+
* @param {string[]} args
|
|
55
|
+
* @param {{ stdio?: any }} [opts]
|
|
56
|
+
*/
|
|
28
57
|
function run(cmd, args, opts = {}) {
|
|
29
58
|
return spawnSync(cmd, args, {
|
|
30
59
|
stdio: opts.stdio ?? "inherit",
|
|
31
60
|
encoding: "utf8",
|
|
32
|
-
|
|
61
|
+
windowsHide: true,
|
|
33
62
|
env: process.env,
|
|
34
63
|
});
|
|
35
64
|
}
|
|
36
65
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
66
|
+
/**
|
|
67
|
+
* @param {PyCandidate} cand
|
|
68
|
+
* @returns {{ major: number, minor: number } | null}
|
|
69
|
+
*/
|
|
70
|
+
function pythonVersion(cand) {
|
|
71
|
+
// Avoid f-strings / nested quotes — Windows cmd historically mangled those
|
|
72
|
+
// when shell:true was used; keep the probe trivial either way.
|
|
73
|
+
const code =
|
|
74
|
+
"import sys; print(str(sys.version_info[0]) + '.' + str(sys.version_info[1]))";
|
|
75
|
+
const prefix = cand.prefixArgs || [];
|
|
76
|
+
const r = run(cand.cmd, [...prefix, "-c", code], {
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
});
|
|
79
|
+
if (r.error && r.error.code === "ENOENT") return null;
|
|
80
|
+
if (r.status !== 0 || !r.stdout) return null;
|
|
81
|
+
const text = r.stdout.toString().trim().split(/\r?\n/).pop() || "";
|
|
82
|
+
const m = text.match(/^(\d+)\.(\d+)/);
|
|
83
|
+
if (!m) return null;
|
|
84
|
+
return { major: Number(m[1]), minor: Number(m[2]) };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Reject the Microsoft Store alias that opens the Store instead of Python.
|
|
89
|
+
* @param {string} exe
|
|
90
|
+
*/
|
|
91
|
+
function isWindowsStoreStub(exe) {
|
|
92
|
+
if (!IS_WIN || !exe) return false;
|
|
93
|
+
const n = exe.replace(/\//g, "\\").toLowerCase();
|
|
94
|
+
return (
|
|
95
|
+
n.includes("\\windowsapps\\") ||
|
|
96
|
+
n.includes("\\microsoft\\windowsapps\\") ||
|
|
97
|
+
n.endsWith("\\windowsapps\\python.exe") ||
|
|
98
|
+
n.endsWith("\\windowsapps\\python3.exe")
|
|
42
99
|
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @param {PyCandidate} cand
|
|
104
|
+
* @returns {string | null}
|
|
105
|
+
*/
|
|
106
|
+
function resolveExecutable(cand) {
|
|
107
|
+
const prefix = cand.prefixArgs || [];
|
|
108
|
+
const r = run(cand.cmd, [...prefix, "-c", "import sys; print(sys.executable)"], {
|
|
109
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
110
|
+
});
|
|
43
111
|
if (r.status !== 0 || !r.stdout) return null;
|
|
44
|
-
const
|
|
45
|
-
|
|
112
|
+
const exe = r.stdout.toString().trim().split(/\r?\n/).pop() || "";
|
|
113
|
+
if (!exe || isWindowsStoreStub(exe)) return null;
|
|
114
|
+
return exe;
|
|
46
115
|
}
|
|
47
116
|
|
|
48
117
|
function whichPython() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return r.stdout.toString().trim();
|
|
118
|
+
const tried = [];
|
|
119
|
+
for (const cand of pyCandidates()) {
|
|
120
|
+
const label = cand.label || cand.cmd;
|
|
121
|
+
const ver = pythonVersion(cand);
|
|
122
|
+
if (!ver) {
|
|
123
|
+
tried.push(`${label} (not found / failed)`);
|
|
124
|
+
continue;
|
|
57
125
|
}
|
|
126
|
+
if (ver.major < 3 || ver.minor < 11) {
|
|
127
|
+
tried.push(`${label} (${ver.major}.${ver.minor} < 3.11)`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const exe = resolveExecutable(cand);
|
|
131
|
+
if (!exe) {
|
|
132
|
+
tried.push(`${label} (${ver.major}.${ver.minor}, stub or unusable)`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
return exe;
|
|
58
136
|
}
|
|
137
|
+
whichPython._tried = tried;
|
|
59
138
|
return null;
|
|
60
139
|
}
|
|
61
140
|
|
|
@@ -91,25 +170,32 @@ function ensureMobiflow(py) {
|
|
|
91
170
|
return false;
|
|
92
171
|
}
|
|
93
172
|
|
|
173
|
+
function pathLookup(binName) {
|
|
174
|
+
// Use where.exe explicitly — ``where`` with shell can behave oddly.
|
|
175
|
+
const cmd = IS_WIN ? "where.exe" : "which";
|
|
176
|
+
return run(cmd, [binName], { stdio: ["ignore", "pipe", "ignore"] });
|
|
177
|
+
}
|
|
178
|
+
|
|
94
179
|
function main(argv) {
|
|
95
180
|
const py = whichPython();
|
|
96
181
|
if (!py) {
|
|
182
|
+
const tried = whichPython._tried || [];
|
|
97
183
|
console.error(
|
|
98
184
|
"[mobiflow] Python 3.11+ is required on PATH.\n" +
|
|
99
185
|
" https://www.python.org/downloads/\n" +
|
|
100
|
-
" Or set MOBIFLOW_PYTHON
|
|
101
|
-
|
|
186
|
+
" Or set MOBIFLOW_PYTHON to your python.exe, e.g.\n" +
|
|
187
|
+
' set MOBIFLOW_PYTHON=C:\\Users\\You\\AppData\\Local\\Programs\\Python\\Python312\\python.exe\n' +
|
|
188
|
+
" Or: py -3.12 -m pip install mobiflow && py -3.12 -m mobiflow --help"
|
|
102
189
|
);
|
|
190
|
+
if (tried.length) {
|
|
191
|
+
console.error(" Tried: " + tried.join("; "));
|
|
192
|
+
}
|
|
103
193
|
process.exit(1);
|
|
104
194
|
}
|
|
105
195
|
|
|
106
196
|
// Prefer an already-installed console script on PATH (avoid recursion).
|
|
107
197
|
const self = path.resolve(__filename);
|
|
108
|
-
const onPath =
|
|
109
|
-
process.platform === "win32" ? "where" : "which",
|
|
110
|
-
["mobiflow"],
|
|
111
|
-
{ stdio: ["ignore", "pipe", "ignore"] }
|
|
112
|
-
);
|
|
198
|
+
const onPath = pathLookup("mobiflow");
|
|
113
199
|
if (onPath.status === 0 && onPath.stdout) {
|
|
114
200
|
const candidates = onPath.stdout
|
|
115
201
|
.toString()
|
|
@@ -131,7 +217,13 @@ function main(argv) {
|
|
|
131
217
|
if (resolved.includes(`${path.sep}mobiflow${path.sep}bin${path.sep}`)) {
|
|
132
218
|
continue;
|
|
133
219
|
}
|
|
134
|
-
|
|
220
|
+
// Console scripts on Windows are often .cmd — shell helps those only.
|
|
221
|
+
const r = spawnSync(bin, argv, {
|
|
222
|
+
stdio: "inherit",
|
|
223
|
+
windowsHide: true,
|
|
224
|
+
env: process.env,
|
|
225
|
+
shell: IS_WIN && /\.(cmd|bat)$/i.test(bin),
|
|
226
|
+
});
|
|
135
227
|
process.exit(r.status ?? 1);
|
|
136
228
|
}
|
|
137
229
|
}
|
|
@@ -139,8 +231,8 @@ function main(argv) {
|
|
|
139
231
|
if (!ensureMobiflow(py)) {
|
|
140
232
|
console.error(
|
|
141
233
|
"[mobiflow] Could not install the Python package.\n" +
|
|
142
|
-
` Try: ${py} -m pip install "git+${REPO}@main"\n` +
|
|
143
|
-
` Or: ${py} -m pip install mobiflow`
|
|
234
|
+
` Try: "${py}" -m pip install "git+${REPO}@main"\n` +
|
|
235
|
+
` Or: "${py}" -m pip install mobiflow`
|
|
144
236
|
);
|
|
145
237
|
process.exit(1);
|
|
146
238
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qubiqlabs/mobiflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "CLI: NL \u2192 Maestro mobile flows via LLM, run on device/emulator, self-heal. (npm wrapper for the Python package)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "QubiQ Labs <labs@qubiq.ai>",
|