flakeiq 1.0.11
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/LICENSE +21 -0
- package/README.md +423 -0
- package/bin/flakeiq.js +173 -0
- package/lib/classify.js +38 -0
- package/lib/config.js +51 -0
- package/lib/seed.js +30 -0
- package/lib/serve.js +59 -0
- package/lib/setup.js +79 -0
- package/lib/utils.js +40 -0
- package/package.json +55 -0
- package/python/classify.py +191 -0
- package/python/dashboard.py +321 -0
- package/python/requirements.txt +2 -0
- package/python/seed.py +256 -0
- package/python/web/static/app.js +296 -0
- package/python/web/static/index.html +43 -0
- package/python/web/static/style.css +46 -0
- package/reporter/flake-reporter.js +93 -0
- package/reporter/index.js +1 -0
- package/scripts/postinstall.js +27 -0
package/lib/seed.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getConfig } = require('./config');
|
|
4
|
+
|
|
5
|
+
function seed(dbPath, options = {}) {
|
|
6
|
+
const config = getConfig();
|
|
7
|
+
const python = config.venvPython;
|
|
8
|
+
const seedScript = path.join(config.pythonDir, 'seed.py');
|
|
9
|
+
|
|
10
|
+
const args = [seedScript];
|
|
11
|
+
if (dbPath) args.push(dbPath);
|
|
12
|
+
|
|
13
|
+
const child = spawn(python, args, {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
env: { ...process.env, FLAKE_DB: dbPath || config.dbPath },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
child.on('error', (err) => {
|
|
19
|
+
if (err.code === 'ENOENT') {
|
|
20
|
+
console.error('Python not found. Run "npx flakeiq setup" or install Python 3.11+.');
|
|
21
|
+
} else {
|
|
22
|
+
console.error('Seed error:', err.message);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on('exit', (code) => { process.exit(code || 0); });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { seed };
|
package/lib/serve.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { getConfig } = require('./config');
|
|
5
|
+
const { findAvailablePort } = require('./utils');
|
|
6
|
+
|
|
7
|
+
async function serve(options = {}) {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const python = config.venvPython;
|
|
10
|
+
const dashboardScript = path.join(config.pythonDir, 'dashboard.py');
|
|
11
|
+
const seedScript = path.join(config.pythonDir, 'seed.py');
|
|
12
|
+
|
|
13
|
+
const port = options.port || config.port;
|
|
14
|
+
const host = options.host || config.host;
|
|
15
|
+
const actualPort = await findAvailablePort(port, host);
|
|
16
|
+
const useSeed = options.seed || false;
|
|
17
|
+
const dbPath = options.db || config.dbPath;
|
|
18
|
+
|
|
19
|
+
if (useSeed && !fs.existsSync(dbPath)) {
|
|
20
|
+
console.log('Seed database not found. Generating...');
|
|
21
|
+
await new Promise((resolve, reject) => {
|
|
22
|
+
const seedChild = spawn(python, [seedScript, dbPath], {
|
|
23
|
+
stdio: 'inherit',
|
|
24
|
+
env: { ...process.env, FLAKE_DB: dbPath },
|
|
25
|
+
});
|
|
26
|
+
seedChild.on('exit', (code) => code === 0 ? resolve() : reject(new Error('Seed failed')));
|
|
27
|
+
seedChild.on('error', reject);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const args = [dashboardScript, '--port', String(actualPort), '--host', host, '--db', dbPath];
|
|
32
|
+
if (options.open !== false) args.push('--open');
|
|
33
|
+
|
|
34
|
+
console.log(`Starting FlakeIQ dashboard on http://${host}:${actualPort}`);
|
|
35
|
+
if (useSeed) console.log('Using seed database...');
|
|
36
|
+
|
|
37
|
+
const child = spawn(python, args, {
|
|
38
|
+
stdio: 'inherit',
|
|
39
|
+
env: { ...process.env, FLAKE_DB: dbPath },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
child.on('error', (err) => {
|
|
43
|
+
if (err.code === 'ENOENT') {
|
|
44
|
+
console.error('Python not found. Run "npx flakeiq setup" or install Python 3.11+.');
|
|
45
|
+
} else {
|
|
46
|
+
console.error('Dashboard error:', err.message);
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
child.on('exit', (code) => {
|
|
52
|
+
process.exit(code || 0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
process.on('SIGINT', () => { child.kill('SIGINT'); });
|
|
56
|
+
process.on('SIGTERM', () => { child.kill('SIGTERM'); });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { serve };
|
package/lib/setup.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { getVenvDir, getVenvPython, PYTHON_DIR } = require('./config');
|
|
5
|
+
const { getPythonVersion } = require('./utils');
|
|
6
|
+
|
|
7
|
+
const REQUIREMENTS = path.join(PYTHON_DIR, 'requirements.txt');
|
|
8
|
+
|
|
9
|
+
function findSystemPython() {
|
|
10
|
+
const candidates = process.platform === 'win32'
|
|
11
|
+
? ['python', 'python3', 'py -3']
|
|
12
|
+
: ['python3', 'python'];
|
|
13
|
+
|
|
14
|
+
for (const cmd of candidates) {
|
|
15
|
+
const ver = getPythonVersion(cmd);
|
|
16
|
+
if (ver && (ver.major > 3 || (ver.major === 3 && ver.minor >= 11))) {
|
|
17
|
+
return cmd;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createVenv(pythonCmd) {
|
|
24
|
+
const venvDir = getVenvDir();
|
|
25
|
+
if (fs.existsSync(getVenvPython())) return true;
|
|
26
|
+
|
|
27
|
+
console.log(' Creating Python virtual environment...');
|
|
28
|
+
try {
|
|
29
|
+
execSync(`"${pythonCmd}" -m venv "${venvDir}"`, { stdio: 'pipe', timeout: 60000 });
|
|
30
|
+
return true;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.error(' Failed to create venv:', e.message);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function installDeps() {
|
|
38
|
+
const pip = process.platform === 'win32'
|
|
39
|
+
? path.join(getVenvDir(), 'Scripts', 'pip.exe')
|
|
40
|
+
: path.join(getVenvDir(), 'bin', 'pip3');
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(pip)) {
|
|
43
|
+
console.error(' pip not found in venv');
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(' Installing Python dependencies...');
|
|
48
|
+
try {
|
|
49
|
+
execSync(`"${pip}" install -r "${REQUIREMENTS}" --quiet`, { stdio: 'pipe', timeout: 120000 });
|
|
50
|
+
return true;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(' Failed to install deps:', e.message);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setup() {
|
|
58
|
+
const python = findSystemPython();
|
|
59
|
+
if (!python) {
|
|
60
|
+
console.log('');
|
|
61
|
+
console.log(' WARNING: Python 3.11+ not found on your system.');
|
|
62
|
+
console.log(' FlakeIQ dashboard requires Python. Install it from:');
|
|
63
|
+
console.log(' https://www.python.org/downloads/');
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(' The flake-reporter will still work without Python.');
|
|
66
|
+
console.log('');
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(` Found Python: ${python}`);
|
|
71
|
+
|
|
72
|
+
if (!createVenv(python)) return false;
|
|
73
|
+
if (!installDeps()) return false;
|
|
74
|
+
|
|
75
|
+
console.log(' Python environment ready.');
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { setup, findSystemPython };
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
|
|
4
|
+
function isPortAvailable(port, host = '127.0.0.1') {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const server = net.createServer();
|
|
7
|
+
server.once('error', () => resolve(false));
|
|
8
|
+
server.once('listening', () => { server.close(() => resolve(true)); });
|
|
9
|
+
server.listen(port, host);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function findAvailablePort(startPort, host = '127.0.0.1') {
|
|
14
|
+
return new Promise(async (resolve) => {
|
|
15
|
+
for (let port = startPort; port < startPort + 100; port++) {
|
|
16
|
+
if (await isPortAvailable(port, host)) { resolve(port); return; }
|
|
17
|
+
}
|
|
18
|
+
resolve(startPort);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function openBrowser(url) {
|
|
23
|
+
const cmd = process.platform === 'win32'
|
|
24
|
+
? `start "" "${url}"`
|
|
25
|
+
: process.platform === 'darwin'
|
|
26
|
+
? `open "${url}"`
|
|
27
|
+
: `xdg-open "${url}"`;
|
|
28
|
+
try { execSync(cmd, { stdio: 'ignore' }); } catch {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getPythonVersion(pythonPath) {
|
|
32
|
+
try {
|
|
33
|
+
const out = execSync(`"${pythonPath}" --version`, { encoding: 'utf8', timeout: 5000 }).trim();
|
|
34
|
+
const match = out.match(/Python (\d+)\.(\d+)/);
|
|
35
|
+
if (match) return { major: parseInt(match[1]), minor: parseInt(match[2]), raw: out };
|
|
36
|
+
} catch {}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { isPortAvailable, findAvailablePort, openBrowser, getPythonVersion };
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flakeiq",
|
|
3
|
+
"version": "1.0.11",
|
|
4
|
+
"description": "Test flake tracking + LLM failure classification for E2E tests — works with Playwright, Detox, Appium, Selenium, and any framework",
|
|
5
|
+
"main": "reporter/flake-reporter.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"flakeiq": "./bin/flakeiq.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"python/",
|
|
13
|
+
"reporter/",
|
|
14
|
+
"scripts/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node scripts/postinstall.js",
|
|
18
|
+
"test": "node --test tests/",
|
|
19
|
+
"start": "node bin/flakeiq.js serve"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"playwright",
|
|
23
|
+
"detox",
|
|
24
|
+
"appium",
|
|
25
|
+
"selenium",
|
|
26
|
+
"flaky-tests",
|
|
27
|
+
"test-results",
|
|
28
|
+
"e2e",
|
|
29
|
+
"test-tracker",
|
|
30
|
+
"llm",
|
|
31
|
+
"classification",
|
|
32
|
+
"test-flake"
|
|
33
|
+
],
|
|
34
|
+
"author": "",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@playwright/test": ">=1.40"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@playwright/test": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/sarang-code2/FlakeIQ-Package.git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/sarang-code2/FlakeIQ-Package#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/sarang-code2/FlakeIQ-Package/issues"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import sqlite3
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from urllib.request import Request, urlopen
|
|
9
|
+
from urllib.error import URLError
|
|
10
|
+
|
|
11
|
+
DB_PATH = os.environ.get("FLAKE_DB", "flake.db")
|
|
12
|
+
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434/api/generate")
|
|
13
|
+
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.2")
|
|
14
|
+
|
|
15
|
+
CLASSIFICATION_PROMPT = """You are a test failure classifier. Given a mobile E2E test failure, classify it into exactly one category.
|
|
16
|
+
|
|
17
|
+
Categories:
|
|
18
|
+
- REAL_BUG: the app behavior is wrong (element missing, wrong UI state, crash, assertion failure)
|
|
19
|
+
- TIMEOUT_FLAKE: action timed out but the element typically appears eventually (race condition, slow render)
|
|
20
|
+
- DEVICE_FLAKE: device/emulator issue (connection lost, ANR dialog, memory pressure, "could not verify foreground")
|
|
21
|
+
- LOCATOR_FLAKE: element found but not visible/interactable, scrollIntoViewIfNeeded failed, stale element
|
|
22
|
+
- UNKNOWN: can't determine from the given information
|
|
23
|
+
|
|
24
|
+
Reply with ONLY the category name and a one-sentence reason separated by "|".
|
|
25
|
+
Example: "TIMEOUT_FLAKE|actionTimeout exceeded on fill, element appeared on retry"
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
Test: {test_name}
|
|
29
|
+
Platform: {platform}
|
|
30
|
+
Error: {error_message}
|
|
31
|
+
Last actions: {last_actions}
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
SCHEMA = """
|
|
35
|
+
CREATE TABLE IF NOT EXISTS test_runs (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
test_file TEXT,
|
|
38
|
+
test_name TEXT,
|
|
39
|
+
platform TEXT,
|
|
40
|
+
device_id TEXT,
|
|
41
|
+
duration_ms INTEGER,
|
|
42
|
+
result TEXT,
|
|
43
|
+
error_message TEXT,
|
|
44
|
+
last_actions TEXT,
|
|
45
|
+
classification TEXT,
|
|
46
|
+
classification_reason TEXT,
|
|
47
|
+
screen_name TEXT,
|
|
48
|
+
action_type TEXT,
|
|
49
|
+
session_id TEXT,
|
|
50
|
+
run_at TEXT
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_run_at ON test_runs(run_at);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_screen ON test_runs(screen_name);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_result ON test_runs(result);
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def init_db():
|
|
59
|
+
db = sqlite3.connect(DB_PATH)
|
|
60
|
+
db.executescript(SCHEMA)
|
|
61
|
+
db.commit()
|
|
62
|
+
return db
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def classify_failure(record: dict) -> tuple[str | None, str | None]:
|
|
66
|
+
prompt = CLASSIFICATION_PROMPT.format(
|
|
67
|
+
test_name=record["test_name"],
|
|
68
|
+
platform=record.get("platform", "unknown"),
|
|
69
|
+
error_message=record.get("error_message", "")[:2000],
|
|
70
|
+
last_actions=json.dumps(record.get("last_actions", [])),
|
|
71
|
+
)
|
|
72
|
+
payload = json.dumps({"model": MODEL, "prompt": prompt, "stream": False}).encode()
|
|
73
|
+
req = Request(OLLAMA_URL, data=payload, headers={"Content-Type": "application/json"})
|
|
74
|
+
try:
|
|
75
|
+
resp = urlopen(req, timeout=30)
|
|
76
|
+
body = json.loads(resp.read())
|
|
77
|
+
text = body.get("response", "").strip()
|
|
78
|
+
if "|" in text:
|
|
79
|
+
cat, reason = text.split("|", 1)
|
|
80
|
+
cat = cat.strip().upper()
|
|
81
|
+
if cat in ("REAL_BUG", "TIMEOUT_FLAKE", "DEVICE_FLAKE", "LOCATOR_FLAKE", "UNKNOWN"):
|
|
82
|
+
return cat, reason.strip()
|
|
83
|
+
return "UNKNOWN", f"Could not parse: {text[:200]}"
|
|
84
|
+
except URLError as e:
|
|
85
|
+
print(f" [warn] Ollama unavailable ({e.reason}), skipping classification")
|
|
86
|
+
return None, None
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f" [warn] Classification failed: {e}")
|
|
89
|
+
return None, None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def guess_action_type(last_actions: list[str]) -> str:
|
|
93
|
+
for action in reversed(last_actions):
|
|
94
|
+
al = action.lower()
|
|
95
|
+
if "fill" in al: return "fill"
|
|
96
|
+
if "tap" in al or "click" in al: return "tap"
|
|
97
|
+
if "swipe" in al or "scroll" in al: return "swipe"
|
|
98
|
+
if "scrollintoview" in al: return "scrollIntoView"
|
|
99
|
+
if "press" in al: return "press"
|
|
100
|
+
return "other"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def process_jsonl(jsonl_path: str, db: sqlite3.Connection):
|
|
104
|
+
count = 0
|
|
105
|
+
classified = 0
|
|
106
|
+
skipped = 0
|
|
107
|
+
cursor = db.cursor()
|
|
108
|
+
|
|
109
|
+
with open(jsonl_path) as f:
|
|
110
|
+
for line in f:
|
|
111
|
+
line = line.strip()
|
|
112
|
+
if not line:
|
|
113
|
+
continue
|
|
114
|
+
record = json.loads(line)
|
|
115
|
+
count += 1
|
|
116
|
+
|
|
117
|
+
# detect duplicate by test_name + run_at
|
|
118
|
+
cursor.execute(
|
|
119
|
+
"SELECT id FROM test_runs WHERE test_name=? AND run_at=?",
|
|
120
|
+
(record["test_name"], record["run_at"]),
|
|
121
|
+
)
|
|
122
|
+
if cursor.fetchone():
|
|
123
|
+
skipped += 1
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
action_type = guess_action_type(record.get("last_actions", []))
|
|
127
|
+
|
|
128
|
+
if record["result"] != "passed":
|
|
129
|
+
cat, reason = classify_failure(record)
|
|
130
|
+
if cat:
|
|
131
|
+
classified += 1
|
|
132
|
+
record["classification"] = cat
|
|
133
|
+
record["classification_reason"] = reason
|
|
134
|
+
else:
|
|
135
|
+
record["classification"] = None
|
|
136
|
+
record["classification_reason"] = None
|
|
137
|
+
|
|
138
|
+
cursor.execute(
|
|
139
|
+
"""INSERT INTO test_runs
|
|
140
|
+
(test_file, test_name, platform, device_id, duration_ms, result,
|
|
141
|
+
error_message, last_actions, classification, classification_reason,
|
|
142
|
+
screen_name, action_type, session_id, run_at)
|
|
143
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
144
|
+
(
|
|
145
|
+
record.get("test_file"),
|
|
146
|
+
record["test_name"],
|
|
147
|
+
record.get("platform"),
|
|
148
|
+
record.get("device_id"),
|
|
149
|
+
record.get("duration_ms"),
|
|
150
|
+
record["result"],
|
|
151
|
+
record.get("error_message", ""),
|
|
152
|
+
json.dumps(record.get("last_actions", [])),
|
|
153
|
+
record.get("classification"),
|
|
154
|
+
record.get("classification_reason"),
|
|
155
|
+
record.get("screen_name"),
|
|
156
|
+
action_type,
|
|
157
|
+
record.get("session_id"),
|
|
158
|
+
record["run_at"],
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
db.commit()
|
|
163
|
+
print(f"Processed: {count} records ({classified} classified, {skipped} skipped duplicates)")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def main():
|
|
167
|
+
parser = argparse.ArgumentParser(description="FlakeIQ: classify test failures and store in SQLite")
|
|
168
|
+
parser.add_argument("jsonl", nargs="?", default="flake-results.jsonl",
|
|
169
|
+
help="Path to JSONL file from flake-reporter.ts")
|
|
170
|
+
parser.add_argument("--db", default=DB_PATH, help="SQLite database path")
|
|
171
|
+
parser.add_argument("--ollama-url", default=OLLAMA_URL, help="Ollama API URL")
|
|
172
|
+
parser.add_argument("--model", default=MODEL, help="Ollama model name")
|
|
173
|
+
args = parser.parse_args()
|
|
174
|
+
|
|
175
|
+
os.environ["FLAKE_DB"] = args.db
|
|
176
|
+
os.environ["OLLAMA_URL"] = args.ollama_url
|
|
177
|
+
os.environ["OLLAMA_MODEL"] = args.model
|
|
178
|
+
|
|
179
|
+
if not os.path.exists(args.jsonl):
|
|
180
|
+
print(f"JSONL file not found: {args.jsonl}")
|
|
181
|
+
sys.exit(1)
|
|
182
|
+
|
|
183
|
+
db = sqlite3.connect(args.db)
|
|
184
|
+
db.executescript(SCHEMA)
|
|
185
|
+
db.commit()
|
|
186
|
+
process_jsonl(args.jsonl, db)
|
|
187
|
+
db.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|