crushdataai 1.2.7 → 1.2.8

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.
Files changed (36) hide show
  1. package/assets/antigravity/data-analyst.md +8 -4
  2. package/assets/claude/SKILL.md +7 -2
  3. package/assets/copilot/data-analyst.prompt.md +8 -4
  4. package/assets/cursor/data-analyst.md +8 -4
  5. package/assets/kiro/data-analyst.md +8 -4
  6. package/assets/windsurf/data-analyst.md +8 -4
  7. package/dist/commands/schema.d.ts +1 -0
  8. package/dist/commands/schema.js +54 -0
  9. package/dist/connections.d.ts +3 -1
  10. package/dist/connections.js +125 -0
  11. package/dist/connectors/additional/index.d.ts +42 -0
  12. package/dist/connectors/additional/index.js +268 -0
  13. package/dist/connectors/cloud/index.d.ts +8 -3
  14. package/dist/connectors/cloud/index.js +321 -10
  15. package/dist/connectors/csv/index.d.ts +1 -0
  16. package/dist/connectors/csv/index.js +27 -7
  17. package/dist/connectors/custom/index.d.ts +10 -0
  18. package/dist/connectors/custom/index.js +61 -0
  19. package/dist/connectors/index.d.ts +6 -0
  20. package/dist/connectors/mysql/index.d.ts +1 -0
  21. package/dist/connectors/mysql/index.js +162 -9
  22. package/dist/connectors/postgresql/index.d.ts +2 -0
  23. package/dist/connectors/postgresql/index.js +140 -8
  24. package/dist/connectors/shopify/index.d.ts +1 -0
  25. package/dist/connectors/shopify/index.js +44 -5
  26. package/dist/index.js +7 -0
  27. package/dist/routes/connections.js +2 -0
  28. package/dist/server.js +8 -0
  29. package/package.json +9 -4
  30. package/ui/assets/index-Ba1mRihD.js +40 -0
  31. package/ui/assets/index-rlcHFDJB.css +1 -0
  32. package/ui/favicon.svg +4 -18
  33. package/ui/index.html +7 -324
  34. package/ui/favicon-32x32.png +0 -0
  35. package/ui/main.js +0 -541
  36. package/ui/styles.css +0 -718
@@ -1,29 +1,182 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.MySQLConnector = void 0;
7
+ const promise_1 = __importDefault(require("mysql2/promise"));
4
8
  class MySQLConnector {
5
9
  constructor() {
6
10
  this.type = 'mysql';
7
11
  }
8
12
  async test(connection) {
9
- // Stub implementation
13
+ console.log(`[MySQL] Testing connection for ${connection.name} (Host: ${connection.host})`);
10
14
  if (!connection.host || !connection.user || !connection.database) {
11
15
  throw new Error('Host, user, and database are required');
12
16
  }
13
- return true;
17
+ let conn = null;
18
+ try {
19
+ conn = await promise_1.default.createConnection({
20
+ host: connection.host,
21
+ port: connection.port || 3306,
22
+ user: connection.user,
23
+ password: connection.password || '',
24
+ database: connection.database,
25
+ connectTimeout: 10000
26
+ });
27
+ await conn.execute('SELECT 1');
28
+ console.log(`[MySQL] Connection test successful for ${connection.name}`);
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ console.error(`[MySQL] Connection test failed:`, error.message);
33
+ throw new Error(`MySQL connection failed: ${error.message}`);
34
+ }
35
+ finally {
36
+ if (conn)
37
+ await conn.end();
38
+ }
14
39
  }
15
40
  async getTables(connection) {
16
- return [];
41
+ console.log(`[MySQL] getTables called for ${connection.name}`);
42
+ let conn = null;
43
+ try {
44
+ conn = await promise_1.default.createConnection({
45
+ host: connection.host,
46
+ port: connection.port || 3306,
47
+ user: connection.user,
48
+ password: connection.password || '',
49
+ database: connection.database
50
+ });
51
+ const [rows] = await conn.execute(`
52
+ SELECT
53
+ TABLE_NAME as name,
54
+ TABLE_ROWS as rowCount
55
+ FROM information_schema.TABLES
56
+ WHERE TABLE_SCHEMA = ?
57
+ ORDER BY TABLE_NAME
58
+ `, [connection.database]);
59
+ return rows.map(row => ({
60
+ name: row.name,
61
+ type: 'table',
62
+ rowCount: row.rowCount
63
+ }));
64
+ }
65
+ catch (error) {
66
+ console.error(`[MySQL] getTables failed:`, error.message);
67
+ throw new Error(`Failed to fetch tables: ${error.message}`);
68
+ }
69
+ finally {
70
+ if (conn)
71
+ await conn.end();
72
+ }
17
73
  }
18
74
  async getData(connection, tableName, page, limit) {
19
- return {
20
- columns: [],
21
- rows: [],
22
- pagination: { page, limit, totalRows: 0, totalPages: 0, startIdx: 0, endIdx: 0 }
23
- };
75
+ console.log(`[MySQL] getData called for ${connection.name}, table: ${tableName}, page: ${page}`);
76
+ let conn = null;
77
+ try {
78
+ conn = await promise_1.default.createConnection({
79
+ host: connection.host,
80
+ port: connection.port || 3306,
81
+ user: connection.user,
82
+ password: connection.password || '',
83
+ database: connection.database
84
+ });
85
+ // Get total row count
86
+ const [countResult] = await conn.execute(`SELECT COUNT(*) as total FROM \`${tableName}\``);
87
+ const totalRows = countResult[0]?.total || 0;
88
+ // Get paginated data
89
+ const offset = (page - 1) * limit;
90
+ const [rows] = await conn.execute(`SELECT * FROM \`${tableName}\` LIMIT ? OFFSET ?`, [limit, offset]);
91
+ const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
92
+ const totalPages = Math.ceil(totalRows / limit) || 1;
93
+ return {
94
+ columns,
95
+ rows: rows,
96
+ pagination: {
97
+ page,
98
+ limit,
99
+ totalRows,
100
+ totalPages,
101
+ startIdx: offset + 1,
102
+ endIdx: offset + rows.length
103
+ }
104
+ };
105
+ }
106
+ catch (error) {
107
+ console.error(`[MySQL] getData failed:`, error.message);
108
+ throw new Error(`Failed to fetch data: ${error.message}`);
109
+ }
110
+ finally {
111
+ if (conn)
112
+ await conn.end();
113
+ }
114
+ }
115
+ async getSchema(connection, tableName) {
116
+ console.log(`[MySQL] getSchema called for ${connection.name}, table: ${tableName}`);
117
+ let conn = null;
118
+ try {
119
+ conn = await promise_1.default.createConnection({
120
+ host: connection.host,
121
+ port: connection.port || 3306,
122
+ user: connection.user,
123
+ password: connection.password || '',
124
+ database: connection.database
125
+ });
126
+ const [rows] = await conn.execute(`
127
+ SELECT
128
+ COLUMN_NAME as name,
129
+ DATA_TYPE as type,
130
+ IS_NULLABLE as nullable
131
+ FROM information_schema.COLUMNS
132
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
133
+ ORDER BY ORDINAL_POSITION
134
+ `, [connection.database, tableName]);
135
+ return rows.map(row => ({
136
+ name: row.name,
137
+ type: row.type,
138
+ nullable: row.nullable === 'YES'
139
+ }));
140
+ }
141
+ catch (error) {
142
+ console.error(`[MySQL] getSchema failed:`, error.message);
143
+ throw new Error(`Failed to fetch schema: ${error.message}`);
144
+ }
145
+ finally {
146
+ if (conn)
147
+ await conn.end();
148
+ }
24
149
  }
25
150
  getSnippet(connection, lang) {
26
- return `# MySQL snippet generation not implemented yet`;
151
+ // Generate env var names
152
+ const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
153
+ if (lang === 'python') {
154
+ return `import os
155
+ import pymysql
156
+ import pandas as pd
157
+
158
+ # Connection: ${connection.name}
159
+ # Type: mysql
160
+ # Credentials loaded from environment variables (set in .env file)
161
+ connection = pymysql.connect(
162
+ host=os.environ["${prefix}_HOST"],
163
+ port=int(os.environ.get("${prefix}_PORT", "3306")),
164
+ user=os.environ["${prefix}_USER"],
165
+ password=os.environ["${prefix}_PASSWORD"],
166
+ database=os.environ["${prefix}_DATABASE"]
167
+ )
168
+
169
+ try:
170
+ # Example: Fetch data from a table
171
+ query = "SELECT * FROM your_table LIMIT 100"
172
+ df = pd.read_sql(query, connection)
173
+ print(f"Successfully loaded {len(df)} rows from ${connection.name}")
174
+ print(df.head())
175
+ finally:
176
+ connection.close()
177
+ `;
178
+ }
179
+ return `# Language ${lang} not supported for MySQL connector yet.`;
27
180
  }
28
181
  }
29
182
  exports.MySQLConnector = MySQLConnector;
@@ -2,8 +2,10 @@ import { Connector, Table, TableData } from '../index';
2
2
  import { Connection } from '../../connections';
3
3
  export declare class PostgreSQLConnector implements Connector {
4
4
  type: string;
5
+ private createClient;
5
6
  test(connection: Connection): Promise<boolean>;
6
7
  getTables(connection: Connection): Promise<Table[]>;
7
8
  getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
9
+ getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
8
10
  getSnippet(connection: Connection, lang: string): string;
9
11
  }
@@ -1,28 +1,160 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PostgreSQLConnector = void 0;
4
+ const pg_1 = require("pg");
4
5
  class PostgreSQLConnector {
5
6
  constructor() {
6
7
  this.type = 'postgresql';
7
8
  }
9
+ createClient(connection) {
10
+ return new pg_1.Client({
11
+ host: connection.host,
12
+ port: connection.port || 5432,
13
+ user: connection.user,
14
+ password: connection.password || '',
15
+ database: connection.database,
16
+ connectionTimeoutMillis: 10000
17
+ });
18
+ }
8
19
  async test(connection) {
20
+ console.log(`[PostgreSQL] Testing connection for ${connection.name} (Host: ${connection.host})`);
9
21
  if (!connection.host || !connection.user || !connection.database) {
10
22
  throw new Error('Host, user, and database are required');
11
23
  }
12
- return true;
24
+ const client = this.createClient(connection);
25
+ try {
26
+ await client.connect();
27
+ await client.query('SELECT NOW()');
28
+ console.log(`[PostgreSQL] Connection test successful for ${connection.name}`);
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ console.error(`[PostgreSQL] Connection test failed:`, error.message);
33
+ throw new Error(`PostgreSQL connection failed: ${error.message}`);
34
+ }
35
+ finally {
36
+ await client.end();
37
+ }
13
38
  }
14
39
  async getTables(connection) {
15
- return [];
40
+ console.log(`[PostgreSQL] getTables called for ${connection.name}`);
41
+ const client = this.createClient(connection);
42
+ try {
43
+ await client.connect();
44
+ const result = await client.query(`
45
+ SELECT
46
+ t.table_name as name,
47
+ (SELECT reltuples::bigint FROM pg_class WHERE relname = t.table_name) as row_count
48
+ FROM information_schema.tables t
49
+ WHERE t.table_schema = 'public'
50
+ AND t.table_type = 'BASE TABLE'
51
+ ORDER BY t.table_name
52
+ `);
53
+ return result.rows.map(row => ({
54
+ name: row.name,
55
+ type: 'table',
56
+ rowCount: row.row_count
57
+ }));
58
+ }
59
+ catch (error) {
60
+ console.error(`[PostgreSQL] getTables failed:`, error.message);
61
+ throw new Error(`Failed to fetch tables: ${error.message}`);
62
+ }
63
+ finally {
64
+ await client.end();
65
+ }
16
66
  }
17
67
  async getData(connection, tableName, page, limit) {
18
- return {
19
- columns: [],
20
- rows: [],
21
- pagination: { page, limit, totalRows: 0, totalPages: 0, startIdx: 0, endIdx: 0 }
22
- };
68
+ console.log(`[PostgreSQL] getData called for ${connection.name}, table: ${tableName}, page: ${page}`);
69
+ const client = this.createClient(connection);
70
+ try {
71
+ await client.connect();
72
+ // Get total row count
73
+ const countResult = await client.query(`SELECT COUNT(*) as total FROM "${tableName}"`);
74
+ const totalRows = parseInt(countResult.rows[0]?.total || '0', 10);
75
+ // Get paginated data
76
+ const offset = (page - 1) * limit;
77
+ const dataResult = await client.query(`SELECT * FROM "${tableName}" LIMIT $1 OFFSET $2`, [limit, offset]);
78
+ const columns = dataResult.fields.map(f => f.name);
79
+ const totalPages = Math.ceil(totalRows / limit) || 1;
80
+ return {
81
+ columns,
82
+ rows: dataResult.rows,
83
+ pagination: {
84
+ page,
85
+ limit,
86
+ totalRows,
87
+ totalPages,
88
+ startIdx: offset + 1,
89
+ endIdx: offset + dataResult.rows.length
90
+ }
91
+ };
92
+ }
93
+ catch (error) {
94
+ console.error(`[PostgreSQL] getData failed:`, error.message);
95
+ throw new Error(`Failed to fetch data: ${error.message}`);
96
+ }
97
+ finally {
98
+ await client.end();
99
+ }
100
+ }
101
+ async getSchema(connection, tableName) {
102
+ console.log(`[PostgreSQL] getSchema called for ${connection.name}, table: ${tableName}`);
103
+ const client = this.createClient(connection);
104
+ try {
105
+ await client.connect();
106
+ const result = await client.query(`
107
+ SELECT
108
+ column_name as name,
109
+ data_type as type,
110
+ is_nullable as nullable
111
+ FROM information_schema.columns
112
+ WHERE table_schema = 'public' AND table_name = $1
113
+ ORDER BY ordinal_position
114
+ `, [tableName]);
115
+ return result.rows.map(row => ({
116
+ name: row.name,
117
+ type: row.type,
118
+ nullable: row.nullable === 'YES'
119
+ }));
120
+ }
121
+ catch (error) {
122
+ console.error(`[PostgreSQL] getSchema failed:`, error.message);
123
+ throw new Error(`Failed to fetch schema: ${error.message}`);
124
+ }
125
+ finally {
126
+ await client.end();
127
+ }
23
128
  }
24
129
  getSnippet(connection, lang) {
25
- return `# PostgreSQL snippet generation not implemented yet`;
130
+ const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
131
+ if (lang === 'python') {
132
+ return `import os
133
+ import psycopg2
134
+ import pandas as pd
135
+
136
+ # Connection: ${connection.name}
137
+ # Type: postgresql
138
+ # Credentials loaded from environment variables (set in .env file)
139
+ connection = psycopg2.connect(
140
+ host=os.environ["${prefix}_HOST"],
141
+ port=int(os.environ.get("${prefix}_PORT", "5432")),
142
+ user=os.environ["${prefix}_USER"],
143
+ password=os.environ["${prefix}_PASSWORD"],
144
+ dbname=os.environ["${prefix}_DATABASE"]
145
+ )
146
+
147
+ try:
148
+ # Example: Fetch data from a table
149
+ query = "SELECT * FROM your_table LIMIT 100"
150
+ df = pd.read_sql(query, connection)
151
+ print(f"Successfully loaded {len(df)} rows from ${connection.name}")
152
+ print(df.head())
153
+ finally:
154
+ connection.close()
155
+ `;
156
+ }
157
+ return `# Language ${lang} not supported for PostgreSQL connector yet.`;
26
158
  }
27
159
  }
28
160
  exports.PostgreSQLConnector = PostgreSQLConnector;
@@ -6,5 +6,6 @@ export declare class ShopifyConnector implements Connector {
6
6
  getTables(connection: Connection): Promise<Table[]>;
7
7
  private fetchTotalCount;
8
8
  getData(connection: Connection, tableName: string, page: number, limit: number): Promise<TableData>;
9
+ getSchema(connection: Connection, tableName: string): Promise<import('../index').ColumnInfo[]>;
9
10
  getSnippet(connection: Connection, lang: string): string;
10
11
  }
@@ -133,17 +133,56 @@ class ShopifyConnector {
133
133
  throw new Error(`Failed to fetch data: ${error.message}`);
134
134
  }
135
135
  }
136
+ async getSchema(connection, tableName) {
137
+ // Shopify schemas are predefined - return common fields for each table type
138
+ const schemas = {
139
+ orders: [
140
+ { name: 'id', type: 'integer', nullable: false },
141
+ { name: 'email', type: 'string', nullable: true },
142
+ { name: 'created_at', type: 'datetime', nullable: false },
143
+ { name: 'total_price', type: 'decimal', nullable: false },
144
+ { name: 'currency', type: 'string', nullable: false },
145
+ { name: 'financial_status', type: 'string', nullable: true },
146
+ { name: 'fulfillment_status', type: 'string', nullable: true },
147
+ { name: 'customer', type: 'json', nullable: true },
148
+ { name: 'line_items', type: 'json', nullable: false }
149
+ ],
150
+ products: [
151
+ { name: 'id', type: 'integer', nullable: false },
152
+ { name: 'title', type: 'string', nullable: false },
153
+ { name: 'body_html', type: 'string', nullable: true },
154
+ { name: 'vendor', type: 'string', nullable: true },
155
+ { name: 'product_type', type: 'string', nullable: true },
156
+ { name: 'created_at', type: 'datetime', nullable: false },
157
+ { name: 'handle', type: 'string', nullable: false },
158
+ { name: 'status', type: 'string', nullable: false },
159
+ { name: 'variants', type: 'json', nullable: false }
160
+ ],
161
+ customers: [
162
+ { name: 'id', type: 'integer', nullable: false },
163
+ { name: 'email', type: 'string', nullable: true },
164
+ { name: 'first_name', type: 'string', nullable: true },
165
+ { name: 'last_name', type: 'string', nullable: true },
166
+ { name: 'created_at', type: 'datetime', nullable: false },
167
+ { name: 'orders_count', type: 'integer', nullable: false },
168
+ { name: 'total_spent', type: 'decimal', nullable: false },
169
+ { name: 'addresses', type: 'json', nullable: true }
170
+ ]
171
+ };
172
+ return schemas[tableName] || [];
173
+ }
136
174
  getSnippet(connection, lang) {
137
- const storeUrl = connection.store?.replace(/\/$/, '') || 'your-shop.myshopify.com';
138
- const apiKey = connection.apiKey || 'shpat_...';
175
+ const prefix = connection.name.toUpperCase().replace(/[^A-Z0-9]/g, '_');
139
176
  if (lang === 'python') {
140
- return `import requests
177
+ return `import os
178
+ import requests
141
179
  import pandas as pd
142
180
 
143
181
  # Connection: ${connection.name}
144
182
  # Type: shopify
145
- shop_url = "${storeUrl}"
146
- access_token = "${apiKey}"
183
+ # Credentials loaded from environment variables (set in .env file)
184
+ shop_url = os.environ["${prefix}_STORE"]
185
+ access_token = os.environ["${prefix}_API_KEY"]
147
186
  api_version = "2024-04"
148
187
 
149
188
  def fetch_shopify_data(endpoint):
package/dist/index.js CHANGED
@@ -118,4 +118,11 @@ program
118
118
  const { snippet } = await Promise.resolve().then(() => __importStar(require('./commands/snippet')));
119
119
  await snippet(name, options.lang);
120
120
  });
121
+ program
122
+ .command('schema <connection> [table]')
123
+ .description('Show table schema (columns) for a connection')
124
+ .action(async (connection, table) => {
125
+ const { schema } = await Promise.resolve().then(() => __importStar(require('./commands/schema')));
126
+ await schema(connection, table);
127
+ });
121
128
  program.parse();
@@ -45,7 +45,9 @@ router.post('/', (req, res) => {
45
45
  res.status(400).json({ success: false, error: 'Name and type are required' });
46
46
  return;
47
47
  }
48
+ // Save to encrypted config AND export to .env
48
49
  (0, connections_1.saveConnection)(connection);
50
+ (0, connections_1.writeToEnvFile)(connection);
49
51
  res.json({ success: true, message: 'Connection saved' });
50
52
  }
51
53
  catch (error) {
package/dist/server.js CHANGED
@@ -45,6 +45,8 @@ const mysql_1 = require("./connectors/mysql");
45
45
  const postgresql_1 = require("./connectors/postgresql");
46
46
  const shopify_1 = require("./connectors/shopify");
47
47
  const cloud_1 = require("./connectors/cloud");
48
+ const custom_1 = require("./connectors/custom");
49
+ const additional_1 = require("./connectors/additional");
48
50
  const connections_1 = __importDefault(require("./routes/connections"));
49
51
  const app = (0, express_1.default)();
50
52
  app.use(express_1.default.json());
@@ -60,6 +62,12 @@ connectors_1.ConnectorRegistry.register(new postgresql_1.PostgreSQLConnector());
60
62
  connectors_1.ConnectorRegistry.register(new shopify_1.ShopifyConnector());
61
63
  connectors_1.ConnectorRegistry.register(new cloud_1.BigQueryConnector());
62
64
  connectors_1.ConnectorRegistry.register(new cloud_1.SnowflakeConnector());
65
+ connectors_1.ConnectorRegistry.register(new custom_1.CustomConnector());
66
+ connectors_1.ConnectorRegistry.register(new additional_1.SQLServerConnector());
67
+ connectors_1.ConnectorRegistry.register(new additional_1.RedshiftConnector());
68
+ connectors_1.ConnectorRegistry.register(new additional_1.DatabricksConnector());
69
+ connectors_1.ConnectorRegistry.register(new additional_1.ClickHouseConnector());
70
+ connectors_1.ConnectorRegistry.register(new additional_1.MongoDBConnector());
63
71
  // Serve static files from ui directory
64
72
  const uiPath = path.join(__dirname, '..', 'ui');
65
73
  app.use(express_1.default.static(uiPath));
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "crushdataai",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "CLI to install CrushData AI data analyst skill for AI coding assistants",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "crushdataai": "./dist/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc",
10
+ "build": "tsc && cd ui-react && npm install && npm run build",
11
11
  "dev": "ts-node src/index.ts",
12
12
  "prepublishOnly": "npm run build"
13
13
  },
@@ -27,14 +27,19 @@
27
27
  "@types/fs-extra": "^11.0.4",
28
28
  "@types/node": "^20.10.0",
29
29
  "@types/papaparse": "^5.5.2",
30
+ "@types/pg": "^8.16.0",
30
31
  "typescript": "^5.3.0"
31
32
  },
32
33
  "dependencies": {
34
+ "@google-cloud/bigquery": "^8.1.1",
33
35
  "commander": "^11.1.0",
34
36
  "express": "^4.18.2",
35
37
  "fs-extra": "^11.2.0",
38
+ "mysql2": "^3.16.0",
36
39
  "open": "^10.0.0",
37
- "papaparse": "^5.5.3"
40
+ "papaparse": "^5.5.3",
41
+ "pg": "^8.16.3",
42
+ "snowflake-sdk": "^2.3.3"
38
43
  },
39
44
  "engines": {
40
45
  "node": ">=18.0.0"
@@ -48,4 +53,4 @@
48
53
  "type": "git",
49
54
  "url": "git+https://github.com/SankaiAI/crushdataai-agent.git"
50
55
  }
51
- }
56
+ }