atlasia-ghost 1.0.2 → 1.1.1

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 (64) hide show
  1. package/AGENTS.md +8 -1
  2. package/README.md +33 -0
  3. package/core/analytics/api-server.js +253 -0
  4. package/core/analytics/examples/populate-sample-data.js +103 -0
  5. package/core/analytics/index.js +54 -1
  6. package/core/analytics/start-api-server.js +40 -0
  7. package/core/analytics/start-telemetry-server.js +50 -0
  8. package/core/analytics/telemetry-ws-server.js +319 -0
  9. package/core/dependency-resolver.js +462 -0
  10. package/core/dev-mode.js +633 -23
  11. package/core/examples/hot-reload-example.js +116 -0
  12. package/core/examples/v0-extension-migration-manifest.json +39 -0
  13. package/core/examples/v0-extension-migration-sample.js +149 -0
  14. package/core/extension-dependency-resolver.js +645 -0
  15. package/core/extension-deps-commands.js +134 -0
  16. package/core/extension-loader.js +290 -17
  17. package/core/extension-migrator.js +1506 -0
  18. package/core/hot-reload-websocket.js +478 -0
  19. package/core/hot-reload.js +395 -0
  20. package/core/index.js +4 -0
  21. package/core/manifest-schema.json +48 -8
  22. package/core/marketplace-backend/README.md +413 -0
  23. package/core/marketplace-backend/admin-dashboard.js +55 -0
  24. package/core/marketplace-backend/auth-manager.js +212 -0
  25. package/core/marketplace-backend/cli.js +57 -0
  26. package/core/marketplace-backend/client-example.js +219 -0
  27. package/core/marketplace-backend/database.js +506 -0
  28. package/core/marketplace-backend/download-tracker.js +65 -0
  29. package/core/marketplace-backend/index.js +19 -0
  30. package/core/marketplace-backend/manifest-validator.js +223 -0
  31. package/core/marketplace-backend/package.json +29 -0
  32. package/core/marketplace-backend/rate-limiter.js +88 -0
  33. package/core/marketplace-backend/security-scanner.js +280 -0
  34. package/core/marketplace-backend/server.js +428 -0
  35. package/core/marketplace.js +25 -3
  36. package/core/pipeline/audit.js +19 -5
  37. package/core/pipeline/execute.js +4 -2
  38. package/core/pipeline/index.js +6 -2
  39. package/core/pipeline/intercept.js +1 -1
  40. package/core/registry-client.js +254 -0
  41. package/core/runtime.js +120 -0
  42. package/core/template-wizard.js +335 -151
  43. package/core/templates/api-integration.js +343 -0
  44. package/core/templates/base-template.js +45 -0
  45. package/core/templates/file-processor.js +474 -0
  46. package/core/templates/gallery.js +50 -0
  47. package/core/templates/git-workflow.js +700 -0
  48. package/core/templates/testing.js +962 -0
  49. package/core/validators/entropy-validator.js +2 -2
  50. package/docs/DEPENDENCY_RESOLUTION.md +272 -0
  51. package/docs/DEVELOPER_TOOLKIT.md +72 -0
  52. package/docs/EXTENSION_DEPENDENCIES.md +435 -0
  53. package/docs/EXTENSION_MIGRATION.md +836 -0
  54. package/docs/HOT_RELOAD.md +541 -0
  55. package/docs/HOT_RELOAD_QUICK_START.md +302 -0
  56. package/docs/QUICK_REFERENCE.md +4 -0
  57. package/docs/REGISTRY_API.md +501 -0
  58. package/docs/TEMPLATE_GALLERY.md +629 -0
  59. package/docs/templates-quick-ref.md +38 -0
  60. package/extensions/ghost-git-extension/extension.js +44 -2
  61. package/extensions/ghost-git-extension/index.js +70 -10
  62. package/extensions/ghost-git-extension/manifest.json +5 -5
  63. package/ghost.js +306 -15
  64. package/package.json +5 -1
package/AGENTS.md CHANGED
@@ -40,9 +40,15 @@
40
40
  New commands and SDK for building extensions:
41
41
 
42
42
  **CLI Commands:**
43
- - `ghost extension init <name>` - Scaffold new extension with boilerplate
43
+ - `ghost extension init` - Interactive template wizard with gallery
44
44
  - `ghost extension validate [path]` - Validate manifest and permissions
45
45
 
46
+ **Template Gallery:** `core/templates/` - Production-ready templates:
47
+ - **API Integration** - REST/GraphQL client with auth (Bearer, API Key, OAuth)
48
+ - **File Processor** - Batch operations with progress tracking and transformations
49
+ - **Git Workflow** - Commit hooks with validation (pre-commit, commit-msg, conventional commits)
50
+ - **Testing** - Vitest/Jest setup with mocking, coverage, and E2E support
51
+
46
52
  **SDK Package:** `packages/extension-sdk/` - @ghost/extension-sdk NPM package with:
47
53
  - `ExtensionSDK` class - High-level API (requestFileRead, requestNetworkCall, requestGitExec)
48
54
  - `IntentBuilder` - Build JSON-RPC intents
@@ -54,3 +60,4 @@ New commands and SDK for building extensions:
54
60
  - `extension-examples.md` - Working examples (file processor, API integration, git helper)
55
61
  - `DEVELOPER_TOOLKIT.md` - Complete toolkit guide
56
62
  - `QUICK_REFERENCE.md` - Quick reference card
63
+ - `TEMPLATE_GALLERY.md` - Template gallery guide
package/README.md CHANGED
@@ -91,6 +91,7 @@ Ghost includes a complete toolkit for building custom extensions:
91
91
 
92
92
  - **`ghost extension init <name>`** - Scaffold a new extension project with boilerplate
93
93
  - **`ghost extension validate [path]`** - Validate manifest syntax and simulate permissions
94
+ - **`ghost extension migrate [path]`** - Migrate v0.x extensions to v1.0.0 SDK
94
95
  - **`ghost extension install <path>`** - Install extension locally
95
96
  - **`ghost extension list`** - List installed extensions
96
97
  - **`ghost extension info <id>`** - Show extension details
@@ -133,6 +134,7 @@ module.exports = MyExtension;
133
134
  ### Documentation
134
135
 
135
136
  - 🛠️ [Developer Toolkit Guide](./docs/DEVELOPER_TOOLKIT.md) - Complete guide to extension development
137
+ - 🔄 [Extension Migration Guide](./docs/EXTENSION_MIGRATION.md) - Migrate v0.x to v1.0.0
136
138
  - 📖 [Extension API Reference](./docs/extension-api.md) - I/O intent schema and examples
137
139
  - 💡 [Extension Examples](./docs/extension-examples.md) - Working examples for common patterns
138
140
  - 📦 [Extension SDK Package](./packages/extension-sdk/README.md) - SDK documentation
@@ -297,6 +299,37 @@ ghost version bump --from-commits --tag --output json
297
299
 
298
300
  ### Creating Extensions
299
301
 
302
+ #### 🎨 Quick Start with Template Gallery
303
+
304
+ Ghost CLI includes a comprehensive template gallery with pre-built patterns:
305
+
306
+ ```bash
307
+ # Interactive template selector
308
+ ghost extension init
309
+
310
+ # Or use specific template
311
+ ghost extension init my-api --template api-integration
312
+ ```
313
+
314
+ **Available Templates:**
315
+ - `api-integration` - REST/GraphQL client with auth, retry, caching
316
+ - `file-processor` - Batch file operations with streaming
317
+ - `git-workflow` - Git hooks and conventional commits
318
+ - `testing` - Test infrastructure with mock RPC client
319
+ - `basic` - Simple minimal structure
320
+ - `typescript` - Type-safe development
321
+ - `advanced` - Production-ready with tests
322
+
323
+ Each template includes:
324
+ - ✅ Fully-implemented, commented code
325
+ - ✅ Complete test suite
326
+ - ✅ Comprehensive README with examples
327
+ - ✅ Best practices built-in
328
+
329
+ [📚 View Template Gallery →](./templates/README.md)
330
+
331
+ #### Manual Extension Creation
332
+
300
333
  1. Create extension directory structure:
301
334
  ```
302
335
  my-extension/
@@ -0,0 +1,253 @@
1
+ const http = require('http');
2
+ const { AnalyticsPlatform } = require('./index');
3
+
4
+ class AnalyticsAPIServer {
5
+ constructor(options = {}) {
6
+ this.options = {
7
+ port: options.port || 9876,
8
+ host: options.host || 'localhost',
9
+ ...options
10
+ };
11
+
12
+ this.analytics = new AnalyticsPlatform(options.analytics || {});
13
+ this.server = null;
14
+ }
15
+
16
+ async start() {
17
+ await this.analytics.initialize();
18
+
19
+ this.server = http.createServer((req, res) => {
20
+ this._handleRequest(req, res);
21
+ });
22
+
23
+ return new Promise((resolve, reject) => {
24
+ this.server.listen(this.options.port, this.options.host, (err) => {
25
+ if (err) {
26
+ reject(err);
27
+ } else {
28
+ console.log(`[AnalyticsAPI] Server listening on http://${this.options.host}:${this.options.port}`);
29
+ resolve();
30
+ }
31
+ });
32
+ });
33
+ }
34
+
35
+ async stop() {
36
+ await this.analytics.shutdown();
37
+
38
+ if (this.server) {
39
+ return new Promise((resolve) => {
40
+ this.server.close(() => {
41
+ console.log('[AnalyticsAPI] Server stopped');
42
+ resolve();
43
+ });
44
+ });
45
+ }
46
+ }
47
+
48
+ async _handleRequest(req, res) {
49
+ const url = new URL(req.url, `http://${req.headers.host}`);
50
+
51
+ res.setHeader('Access-Control-Allow-Origin', '*');
52
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
53
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
54
+ res.setHeader('Content-Type', 'application/json');
55
+
56
+ if (req.method === 'OPTIONS') {
57
+ res.writeHead(200);
58
+ res.end();
59
+ return;
60
+ }
61
+
62
+ try {
63
+ if (url.pathname === '/api/analytics/dashboard') {
64
+ await this._handleDashboard(req, res, url);
65
+ } else if (url.pathname.startsWith('/api/analytics/performance/')) {
66
+ await this._handlePerformanceHistory(req, res, url);
67
+ } else if (url.pathname === '/api/analytics/extensions') {
68
+ await this._handleExtensionsList(req, res);
69
+ } else if (url.pathname.startsWith('/api/analytics/extension/')) {
70
+ await this._handleExtensionDetail(req, res, url);
71
+ } else {
72
+ res.writeHead(404);
73
+ res.end(JSON.stringify({ error: 'Not found' }));
74
+ }
75
+ } catch (error) {
76
+ console.error('[AnalyticsAPI] Error handling request:', error);
77
+ res.writeHead(500);
78
+ res.end(JSON.stringify({ error: error.message }));
79
+ }
80
+ }
81
+
82
+ async _handleDashboard(req, res, url) {
83
+ const timeRange = url.searchParams.get('timeRange') || '6h';
84
+
85
+ const allMetrics = this.analytics.collector.getAllMetrics();
86
+ const costs = [];
87
+ const alerts = this.analytics.performance.getAlerts();
88
+
89
+ for (const [extensionId, metrics] of Object.entries(allMetrics)) {
90
+ const costData = this.analytics.cost.getCostsByExtension(extensionId);
91
+ if (costData) {
92
+ costs.push({
93
+ extensionId,
94
+ totalCost: costData.total || 0,
95
+ resources: costData.breakdown || {
96
+ cpu: 0,
97
+ memory: 0,
98
+ io: 0,
99
+ network: 0,
100
+ storage: 0
101
+ },
102
+ billingPeriod: costData.period || 'current'
103
+ });
104
+ }
105
+ }
106
+
107
+ const crossExtensionCalls = this.analytics.tracing.getCrossExtensionCalls();
108
+ const extensionInteractions = this.analytics.tracing.getExtensionInteractions();
109
+
110
+ const callGraph = {
111
+ nodes: [],
112
+ edges: []
113
+ };
114
+
115
+ if (crossExtensionCalls && crossExtensionCalls.length > 0) {
116
+ const nodeMap = new Map();
117
+
118
+ crossExtensionCalls.forEach(call => {
119
+ const fromKey = `${call.from.extensionId}:${call.from.operation}`;
120
+ const toKey = `${call.to.extensionId}:${call.to.operation}`;
121
+
122
+ if (!nodeMap.has(fromKey)) {
123
+ nodeMap.set(fromKey, {
124
+ extensionId: call.from.extensionId,
125
+ operation: call.from.operation,
126
+ callCount: 0,
127
+ totalDuration: 0,
128
+ avgDuration: 0
129
+ });
130
+ }
131
+
132
+ if (!nodeMap.has(toKey)) {
133
+ nodeMap.set(toKey, {
134
+ extensionId: call.to.extensionId,
135
+ operation: call.to.operation,
136
+ callCount: 0,
137
+ totalDuration: 0,
138
+ avgDuration: 0
139
+ });
140
+ }
141
+
142
+ const fromNode = nodeMap.get(fromKey);
143
+ const toNode = nodeMap.get(toKey);
144
+
145
+ fromNode.callCount += call.count;
146
+ toNode.callCount += call.count;
147
+ fromNode.totalDuration += call.totalDuration || 0;
148
+ toNode.totalDuration += call.totalDuration || 0;
149
+
150
+ callGraph.edges.push({
151
+ from: fromKey,
152
+ to: toKey,
153
+ callCount: call.count
154
+ });
155
+ });
156
+
157
+ nodeMap.forEach((node, key) => {
158
+ node.avgDuration = node.callCount > 0 ? node.totalDuration / node.callCount : 0;
159
+ callGraph.nodes.push(node);
160
+ });
161
+ }
162
+
163
+ const formattedAlerts = alerts.map(alert => ({
164
+ id: alert.id || `alert-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
165
+ extensionId: alert.extensionId,
166
+ version: alert.version || '1.0.0',
167
+ severity: alert.severity || 'medium',
168
+ metric: alert.metric,
169
+ baselineValue: alert.baselineValue,
170
+ currentValue: alert.currentValue,
171
+ percentChange: alert.percentChange,
172
+ threshold: alert.threshold,
173
+ timestamp: alert.timestamp || Date.now()
174
+ }));
175
+
176
+ const dashboardData = {
177
+ metrics: allMetrics,
178
+ costs,
179
+ alerts: formattedAlerts,
180
+ callGraph,
181
+ timestamp: Date.now()
182
+ };
183
+
184
+ res.writeHead(200);
185
+ res.end(JSON.stringify(dashboardData));
186
+ }
187
+
188
+ async _handlePerformanceHistory(req, res, url) {
189
+ const extensionId = url.pathname.split('/').pop();
190
+ const timeRange = url.searchParams.get('timeRange') || '6h';
191
+
192
+ const history = [];
193
+ const now = Date.now();
194
+ const timeRangeMs = this._parseTimeRange(timeRange);
195
+
196
+ const invocations = Array.from(this.analytics.collector.metrics.values())
197
+ .filter(m => m.extensionId === extensionId && m.timestamp > (now - timeRangeMs))
198
+ .sort((a, b) => a.timestamp - b.timestamp);
199
+
200
+ const bucketSize = Math.max(1, Math.floor(invocations.length / 50));
201
+
202
+ for (let i = 0; i < invocations.length; i += bucketSize) {
203
+ const bucket = invocations.slice(i, i + bucketSize);
204
+ const durations = bucket
205
+ .filter(inv => inv.duration !== undefined)
206
+ .map(inv => inv.duration)
207
+ .sort((a, b) => a - b);
208
+
209
+ if (durations.length > 0) {
210
+ history.push({
211
+ timestamp: bucket[0].timestamp,
212
+ p50: durations[Math.floor(durations.length * 0.5)] || 0,
213
+ p95: durations[Math.floor(durations.length * 0.95)] || 0,
214
+ p99: durations[Math.floor(durations.length * 0.99)] || 0
215
+ });
216
+ }
217
+ }
218
+
219
+ res.writeHead(200);
220
+ res.end(JSON.stringify({ history }));
221
+ }
222
+
223
+ async _handleExtensionsList(req, res) {
224
+ const allMetrics = this.analytics.collector.getAllMetrics();
225
+ const extensions = Object.keys(allMetrics).map(extensionId => ({
226
+ id: extensionId,
227
+ metrics: allMetrics[extensionId]
228
+ }));
229
+
230
+ res.writeHead(200);
231
+ res.end(JSON.stringify({ extensions }));
232
+ }
233
+
234
+ async _handleExtensionDetail(req, res, url) {
235
+ const extensionId = url.pathname.split('/').pop();
236
+ const metrics = this.analytics.getExtensionMetrics(extensionId);
237
+
238
+ res.writeHead(200);
239
+ res.end(JSON.stringify(metrics));
240
+ }
241
+
242
+ _parseTimeRange(timeRange) {
243
+ const ranges = {
244
+ '1h': 60 * 60 * 1000,
245
+ '6h': 6 * 60 * 60 * 1000,
246
+ '24h': 24 * 60 * 60 * 1000,
247
+ '7d': 7 * 24 * 60 * 60 * 1000
248
+ };
249
+ return ranges[timeRange] || ranges['6h'];
250
+ }
251
+ }
252
+
253
+ module.exports = AnalyticsAPIServer;
@@ -0,0 +1,103 @@
1
+ const { AnalyticsPlatform } = require('../index');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ async function populateSampleData() {
6
+ const analytics = new AnalyticsPlatform({
7
+ persistenceDir: path.join(os.homedir(), '.ghost', 'analytics'),
8
+ flushInterval: 60000,
9
+ retentionDays: 30
10
+ });
11
+
12
+ await analytics.initialize();
13
+
14
+ console.log('[Sample Data] Generating analytics data...');
15
+
16
+ const extensions = [
17
+ 'ghost-git-extension',
18
+ 'ghost-npm-extension',
19
+ 'ghost-docker-extension',
20
+ 'ghost-lint-extension'
21
+ ];
22
+
23
+ const operations = {
24
+ 'ghost-git-extension': ['status', 'commit', 'push', 'pull', 'branch'],
25
+ 'ghost-npm-extension': ['install', 'test', 'build', 'publish'],
26
+ 'ghost-docker-extension': ['build', 'run', 'ps', 'logs'],
27
+ 'ghost-lint-extension': ['check', 'fix', 'format']
28
+ };
29
+
30
+ for (let i = 0; i < 200; i++) {
31
+ const extensionId = extensions[Math.floor(Math.random() * extensions.length)];
32
+ const method = operations[extensionId][Math.floor(Math.random() * operations[extensionId].length)];
33
+
34
+ const context = analytics.trackExtensionInvocation(extensionId, method, {
35
+ param1: 'value1',
36
+ param2: 'value2'
37
+ });
38
+
39
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 50 + 10));
40
+
41
+ const duration = Math.random() * 200 + 10;
42
+ const success = Math.random() > 0.15;
43
+
44
+ analytics.trackResourceUsage(context, {
45
+ cpu: Math.random() * 5,
46
+ memory: Math.random() * 100 + 20,
47
+ io: Math.random() * 1024 * 10,
48
+ network: Math.random() * 1024 * 5
49
+ });
50
+
51
+ if (success) {
52
+ analytics.trackExtensionSuccess(context, { status: 'ok', data: {} });
53
+ } else {
54
+ analytics.trackExtensionFailure(context, new Error('Sample error'));
55
+ }
56
+
57
+ analytics.trackVersionMetric(extensionId, '1.0.0', {
58
+ duration,
59
+ cpu: Math.random() * 5,
60
+ memory: Math.random() * 100,
61
+ errorRate: success ? 0 : 1
62
+ });
63
+
64
+ if (Math.random() > 0.7) {
65
+ const targetExtension = extensions[Math.floor(Math.random() * extensions.length)];
66
+ if (targetExtension !== extensionId) {
67
+ const childSpanId = analytics.trackCrossExtensionCall(
68
+ context.spanId,
69
+ targetExtension,
70
+ 'delegate',
71
+ {}
72
+ );
73
+
74
+ await new Promise(resolve => setTimeout(resolve, Math.random() * 30));
75
+
76
+ analytics.tracing.endSpan(childSpanId, 'success', {});
77
+ }
78
+ }
79
+
80
+ if (i % 10 === 0) {
81
+ console.log(`[Sample Data] Generated ${i} invocations...`);
82
+ }
83
+ }
84
+
85
+ for (const extensionId of extensions) {
86
+ analytics.performance.setBaseline(extensionId, '1.0.0');
87
+
88
+ analytics.trackVersionMetric(extensionId, '1.1.0', {
89
+ duration: Math.random() * 300 + 50,
90
+ cpu: Math.random() * 8,
91
+ memory: Math.random() * 150,
92
+ errorRate: 0.2
93
+ });
94
+ }
95
+
96
+ await analytics.persist();
97
+ console.log('[Sample Data] Sample data generated successfully!');
98
+ console.log('[Sample Data] Start the API server with: node core/analytics/start-api-server.js');
99
+
100
+ await analytics.shutdown();
101
+ }
102
+
103
+ populateSampleData().catch(console.error);
@@ -4,6 +4,7 @@ const CostAttribution = require('./cost-attribution');
4
4
  const PerformanceRegression = require('./performance-regression');
5
5
  const DistributedTracing = require('./distributed-tracing');
6
6
  const RecommendationEngine = require('./recommendation-engine');
7
+ const TelemetryWebSocketServer = require('./telemetry-ws-server');
7
8
  const { EventEmitter } = require('events');
8
9
 
9
10
  class AnalyticsPlatform extends EventEmitter {
@@ -17,6 +18,14 @@ class AnalyticsPlatform extends EventEmitter {
17
18
  this.performance = new PerformanceRegression(options);
18
19
  this.tracing = new DistributedTracing(options);
19
20
  this.recommendations = new RecommendationEngine(options);
21
+
22
+ this.wsServer = null;
23
+ if (options.enableWebSocket !== false) {
24
+ this.wsServer = new TelemetryWebSocketServer({
25
+ port: options.wsPort || 9877,
26
+ host: options.wsHost || 'localhost'
27
+ });
28
+ }
20
29
 
21
30
  this._setupEventForwarding();
22
31
  }
@@ -28,6 +37,14 @@ class AnalyticsPlatform extends EventEmitter {
28
37
  await this.tracing.load();
29
38
  await this.recommendations.load();
30
39
 
40
+ if (this.wsServer) {
41
+ try {
42
+ await this.wsServer.start();
43
+ } catch (error) {
44
+ console.error('[AnalyticsPlatform] Failed to start WebSocket server:', error.message);
45
+ }
46
+ }
47
+
31
48
  this.emit('initialized');
32
49
  }
33
50
 
@@ -51,6 +68,17 @@ class AnalyticsPlatform extends EventEmitter {
51
68
 
52
69
  this.collector.recordSuccess(trackingContext.invocationId, result, duration);
53
70
  this.tracing.endSpan(trackingContext.spanId, 'success', { resultSize: JSON.stringify(result).length });
71
+
72
+ if (this.wsServer) {
73
+ this.wsServer.broadcastToExtension(trackingContext.extensionId || this._getExtensionIdFromInvocation(trackingContext.invocationId), {
74
+ type: 'invocation-completed',
75
+ extensionId: trackingContext.extensionId || this._getExtensionIdFromInvocation(trackingContext.invocationId),
76
+ invocationId: trackingContext.invocationId,
77
+ status: 'success',
78
+ duration,
79
+ timestamp: Date.now()
80
+ });
81
+ }
54
82
  }
55
83
 
56
84
  trackExtensionFailure(trackingContext, error) {
@@ -58,6 +86,18 @@ class AnalyticsPlatform extends EventEmitter {
58
86
 
59
87
  this.collector.recordFailure(trackingContext.invocationId, error, duration);
60
88
  this.tracing.endSpan(trackingContext.spanId, 'error', { error: error.message });
89
+
90
+ if (this.wsServer) {
91
+ this.wsServer.broadcastToExtension(trackingContext.extensionId || this._getExtensionIdFromInvocation(trackingContext.invocationId), {
92
+ type: 'invocation-completed',
93
+ extensionId: trackingContext.extensionId || this._getExtensionIdFromInvocation(trackingContext.invocationId),
94
+ invocationId: trackingContext.invocationId,
95
+ status: 'failure',
96
+ duration,
97
+ error: error.message,
98
+ timestamp: Date.now()
99
+ });
100
+ }
61
101
  }
62
102
 
63
103
  trackResourceUsage(trackingContext, resources) {
@@ -169,6 +209,15 @@ class AnalyticsPlatform extends EventEmitter {
169
209
  async shutdown() {
170
210
  await this.persist();
171
211
  this.collector.shutdown();
212
+
213
+ if (this.wsServer) {
214
+ try {
215
+ await this.wsServer.stop();
216
+ } catch (error) {
217
+ console.error('[AnalyticsPlatform] Failed to stop WebSocket server:', error.message);
218
+ }
219
+ }
220
+
172
221
  this.emit('shutdown');
173
222
  }
174
223
 
@@ -204,6 +253,8 @@ class AnalyticsPlatform extends EventEmitter {
204
253
  }
205
254
  }
206
255
 
256
+ const AnalyticsAPIServer = require('./api-server');
257
+
207
258
  module.exports = {
208
259
  AnalyticsPlatform,
209
260
  AnalyticsCollector,
@@ -211,5 +262,7 @@ module.exports = {
211
262
  CostAttribution,
212
263
  PerformanceRegression,
213
264
  DistributedTracing,
214
- RecommendationEngine
265
+ RecommendationEngine,
266
+ AnalyticsAPIServer,
267
+ TelemetryWebSocketServer
215
268
  };
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { AnalyticsAPIServer } = require('./index');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const server = new AnalyticsAPIServer({
8
+ port: 9876,
9
+ host: 'localhost',
10
+ analytics: {
11
+ persistenceDir: path.join(os.homedir(), '.ghost', 'analytics'),
12
+ flushInterval: 60000,
13
+ retentionDays: 30
14
+ }
15
+ });
16
+
17
+ async function main() {
18
+ try {
19
+ await server.start();
20
+ console.log('[Analytics] API Server started successfully');
21
+ console.log('[Analytics] Access dashboard at: http://localhost:9876/api/analytics/dashboard');
22
+ } catch (error) {
23
+ console.error('[Analytics] Failed to start API server:', error);
24
+ process.exit(1);
25
+ }
26
+ }
27
+
28
+ process.on('SIGINT', async () => {
29
+ console.log('\n[Analytics] Shutting down...');
30
+ await server.stop();
31
+ process.exit(0);
32
+ });
33
+
34
+ process.on('SIGTERM', async () => {
35
+ console.log('\n[Analytics] Shutting down...');
36
+ await server.stop();
37
+ process.exit(0);
38
+ });
39
+
40
+ main();
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { AnalyticsPlatform } = require('./index');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const analytics = new AnalyticsPlatform({
8
+ persistenceDir: path.join(os.homedir(), '.ghost', 'analytics'),
9
+ enableWebSocket: true,
10
+ wsPort: 9877,
11
+ wsHost: 'localhost'
12
+ });
13
+
14
+ async function startServer() {
15
+ try {
16
+ await analytics.initialize();
17
+ console.log('[TelemetryServer] Analytics Platform initialized');
18
+ console.log('[TelemetryServer] WebSocket server running on ws://localhost:9877/telemetry');
19
+ console.log('[TelemetryServer] Press Ctrl+C to stop');
20
+
21
+ analytics.on('invocation-started', (event) => {
22
+ console.log(`[Invocation] Started: ${event.extensionId} - ${event.method}`);
23
+ });
24
+
25
+ analytics.on('invocation-completed', (event) => {
26
+ console.log(`[Invocation] Completed: ${event.extensionId} - ${event.status} (${event.duration}ms)`);
27
+ });
28
+
29
+ analytics.on('regression-detected', (alert) => {
30
+ console.log(`[Alert] Regression detected: ${alert.extensionId} - ${alert.severity}`);
31
+ });
32
+ } catch (error) {
33
+ console.error('[TelemetryServer] Failed to start:', error.message);
34
+ process.exit(1);
35
+ }
36
+ }
37
+
38
+ process.on('SIGINT', async () => {
39
+ console.log('\n[TelemetryServer] Shutting down...');
40
+ await analytics.shutdown();
41
+ process.exit(0);
42
+ });
43
+
44
+ process.on('SIGTERM', async () => {
45
+ console.log('\n[TelemetryServer] Shutting down...');
46
+ await analytics.shutdown();
47
+ process.exit(0);
48
+ });
49
+
50
+ startServer();