crushdataai 1.2.13 → 1.2.14

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.
@@ -114,6 +114,29 @@ print(f"Missing values:\n{df.isnull().sum()}")
114
114
 
115
115
  ---
116
116
 
117
+ ## Step 5: Generate Dashboard Output
118
+
119
+ After analysis, save for visualization:
120
+ ```python
121
+ from pathlib import Path
122
+ import json
123
+ from datetime import datetime
124
+
125
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
126
+
127
+ dashboard = {
128
+ "metadata": {"title": "Analysis Dashboard", "generatedAt": datetime.now().isoformat()},
129
+ "kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%", "trendDirection": "up"}],
130
+ "charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
131
+ }
132
+
133
+ with open("reports/dashboards/dashboard.json", "w") as f:
134
+ json.dump(dashboard, f, indent=2)
135
+ ```
136
+ Tell user: "Run `npx crushdataai dashboard` to view."
137
+
138
+ ---
139
+
117
140
  ## Pre-Delivery Checklist
118
141
 
119
142
  - [ ] Business question answered
@@ -170,6 +170,54 @@ FROM table;
170
170
 
171
171
  ---
172
172
 
173
+ ## Step 5: Generate Dashboard Output
174
+
175
+ **After analysis, save results for dashboard visualization:**
176
+
177
+ 1. **Search for best chart type:**
178
+ ```bash
179
+ python3 .claude/skills/data-analyst/scripts/search.py "<metric description>" --domain chart
180
+ ```
181
+
182
+ 2. **Create dashboard JSON:**
183
+ ```python
184
+ from pathlib import Path
185
+ import json
186
+ from datetime import datetime
187
+
188
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
189
+
190
+ dashboard = {
191
+ "metadata": {
192
+ "title": "Analysis Dashboard",
193
+ "generatedAt": datetime.now().isoformat(),
194
+ "dataRange": f"{start_date} to {end_date}"
195
+ },
196
+ "kpis": [
197
+ {"id": "kpi-1", "label": "Total Revenue", "value": "$50,000", "trend": "+12%", "trendDirection": "up"}
198
+ ],
199
+ "charts": [
200
+ {
201
+ "id": "chart-1",
202
+ "type": "line", # line, bar, pie, area, scatter, donut, table
203
+ "title": "Monthly Trend",
204
+ "data": {
205
+ "labels": ["Jan", "Feb", "Mar"],
206
+ "datasets": [{"label": "Revenue", "values": [10000, 15000, 25000]}]
207
+ }
208
+ }
209
+ ]
210
+ }
211
+
212
+ with open("reports/dashboards/dashboard.json", "w") as f:
213
+ json.dump(dashboard, f, indent=2)
214
+ ```
215
+
216
+ 3. **Tell user:**
217
+ > "Dashboard ready! Run `npx crushdataai dashboard` to view."
218
+
219
+ ---
220
+
173
221
  ## Pre-Delivery Checklist
174
222
 
175
223
  Before presenting final results:
@@ -60,6 +60,26 @@ Report findings and ask user for confirmation.
60
60
  - Compare to benchmarks
61
61
  - Present for user validation
62
62
 
63
+ ### 5. Generate Dashboard Output
64
+ After analysis, save for visualization:
65
+ ```python
66
+ from pathlib import Path
67
+ import json
68
+ from datetime import datetime
69
+
70
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
71
+
72
+ dashboard = {
73
+ "metadata": {"title": "Analysis", "generatedAt": datetime.now().isoformat()},
74
+ "kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
75
+ "charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
76
+ }
77
+
78
+ with open("reports/dashboards/dashboard.json", "w") as f:
79
+ json.dump(dashboard, f, indent=2)
80
+ ```
81
+ Tell user: "Run `npx crushdataai dashboard` to view."
82
+
63
83
  ---
64
84
 
65
85
  ## Quick Reference
@@ -58,3 +58,21 @@ Report and confirm before proceeding.
58
58
  - Check totals
59
59
  - Compare benchmarks
60
60
  - User validation
61
+
62
+ ### 5. Generate Dashboard Output
63
+ After analysis, save for visualization:
64
+ ```python
65
+ from pathlib import Path
66
+ import json
67
+ from datetime import datetime
68
+
69
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
70
+ dashboard = {
71
+ "metadata": {"title": "Analysis", "generatedAt": datetime.now().isoformat()},
72
+ "kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
73
+ "charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
74
+ }
75
+ with open("reports/dashboards/dashboard.json", "w") as f:
76
+ json.dump(dashboard, f, indent=2)
77
+ ```
78
+ Tell user: "Run `npx crushdataai dashboard` to view."
@@ -60,3 +60,21 @@ Ask: "Does this match your expectation?"
60
60
  - Compare to benchmarks
61
61
  - Document assumptions
62
62
  - Present for user confirmation
63
+
64
+ ### 5. Generate Dashboard Output
65
+ After analysis, save for visualization:
66
+ ```python
67
+ from pathlib import Path
68
+ import json
69
+ from datetime import datetime
70
+
71
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
72
+ dashboard = {
73
+ "metadata": {"title": "Analysis", "generatedAt": datetime.now().isoformat()},
74
+ "kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
75
+ "charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
76
+ }
77
+ with open("reports/dashboards/dashboard.json", "w") as f:
78
+ json.dump(dashboard, f, indent=2)
79
+ ```
80
+ Tell user: "Run `npx crushdataai dashboard` to view."
@@ -58,3 +58,21 @@ print(f"Shape: {df.shape}, Dates: {df['date'].min()} to {df['date'].max()}")
58
58
  - Check totals are reasonable
59
59
  - Compare to benchmarks
60
60
  - Present for user validation
61
+
62
+ ### 5. Generate Dashboard Output
63
+ After analysis, save for visualization:
64
+ ```python
65
+ from pathlib import Path
66
+ import json
67
+ from datetime import datetime
68
+
69
+ Path("reports/dashboards").mkdir(parents=True, exist_ok=True)
70
+ dashboard = {
71
+ "metadata": {"title": "Analysis", "generatedAt": datetime.now().isoformat()},
72
+ "kpis": [{"id": "kpi-1", "label": "Total", "value": "$50K", "trend": "+12%"}],
73
+ "charts": [{"id": "chart-1", "type": "line", "title": "Trend", "data": {"labels": ["Jan","Feb"], "datasets": [{"label": "Revenue", "values": [10000,20000]}]}}]
74
+ }
75
+ with open("reports/dashboards/dashboard.json", "w") as f:
76
+ json.dump(dashboard, f, indent=2)
77
+ ```
78
+ Tell user: "Run `npx crushdataai dashboard` to view."
@@ -0,0 +1 @@
1
+ export declare function startDashboardServer(port: number): Promise<void>;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.startDashboardServer = startDashboardServer;
40
+ const express_1 = __importDefault(require("express"));
41
+ const path = __importStar(require("path"));
42
+ const open_1 = __importDefault(require("open"));
43
+ const dashboard_1 = __importDefault(require("./routes/dashboard"));
44
+ async function startDashboardServer(port) {
45
+ const app = (0, express_1.default)();
46
+ // Serve static files from the built dashboard UI
47
+ const uiPath = path.join(__dirname, '..', 'ui-dashboard-dist');
48
+ app.use(express_1.default.static(uiPath));
49
+ // API routes
50
+ app.use('/api', dashboard_1.default);
51
+ // SPA fallback - serve index.html for client-side routing
52
+ app.get('*', (_req, res) => {
53
+ res.sendFile(path.join(uiPath, 'index.html'));
54
+ });
55
+ return new Promise((resolve, reject) => {
56
+ const server = app.listen(port, async () => {
57
+ console.log(`\n📊 Dashboard UI running at http://localhost:${port}\n`);
58
+ console.log(' Looking for dashboards in: reports/dashboards/');
59
+ console.log(' Press Ctrl+C to stop\n');
60
+ // Open browser
61
+ try {
62
+ await (0, open_1.default)(`http://localhost:${port}`);
63
+ }
64
+ catch (err) {
65
+ // Ignore open errors
66
+ }
67
+ resolve();
68
+ });
69
+ server.on('error', (err) => {
70
+ if (err.code === 'EADDRINUSE') {
71
+ reject(new Error(`Port ${port} is already in use. Try a different port with --port`));
72
+ }
73
+ else {
74
+ reject(err);
75
+ }
76
+ });
77
+ });
78
+ }
package/dist/index.js CHANGED
@@ -37,12 +37,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
37
37
  const commander_1 = require("commander");
38
38
  const commands_1 = require("./commands");
39
39
  const server_1 = require("./server");
40
+ const dashboard_server_1 = require("./dashboard-server");
40
41
  const connections_1 = require("./connections");
41
42
  const program = new commander_1.Command();
42
43
  program
43
44
  .name('crushdataai')
44
45
  .description('CLI to install CrushData AI data analyst skill for AI coding assistants')
45
- .version('1.2.1');
46
+ .version('1.2.14');
46
47
  program
47
48
  .command('init')
48
49
  .description('Initialize CrushData AI skill in current project')
@@ -125,4 +126,19 @@ program
125
126
  const { schema } = await Promise.resolve().then(() => __importStar(require('./commands/schema')));
126
127
  await schema(connection, table);
127
128
  });
129
+ program
130
+ .command('dashboard')
131
+ .description('Open the dashboard visualization UI')
132
+ .option('-p, --port <port>', 'Server port', '3002')
133
+ .action(async (options) => {
134
+ const port = parseInt(options.port);
135
+ console.log('\n📊 Starting CrushData AI Dashboard...\n');
136
+ try {
137
+ await (0, dashboard_server_1.startDashboardServer)(port);
138
+ }
139
+ catch (error) {
140
+ console.error(`❌ Error: ${error.message}`);
141
+ process.exit(1);
142
+ }
143
+ });
128
144
  program.parse();
@@ -0,0 +1,2 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const express_1 = require("express");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const router = (0, express_1.Router)();
40
+ // Get reports/dashboards directory path relative to current working directory
41
+ function getDashboardsDir() {
42
+ return path.join(process.cwd(), 'reports', 'dashboards');
43
+ }
44
+ // List all available dashboards
45
+ router.get('/dashboards', (_req, res) => {
46
+ try {
47
+ const dashboardsDir = getDashboardsDir();
48
+ // Check if directory exists
49
+ if (!fs.existsSync(dashboardsDir)) {
50
+ return res.json([]);
51
+ }
52
+ // Read all JSON files in the directory
53
+ const files = fs.readdirSync(dashboardsDir)
54
+ .filter(file => file.endsWith('.json'));
55
+ const dashboards = files.map(file => {
56
+ const filePath = path.join(dashboardsDir, file);
57
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
58
+ // Use filename without extension as ID
59
+ const id = path.basename(file, '.json');
60
+ return {
61
+ id,
62
+ title: content.metadata?.title || id,
63
+ generatedAt: content.metadata?.generatedAt || '',
64
+ chartCount: content.charts?.length || 0,
65
+ kpiCount: content.kpis?.length || 0
66
+ };
67
+ });
68
+ res.json(dashboards);
69
+ }
70
+ catch (error) {
71
+ console.error('Error listing dashboards:', error);
72
+ res.status(500).json({ error: 'Failed to list dashboards' });
73
+ }
74
+ });
75
+ // Get a specific dashboard by ID
76
+ router.get('/dashboards/:id', (req, res) => {
77
+ try {
78
+ const { id } = req.params;
79
+ const dashboardsDir = getDashboardsDir();
80
+ const filePath = path.join(dashboardsDir, `${id}.json`);
81
+ if (!fs.existsSync(filePath)) {
82
+ return res.status(404).json({ error: 'Dashboard not found' });
83
+ }
84
+ const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
85
+ res.json(content);
86
+ }
87
+ catch (error) {
88
+ console.error('Error reading dashboard:', error);
89
+ res.status(500).json({ error: 'Failed to read dashboard' });
90
+ }
91
+ });
92
+ // Refresh a chart's data (placeholder for now - would re-run query)
93
+ router.post('/charts/:id/refresh', (req, res) => {
94
+ try {
95
+ const { id } = req.params;
96
+ // For now, just return a success message
97
+ // In the future, this would:
98
+ // 1. Find the chart in a dashboard
99
+ // 2. Re-run its query against the data source
100
+ // 3. Update the chart data
101
+ // 4. Save the updated dashboard
102
+ res.json({
103
+ message: 'Refresh not yet implemented',
104
+ chartId: id,
105
+ lastRefreshed: new Date().toISOString()
106
+ });
107
+ }
108
+ catch (error) {
109
+ console.error('Error refreshing chart:', error);
110
+ res.status(500).json({ error: 'Failed to refresh chart' });
111
+ }
112
+ });
113
+ exports.default = router;
@@ -0,0 +1,48 @@
1
+ export interface DashboardMetadata {
2
+ title: string;
3
+ generatedAt: string;
4
+ dataRange?: string;
5
+ recordCount?: number;
6
+ }
7
+ export interface KPI {
8
+ id: string;
9
+ label: string;
10
+ value: string;
11
+ trend?: string;
12
+ trendDirection?: 'up' | 'down' | 'neutral';
13
+ }
14
+ export interface ChartQuery {
15
+ type: 'sql' | 'api';
16
+ connection?: string;
17
+ sql?: string;
18
+ }
19
+ export interface ChartDataset {
20
+ label: string;
21
+ values: number[];
22
+ color?: string;
23
+ }
24
+ export interface ChartData {
25
+ labels: string[];
26
+ datasets: ChartDataset[];
27
+ }
28
+ export interface Chart {
29
+ id: string;
30
+ type: string;
31
+ title: string;
32
+ description?: string;
33
+ query?: ChartQuery;
34
+ data: ChartData;
35
+ lastRefreshed?: string;
36
+ }
37
+ export interface Dashboard {
38
+ metadata: DashboardMetadata;
39
+ kpis: KPI[];
40
+ charts: Chart[];
41
+ }
42
+ export interface DashboardListItem {
43
+ id: string;
44
+ title: string;
45
+ generatedAt: string;
46
+ chartCount: number;
47
+ kpiCount: number;
48
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Dashboard types for the API layer
3
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "crushdataai",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
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 && cd ui-react && npm install && npm run build",
10
+ "build": "tsc && cd ui-react && npm install && npm run build && cd ../ui-dashboard && npm install && npm run build",
11
11
  "dev": "ts-node src/index.ts",
12
12
  "prepublishOnly": "npm run build"
13
13
  },
@@ -47,7 +47,8 @@
47
47
  "files": [
48
48
  "dist",
49
49
  "assets",
50
- "ui"
50
+ "ui",
51
+ "ui-dashboard-dist"
51
52
  ],
52
53
  "repository": {
53
54
  "type": "git",