drift-ml 0.1.6 → 0.1.10
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 +8 -2
- package/bin/drift.js +174 -62
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,13 +52,19 @@ drift
|
|
|
52
52
|
|
|
53
53
|
The `drift` command installs or upgrades the chat CLI (Python) and runs it. You get the welcome and instructions every time.
|
|
54
54
|
|
|
55
|
-
### Alternative: pipx (Python only)
|
|
55
|
+
### Alternative: pipx (Python only — macOS, Linux, Windows)
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
pipx install drift
|
|
58
|
+
pipx install drift-ml
|
|
59
59
|
drift
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
**Update (pipx):**
|
|
63
|
+
```bash
|
|
64
|
+
pipx upgrade drift-ml
|
|
65
|
+
```
|
|
66
|
+
(PowerShell on Windows: same command.)
|
|
67
|
+
|
|
62
68
|
---
|
|
63
69
|
|
|
64
70
|
## Example usage
|
package/bin/drift.js
CHANGED
|
@@ -1,101 +1,213 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
//
|
|
3
|
+
// REMOVED: Any pip/pipx/python -m pip install or upgrade logic.
|
|
4
|
+
// WHY: PEP 668 and user envs; we must not modify Python. User installs CLI via pipx once.
|
|
5
|
+
//
|
|
6
|
+
// FINAL FLOW: (1) If no DRIFT_BACKEND_URL, ensure engine at 127.0.0.1:8000 (download + start if needed).
|
|
7
|
+
// (2) Locate Python drift ONLY in ~/.local/bin (macOS/Linux) or %USERPROFILE%\.local\bin (Windows).
|
|
8
|
+
// (3) Spawn that binary with DRIFT_BACKEND_URL set; never run drift.cmd/drift.ps1/drift.js (self).
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
const { spawnSync, spawn } = require("child_process");
|
|
4
12
|
const path = require("path");
|
|
5
13
|
const fs = require("fs");
|
|
14
|
+
const https = require("https");
|
|
15
|
+
const http = require("http");
|
|
6
16
|
|
|
7
17
|
const isWindows = process.platform === "win32";
|
|
18
|
+
const ENGINE_PORT = process.env.DRIFT_ENGINE_PORT || "8000";
|
|
19
|
+
// Pinned tag: draft releases are invisible to /releases/latest.
|
|
20
|
+
const ENGINE_BASE_URL = process.env.DRIFT_ENGINE_BASE_URL || "https://github.com/lakshitsachdeva/intent2model/releases/download/v0.1.0";
|
|
21
|
+
const HEALTH_URL = `http://127.0.0.1:${ENGINE_PORT}/health`;
|
|
22
|
+
const HEALTH_TIMEOUT_MS = 2000;
|
|
23
|
+
const HEALTH_POLL_MS = 500;
|
|
24
|
+
const HEALTH_POLL_MAX = 60; // 30 seconds total
|
|
25
|
+
const isMac = process.platform === "darwin";
|
|
26
|
+
|
|
27
|
+
function getPlatformKey() {
|
|
28
|
+
const p = process.platform;
|
|
29
|
+
const a = process.arch;
|
|
30
|
+
const plat = p === "darwin" ? "macos" : p === "win32" ? "windows" : "linux";
|
|
31
|
+
const arch = a === "arm64" || a === "aarch64" ? "arm64" : "x64";
|
|
32
|
+
return { plat, arch };
|
|
33
|
+
}
|
|
8
34
|
|
|
9
|
-
|
|
10
|
-
function getPipxBinPaths() {
|
|
35
|
+
function getEngineDir() {
|
|
11
36
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
12
|
-
if (!home) return
|
|
13
|
-
|
|
14
|
-
if (isWindows) {
|
|
15
|
-
return [
|
|
16
|
-
path.join(localBin, "drift.exe"),
|
|
17
|
-
path.join(localBin, "drift"),
|
|
18
|
-
];
|
|
19
|
-
}
|
|
20
|
-
return [
|
|
21
|
-
path.join(localBin, "drift"),
|
|
22
|
-
"/usr/local/bin/drift",
|
|
23
|
-
];
|
|
37
|
+
if (!home) return null;
|
|
38
|
+
return path.join(home, ".drift", "bin");
|
|
24
39
|
}
|
|
25
40
|
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (s.includes("node") && (s.startsWith("@") || s.includes("cmd.exe"))) return true;
|
|
33
|
-
return false;
|
|
41
|
+
function getEnginePath() {
|
|
42
|
+
const dir = getEngineDir();
|
|
43
|
+
if (!dir) return null;
|
|
44
|
+
const { plat, arch } = getPlatformKey();
|
|
45
|
+
const ext = isWindows ? ".exe" : "";
|
|
46
|
+
return path.join(dir, `drift-engine-${plat}-${arch}${ext}`);
|
|
34
47
|
}
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return p;
|
|
49
|
+
function fetchOk(url) {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
const client = url.startsWith("https") ? https : http;
|
|
52
|
+
const req = client.get(url, { timeout: HEALTH_TIMEOUT_MS }, (res) => {
|
|
53
|
+
const redirect = res.statusCode >= 301 && res.statusCode <= 302 && res.headers.location;
|
|
54
|
+
if (redirect) {
|
|
55
|
+
fetchOk(redirect).then(resolve).catch(() => resolve(false));
|
|
56
|
+
return;
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
resolve(res.statusCode === 200);
|
|
59
|
+
});
|
|
60
|
+
req.on("error", () => resolve(false));
|
|
61
|
+
req.on("timeout", () => { req.destroy(); resolve(false); });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function downloadFile(url, destPath) {
|
|
66
|
+
return new Promise((resolve, reject) => {
|
|
67
|
+
const client = url.startsWith("https") ? https : http;
|
|
68
|
+
const req = client.get(url, (res) => {
|
|
69
|
+
const redirect = res.statusCode >= 301 && res.statusCode <= 302 && res.headers.location;
|
|
70
|
+
if (redirect) {
|
|
71
|
+
downloadFile(redirect, destPath).then(resolve).catch(reject);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (res.statusCode !== 200) {
|
|
75
|
+
reject(new Error(`Download failed: ${res.statusCode}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const dir = path.dirname(destPath);
|
|
79
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
80
|
+
const file = fs.createWriteStream(destPath);
|
|
81
|
+
res.pipe(file);
|
|
82
|
+
file.on("finish", () => { file.close(); resolve(); });
|
|
83
|
+
file.on("error", reject);
|
|
84
|
+
});
|
|
85
|
+
req.on("error", reject);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function engineRunning() {
|
|
90
|
+
return fetchOk(HEALTH_URL);
|
|
91
|
+
}
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
|
|
93
|
+
function waitForEngine() {
|
|
94
|
+
return new Promise((resolve) => {
|
|
95
|
+
let n = 0;
|
|
96
|
+
const t = setInterval(() => {
|
|
97
|
+
n++;
|
|
98
|
+
fetchOk(HEALTH_URL).then((ok) => {
|
|
99
|
+
if (ok) { clearInterval(t); resolve(true); }
|
|
100
|
+
else if (n >= HEALTH_POLL_MAX) { clearInterval(t); resolve(false); }
|
|
101
|
+
});
|
|
102
|
+
}, HEALTH_POLL_MS);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
51
105
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
106
|
+
async function ensureEngine() {
|
|
107
|
+
const binPath = getEnginePath();
|
|
108
|
+
const binDir = getEngineDir();
|
|
109
|
+
if (!binPath || !binDir) {
|
|
110
|
+
console.error("drift: Could not resolve engine directory (~/.drift/bin). Set HOME or USERPROFILE.");
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
if (!fs.existsSync(binDir)) {
|
|
114
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
if (!fs.existsSync(binPath)) {
|
|
117
|
+
const { plat, arch } = getPlatformKey();
|
|
118
|
+
const ext = isWindows ? ".exe" : "";
|
|
119
|
+
const asset = `drift-engine-${plat}-${arch}${ext}`;
|
|
120
|
+
const url = `${ENGINE_BASE_URL.replace(/\/$/, "")}/${asset}`;
|
|
121
|
+
process.stderr.write(`drift: Downloading engine (${asset})...\n`);
|
|
122
|
+
try {
|
|
123
|
+
await downloadFile(url, binPath);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error("drift: Download failed.", e.message);
|
|
126
|
+
console.error("drift: Set DRIFT_ENGINE_BASE_URL or run the engine manually.");
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (!isWindows) {
|
|
130
|
+
try { fs.chmodSync(binPath, 0o755); } catch (_) {}
|
|
131
|
+
}
|
|
132
|
+
if (isMac) {
|
|
59
133
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return candidate;
|
|
63
|
-
} catch (_) {
|
|
64
|
-
return candidate;
|
|
65
|
-
}
|
|
134
|
+
spawnSync("xattr", ["-dr", "com.apple.quarantine", binPath], { stdio: "pipe" });
|
|
135
|
+
} catch (_) {}
|
|
66
136
|
}
|
|
67
137
|
}
|
|
138
|
+
// On macOS, always ensure binary is executable and not quarantined before spawn (covers existing binaries).
|
|
139
|
+
if (isMac && binPath) {
|
|
140
|
+
try {
|
|
141
|
+
fs.chmodSync(binPath, 0o755);
|
|
142
|
+
spawnSync("xattr", ["-dr", "com.apple.quarantine", binPath], { stdio: "pipe" });
|
|
143
|
+
} catch (_) {}
|
|
144
|
+
}
|
|
145
|
+
const child = spawn(binPath, [], {
|
|
146
|
+
detached: true,
|
|
147
|
+
stdio: "ignore",
|
|
148
|
+
cwd: binDir,
|
|
149
|
+
env: { ...process.env, DRIFT_ENGINE_PORT: ENGINE_PORT },
|
|
150
|
+
});
|
|
151
|
+
child.unref();
|
|
152
|
+
return waitForEngine();
|
|
153
|
+
}
|
|
68
154
|
|
|
155
|
+
// Locate Python drift ONLY in pipx bin dir. Never search PATH (avoids running drift.cmd/drift.ps1/drift.js).
|
|
156
|
+
function findPythonDrift() {
|
|
157
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
158
|
+
if (!home) return null;
|
|
159
|
+
const localBin = path.join(home, ".local", "bin");
|
|
160
|
+
if (isWindows) {
|
|
161
|
+
const exe = path.join(localBin, "drift.exe");
|
|
162
|
+
if (fs.existsSync(exe)) return exe;
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const unix = path.join(localBin, "drift");
|
|
166
|
+
if (fs.existsSync(unix)) return unix;
|
|
69
167
|
return null;
|
|
70
168
|
}
|
|
71
169
|
|
|
72
|
-
function main() {
|
|
170
|
+
async function main() {
|
|
171
|
+
const userBackend = process.env.DRIFT_BACKEND_URL;
|
|
172
|
+
if (!userBackend) {
|
|
173
|
+
const already = await engineRunning();
|
|
174
|
+
if (!already) {
|
|
175
|
+
const started = await ensureEngine().catch((e) => {
|
|
176
|
+
console.error("drift:", e.message);
|
|
177
|
+
return false;
|
|
178
|
+
});
|
|
179
|
+
if (!started) {
|
|
180
|
+
console.error("Failed to start drift engine.");
|
|
181
|
+
console.error("Please check permissions or download the engine manually.");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
73
187
|
const driftPath = findPythonDrift();
|
|
74
|
-
|
|
75
188
|
if (!driftPath) {
|
|
76
189
|
console.error(`
|
|
77
190
|
drift is not installed.
|
|
78
191
|
|
|
79
|
-
Install
|
|
192
|
+
Install it with:
|
|
80
193
|
|
|
81
|
-
pip install --user pipx
|
|
82
|
-
pipx ensurepath
|
|
83
194
|
pipx install drift-ml
|
|
84
|
-
|
|
85
|
-
Then run:
|
|
86
|
-
|
|
87
|
-
drift
|
|
88
195
|
`);
|
|
89
196
|
process.exit(1);
|
|
90
197
|
}
|
|
91
198
|
|
|
199
|
+
const backendUrl = userBackend || `http://127.0.0.1:${ENGINE_PORT}`;
|
|
200
|
+
const env = { ...process.env, DRIFT_BACKEND_URL: backendUrl };
|
|
201
|
+
|
|
92
202
|
const result = spawnSync(driftPath, process.argv.slice(2), {
|
|
93
203
|
stdio: "inherit",
|
|
94
|
-
env
|
|
95
|
-
shell: isWindows,
|
|
204
|
+
env,
|
|
96
205
|
});
|
|
97
206
|
|
|
98
207
|
process.exit(result.status === null ? (result.signal ? 128 + 9 : 1) : result.status);
|
|
99
208
|
}
|
|
100
209
|
|
|
101
|
-
main()
|
|
210
|
+
main().catch((e) => {
|
|
211
|
+
console.error("drift:", e.message);
|
|
212
|
+
process.exit(1);
|
|
213
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drift-ml",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Drift — terminal-first, chat-based AutoML. Same engine as the web app. On first run: downloads and starts the engine locally (never exposes engine source).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"drift": "./bin/drift.js"
|