pandexai 0.0.1 → 0.1.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/SKILL.md +22 -0
- package/bin/pandex.js +84 -0
- package/commands/scan.md +21 -0
- package/package.json +13 -4
- package/scripts/profile.py +58 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# PandexAI
|
|
2
|
+
|
|
3
|
+
PandexAI is an AI-native data profiling CLI. It runs inside AI coding CLIs
|
|
4
|
+
(Claude Code, Cursor, etc.) to handle deterministic data work as real code,
|
|
5
|
+
and hands the AI clean, structured output to reason over.
|
|
6
|
+
|
|
7
|
+
## Core rule: judgment vs execution
|
|
8
|
+
|
|
9
|
+
- Execution (deterministic): anything that computes numbers from data MUST
|
|
10
|
+
run as real Python/pandas code in scripts/. Never let the AI estimate or
|
|
11
|
+
guess statistics, column types, null counts, or any other figure that a
|
|
12
|
+
script can compute exactly.
|
|
13
|
+
- Judgment (AI): interpreting results, flagging likely issues, suggesting
|
|
14
|
+
next steps, and writing human-readable summaries is the AI's job.
|
|
15
|
+
|
|
16
|
+
This split exists so PandexAI's numbers are always trustworthy: they come
|
|
17
|
+
from real computation, not a language model's guess.
|
|
18
|
+
|
|
19
|
+
## Available commands
|
|
20
|
+
|
|
21
|
+
- scan - profiles a CSV file (columns, types, null %, unique counts, basic
|
|
22
|
+
stats). See commands/scan.md for exact instructions.
|
package/bin/pandex.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { execSync, spawnSync } = require("child_process");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const command = args[0];
|
|
8
|
+
|
|
9
|
+
if (command !== "init") {
|
|
10
|
+
console.log("Usage: npx pandex init");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
const venvDir = path.join(cwd, ".pandex", "venv");
|
|
16
|
+
|
|
17
|
+
function findPython() {
|
|
18
|
+
for (const name of ["python3", "python"]) {
|
|
19
|
+
const result = spawnSync(name, ["--version"], { stdio: "ignore" });
|
|
20
|
+
if (result.status === 0) return name;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("Setting up PandexAI...");
|
|
26
|
+
|
|
27
|
+
const pythonCmd = findPython();
|
|
28
|
+
if (!pythonCmd) {
|
|
29
|
+
console.error("ERROR: No Python installation found on your PATH.");
|
|
30
|
+
console.error("PandexAI needs Python 3 to run its data profiling scripts.");
|
|
31
|
+
console.error("Install Python from https://python.org and try again.");
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
console.log(`Found Python: ${pythonCmd}`);
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(venvDir)) {
|
|
37
|
+
console.log("Creating virtual environment at .pandex/venv ...");
|
|
38
|
+
const venvResult = spawnSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
39
|
+
if (venvResult.status !== 0) {
|
|
40
|
+
console.error("ERROR: Failed to create virtual environment.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
console.log(".pandex/venv already exists, skipping creation.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isWindows = process.platform === "win32";
|
|
48
|
+
const venvPython = isWindows
|
|
49
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
50
|
+
: path.join(venvDir, "bin", "python");
|
|
51
|
+
|
|
52
|
+
console.log("Installing pandas into the virtual environment...");
|
|
53
|
+
const installResult = spawnSync(venvPython, ["-m", "pip", "install", "--quiet", "pandas"], { stdio: "inherit" });
|
|
54
|
+
if (installResult.status !== 0) {
|
|
55
|
+
console.error("ERROR: Failed to install pandas.");
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const packageRoot = path.join(__dirname, "..");
|
|
60
|
+
const filesToCopy = ["SKILL.md", "commands", "scripts"];
|
|
61
|
+
|
|
62
|
+
function copyRecursive(src, dest) {
|
|
63
|
+
const stat = fs.statSync(src);
|
|
64
|
+
if (stat.isDirectory()) {
|
|
65
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
66
|
+
for (const entry of fs.readdirSync(src)) {
|
|
67
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
fs.copyFileSync(src, dest);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("Copying PandexAI files into your project...");
|
|
75
|
+
for (const item of filesToCopy) {
|
|
76
|
+
const src = path.join(packageRoot, item);
|
|
77
|
+
const dest = path.join(cwd, item);
|
|
78
|
+
if (fs.existsSync(src)) {
|
|
79
|
+
copyRecursive(src, dest);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log("PandexAI is ready. Try: /pandex scan <your-file.csv> inside your AI CLI.");
|
package/commands/scan.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# scan
|
|
2
|
+
|
|
3
|
+
Profiles a CSV file: column types, null percentage, unique counts, and
|
|
4
|
+
basic stats (mean/median/min/max for numeric columns, top value counts for
|
|
5
|
+
categorical columns).
|
|
6
|
+
|
|
7
|
+
## How to run this command
|
|
8
|
+
|
|
9
|
+
1. Run: python scripts/profile.py <path-to-csv>
|
|
10
|
+
(use the project's .pandex/venv Python interpreter, not the system one)
|
|
11
|
+
2. The script prints a JSON object with the profiling results.
|
|
12
|
+
3. Do NOT recompute, estimate, or guess any of these numbers yourself.
|
|
13
|
+
Present the JSON results to the user in a clear, readable summary, and
|
|
14
|
+
flag anything that looks like a data quality issue (e.g. high null %,
|
|
15
|
+
suspicious types, low-cardinality columns that might be categorical).
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
user runs: /pandex scan sales.csv
|
|
20
|
+
-> python scripts/profile.py sales.csv
|
|
21
|
+
-> present the resulting JSON as a readable summary
|
package/package.json
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pandexai",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "PandexAI
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "PandexAI - AI-native data profiling CLI. Run pandex init inside your project to get started.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/jahirulanik/pandexai.git"
|
|
9
9
|
},
|
|
10
|
-
"keywords": ["pandas", "data", "cli", "ai"]
|
|
11
|
-
|
|
10
|
+
"keywords": ["pandas", "data", "cli", "ai"],
|
|
11
|
+
"bin": {
|
|
12
|
+
"pandex": "./bin/pandex.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"SKILL.md",
|
|
17
|
+
"commands",
|
|
18
|
+
"scripts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import json
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def profile_csv(path):
|
|
7
|
+
df = pd.read_csv(path)
|
|
8
|
+
result = {
|
|
9
|
+
"file": path,
|
|
10
|
+
"row_count": len(df),
|
|
11
|
+
"column_count": len(df.columns),
|
|
12
|
+
"columns": {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
for col in df.columns:
|
|
16
|
+
series = df[col]
|
|
17
|
+
col_info = {
|
|
18
|
+
"dtype": str(series.dtype),
|
|
19
|
+
"null_count": int(series.isnull().sum()),
|
|
20
|
+
"null_percent": round(float(series.isnull().mean() * 100), 2),
|
|
21
|
+
"unique_count": int(series.nunique())
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if pd.api.types.is_numeric_dtype(series):
|
|
25
|
+
col_info["mean"] = _safe_float(series.mean())
|
|
26
|
+
col_info["median"] = _safe_float(series.median())
|
|
27
|
+
col_info["min"] = _safe_float(series.min())
|
|
28
|
+
col_info["max"] = _safe_float(series.max())
|
|
29
|
+
else:
|
|
30
|
+
top_values = series.value_counts().head(5)
|
|
31
|
+
col_info["top_values"] = {str(k): int(v) for k, v in top_values.items()}
|
|
32
|
+
|
|
33
|
+
result["columns"][col] = col_info
|
|
34
|
+
|
|
35
|
+
return result
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _safe_float(value):
|
|
39
|
+
try:
|
|
40
|
+
if pd.isna(value):
|
|
41
|
+
return None
|
|
42
|
+
return round(float(value), 4)
|
|
43
|
+
except (TypeError, ValueError):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
if len(sys.argv) != 2:
|
|
49
|
+
print(json.dumps({"error": "Usage: python profile.py <path-to-csv>"}))
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
csv_path = sys.argv[1]
|
|
53
|
+
try:
|
|
54
|
+
profile = profile_csv(csv_path)
|
|
55
|
+
print(json.dumps(profile, indent=2))
|
|
56
|
+
except Exception as e:
|
|
57
|
+
print(json.dumps({"error": str(e)}))
|
|
58
|
+
sys.exit(1)
|