swarm-tickets 1.0.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/ticket-cli.js ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require('readline');
4
+ const fs = require('fs').promises;
5
+ const path = require('path');
6
+
7
+ const TICKETS_FILE = path.join(__dirname, 'tickets.json');
8
+
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout
12
+ });
13
+
14
+ function question(prompt) {
15
+ return new Promise((resolve) => {
16
+ rl.question(prompt, resolve);
17
+ });
18
+ }
19
+
20
+ async function readTickets() {
21
+ try {
22
+ const data = await fs.readFile(TICKETS_FILE, 'utf8');
23
+ return JSON.parse(data);
24
+ } catch {
25
+ return { tickets: [] };
26
+ }
27
+ }
28
+
29
+ async function writeTickets(data) {
30
+ await fs.writeFile(TICKETS_FILE, JSON.stringify(data, null, 2));
31
+ }
32
+
33
+ async function createTicket() {
34
+ console.log('\nšŸŽ« Create New Ticket\n');
35
+
36
+ const route = await question('Route/Webpage: ');
37
+
38
+ console.log('\nPaste F12 Console Errors (press Enter twice when done):');
39
+ let f12Errors = '';
40
+ let line;
41
+ while ((line = await question('')) !== '') {
42
+ f12Errors += line + '\n';
43
+ }
44
+
45
+ console.log('\nPaste Server Console Errors (press Enter twice when done):');
46
+ let serverErrors = '';
47
+ while ((line = await question('')) !== '') {
48
+ serverErrors += line + '\n';
49
+ }
50
+
51
+ const description = await question('\nDescription (optional): ');
52
+
53
+ const ticket = {
54
+ id: 'TKT-' + Date.now(),
55
+ route: route,
56
+ f12Errors: f12Errors.trim(),
57
+ serverErrors: serverErrors.trim(),
58
+ description: description,
59
+ status: 'open',
60
+ priority: null,
61
+ relatedTickets: [],
62
+ swarmActions: [],
63
+ namespace: null,
64
+ createdAt: new Date().toISOString(),
65
+ updatedAt: new Date().toISOString()
66
+ };
67
+
68
+ const data = await readTickets();
69
+ data.tickets.push(ticket);
70
+ await writeTickets(data);
71
+
72
+ console.log(`\nāœ… Ticket created: ${ticket.id}\n`);
73
+ rl.close();
74
+ }
75
+
76
+ async function listTickets() {
77
+ const data = await readTickets();
78
+
79
+ if (data.tickets.length === 0) {
80
+ console.log('No tickets found.');
81
+ rl.close();
82
+ return;
83
+ }
84
+
85
+ console.log(`\nšŸ“‹ Total Tickets: ${data.tickets.length}\n`);
86
+
87
+ data.tickets
88
+ .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
89
+ .forEach(ticket => {
90
+ console.log(`${ticket.id} - ${ticket.status.toUpperCase()}`);
91
+ console.log(` Route: ${ticket.route}`);
92
+ if (ticket.priority) console.log(` Priority: ${ticket.priority}`);
93
+ console.log(` Created: ${new Date(ticket.createdAt).toLocaleString()}`);
94
+ console.log('');
95
+ });
96
+
97
+ rl.close();
98
+ }
99
+
100
+ async function main() {
101
+ const args = process.argv.slice(2);
102
+
103
+ if (args[0] === 'list' || args[0] === 'ls') {
104
+ await listTickets();
105
+ } else if (args[0] === 'create' || args[0] === 'new' || args.length === 0) {
106
+ await createTicket();
107
+ } else {
108
+ console.log('Usage:');
109
+ console.log(' node ticket-cli.js - Create a new ticket');
110
+ console.log(' node ticket-cli.js create - Create a new ticket');
111
+ console.log(' node ticket-cli.js list - List all tickets');
112
+ rl.close();
113
+ }
114
+ }
115
+
116
+ main().catch(error => {
117
+ console.error('Error:', error);
118
+ rl.close();
119
+ process.exit(1);
120
+ });
@@ -0,0 +1,327 @@
1
+ #!/usr/bin/env node
2
+
3
+ const express = require('express');
4
+ const fs = require('fs').promises;
5
+ const path = require('path');
6
+ const cors = require('cors');
7
+
8
+ const app = express();
9
+ const PORT = process.env.PORT || 3456;
10
+
11
+ // When installed as package, look for tickets.json in the project root (cwd)
12
+ // When run standalone, look in the same directory as the script
13
+ const projectRoot = process.cwd();
14
+ const TICKETS_FILE = path.join(projectRoot, 'tickets.json');
15
+ const BACKUP_DIR = path.join(projectRoot, 'ticket-backups');
16
+
17
+ // Serve static files from the package directory (where ticket-tracker.html is)
18
+ const packageDir = __dirname;
19
+
20
+ app.use(cors());
21
+ app.use(express.json());
22
+ app.use(express.static(projectRoot)); // Serve from project root first
23
+ app.use(express.static(packageDir)); // Fall back to package directory
24
+
25
+ // Auto-find available port
26
+ async function findAvailablePort(startPort) {
27
+ const net = require('net');
28
+
29
+ return new Promise((resolve) => {
30
+ const server = net.createServer();
31
+ server.unref();
32
+ server.on('error', () => {
33
+ resolve(findAvailablePort(startPort + 1));
34
+ });
35
+ server.listen(startPort, () => {
36
+ const { port } = server.address();
37
+ server.close(() => {
38
+ resolve(port);
39
+ });
40
+ });
41
+ });
42
+ }
43
+
44
+ // Initialize tickets file if it doesn't exist
45
+ async function initTicketsFile() {
46
+ try {
47
+ await fs.access(TICKETS_FILE);
48
+ } catch {
49
+ await fs.writeFile(TICKETS_FILE, JSON.stringify({ tickets: [] }, null, 2));
50
+ }
51
+
52
+ // Create backup directory
53
+ try {
54
+ await fs.mkdir(BACKUP_DIR, { recursive: true });
55
+ } catch (error) {
56
+ // Directory already exists, that's fine
57
+ }
58
+ }
59
+
60
+ // Read tickets
61
+ async function readTickets() {
62
+ try {
63
+ const data = await fs.readFile(TICKETS_FILE, 'utf8');
64
+ return JSON.parse(data);
65
+ } catch (error) {
66
+ return { tickets: [] };
67
+ }
68
+ }
69
+
70
+ // Create backup before writing
71
+ async function createBackup() {
72
+ try {
73
+ // Check if tickets.json exists
74
+ await fs.access(TICKETS_FILE);
75
+
76
+ // Create timestamped backup in folder
77
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
78
+ const timestampedBackup = path.join(BACKUP_DIR, `tickets-${timestamp}.json`);
79
+ await fs.copyFile(TICKETS_FILE, timestampedBackup);
80
+
81
+ // Rotate backups - keep last 10
82
+ const files = await fs.readdir(BACKUP_DIR);
83
+ const backupFiles = files
84
+ .filter(f => f.startsWith('tickets-') && f.endsWith('.json'))
85
+ .sort()
86
+ .reverse();
87
+
88
+ if (backupFiles.length > 10) {
89
+ for (let i = 10; i < backupFiles.length; i++) {
90
+ await fs.unlink(path.join(BACKUP_DIR, backupFiles[i]));
91
+ }
92
+ }
93
+
94
+ console.log(`āœ… Backup created: ${timestampedBackup}`);
95
+ } catch (error) {
96
+ console.warn('āš ļø Backup failed:', error.message);
97
+ }
98
+ }
99
+
100
+ // Write tickets (with automatic backup)
101
+ async function writeTickets(data) {
102
+ await createBackup();
103
+ await fs.writeFile(TICKETS_FILE, JSON.stringify(data, null, 2));
104
+ }
105
+
106
+ // GET all tickets
107
+ app.get('/api/tickets', async (req, res) => {
108
+ try {
109
+ const data = await readTickets();
110
+ res.json(data.tickets);
111
+ } catch (error) {
112
+ res.status(500).json({ error: error.message });
113
+ }
114
+ });
115
+
116
+ // GET single ticket
117
+ app.get('/api/tickets/:id', async (req, res) => {
118
+ try {
119
+ const data = await readTickets();
120
+ const ticket = data.tickets.find(t => t.id === req.params.id);
121
+ if (ticket) {
122
+ res.json(ticket);
123
+ } else {
124
+ res.status(404).json({ error: 'Ticket not found' });
125
+ }
126
+ } catch (error) {
127
+ res.status(500).json({ error: error.message });
128
+ }
129
+ });
130
+
131
+ // POST new ticket
132
+ app.post('/api/tickets', async (req, res) => {
133
+ try {
134
+ const data = await readTickets();
135
+
136
+ const ticket = {
137
+ id: 'TKT-' + Date.now(),
138
+ route: req.body.route,
139
+ f12Errors: req.body.f12Errors || '',
140
+ serverErrors: req.body.serverErrors || '',
141
+ description: req.body.description || '',
142
+ status: req.body.status || 'open',
143
+ priority: req.body.priority || null,
144
+ relatedTickets: req.body.relatedTickets || [],
145
+ swarmActions: req.body.swarmActions || [],
146
+ namespace: req.body.namespace || null,
147
+ createdAt: new Date().toISOString(),
148
+ updatedAt: new Date().toISOString()
149
+ };
150
+
151
+ data.tickets.push(ticket);
152
+ await writeTickets(data);
153
+
154
+ res.status(201).json(ticket);
155
+ } catch (error) {
156
+ res.status(500).json({ error: error.message });
157
+ }
158
+ });
159
+
160
+ // PATCH update ticket
161
+ app.patch('/api/tickets/:id', async (req, res) => {
162
+ try {
163
+ const data = await readTickets();
164
+ const ticketIndex = data.tickets.findIndex(t => t.id === req.params.id);
165
+
166
+ if (ticketIndex === -1) {
167
+ return res.status(404).json({ error: 'Ticket not found' });
168
+ }
169
+
170
+ // Update allowed fields
171
+ const allowedFields = [
172
+ 'status', 'priority', 'relatedTickets', 'swarmActions',
173
+ 'namespace', 'description', 'f12Errors', 'serverErrors'
174
+ ];
175
+
176
+ allowedFields.forEach(field => {
177
+ if (req.body[field] !== undefined) {
178
+ data.tickets[ticketIndex][field] = req.body[field];
179
+ }
180
+ });
181
+
182
+ data.tickets[ticketIndex].updatedAt = new Date().toISOString();
183
+
184
+ await writeTickets(data);
185
+ res.json(data.tickets[ticketIndex]);
186
+ } catch (error) {
187
+ res.status(500).json({ error: error.message });
188
+ }
189
+ });
190
+
191
+ // POST add swarm action to ticket
192
+ app.post('/api/tickets/:id/swarm-action', async (req, res) => {
193
+ try {
194
+ const data = await readTickets();
195
+ const ticket = data.tickets.find(t => t.id === req.params.id);
196
+
197
+ if (!ticket) {
198
+ return res.status(404).json({ error: 'Ticket not found' });
199
+ }
200
+
201
+ const action = {
202
+ timestamp: new Date().toISOString(),
203
+ action: req.body.action,
204
+ result: req.body.result || null
205
+ };
206
+
207
+ ticket.swarmActions.push(action);
208
+ ticket.updatedAt = new Date().toISOString();
209
+
210
+ await writeTickets(data);
211
+ res.json(ticket);
212
+ } catch (error) {
213
+ res.status(500).json({ error: error.message });
214
+ }
215
+ });
216
+
217
+ // POST analyze ticket with swarm (placeholder for swarm integration)
218
+ app.post('/api/tickets/:id/analyze', async (req, res) => {
219
+ try {
220
+ const data = await readTickets();
221
+ const ticket = data.tickets.find(t => t.id === req.params.id);
222
+
223
+ if (!ticket) {
224
+ return res.status(404).json({ error: 'Ticket not found' });
225
+ }
226
+
227
+ // This is where you'd integrate with your swarm to analyze the ticket
228
+ // For now, we'll do a simple analysis
229
+
230
+ // Determine priority based on errors
231
+ let priority = 'low';
232
+ if (ticket.f12Errors.toLowerCase().includes('error') ||
233
+ ticket.serverErrors.toLowerCase().includes('error')) {
234
+ priority = 'medium';
235
+ }
236
+ if (ticket.f12Errors.toLowerCase().includes('uncaught') ||
237
+ ticket.serverErrors.toLowerCase().includes('fatal') ||
238
+ ticket.serverErrors.toLowerCase().includes('crash')) {
239
+ priority = 'high';
240
+ }
241
+ if (ticket.route.includes('auth') || ticket.route.includes('payment')) {
242
+ priority = 'critical';
243
+ }
244
+
245
+ // Find related tickets (simple route-based matching)
246
+ const relatedTickets = data.tickets
247
+ .filter(t => t.id !== ticket.id && t.route === ticket.route)
248
+ .map(t => t.id)
249
+ .slice(0, 3);
250
+
251
+ ticket.priority = priority;
252
+ ticket.relatedTickets = relatedTickets;
253
+ ticket.swarmActions.push({
254
+ timestamp: new Date().toISOString(),
255
+ action: 'auto-analysis',
256
+ result: `Priority set to ${priority}, found ${relatedTickets.length} related tickets`
257
+ });
258
+ ticket.updatedAt = new Date().toISOString();
259
+
260
+ await writeTickets(data);
261
+ res.json(ticket);
262
+ } catch (error) {
263
+ res.status(500).json({ error: error.message });
264
+ }
265
+ });
266
+
267
+ // DELETE ticket
268
+ app.delete('/api/tickets/:id', async (req, res) => {
269
+ try {
270
+ const data = await readTickets();
271
+ const ticketIndex = data.tickets.findIndex(t => t.id === req.params.id);
272
+
273
+ if (ticketIndex === -1) {
274
+ return res.status(404).json({ error: 'Ticket not found' });
275
+ }
276
+
277
+ data.tickets.splice(ticketIndex, 1);
278
+ await writeTickets(data);
279
+
280
+ res.json({ message: 'Ticket deleted' });
281
+ } catch (error) {
282
+ res.status(500).json({ error: error.message });
283
+ }
284
+ });
285
+
286
+ // GET stats
287
+ app.get('/api/stats', async (req, res) => {
288
+ try {
289
+ const data = await readTickets();
290
+ const tickets = data.tickets;
291
+
292
+ const stats = {
293
+ total: tickets.length,
294
+ byStatus: {
295
+ open: tickets.filter(t => t.status === 'open').length,
296
+ inProgress: tickets.filter(t => t.status === 'in-progress').length,
297
+ fixed: tickets.filter(t => t.status === 'fixed').length,
298
+ closed: tickets.filter(t => t.status === 'closed').length
299
+ },
300
+ byPriority: {
301
+ critical: tickets.filter(t => t.priority === 'critical').length,
302
+ high: tickets.filter(t => t.priority === 'high').length,
303
+ medium: tickets.filter(t => t.priority === 'medium').length,
304
+ low: tickets.filter(t => t.priority === 'low').length
305
+ }
306
+ };
307
+
308
+ res.json(stats);
309
+ } catch (error) {
310
+ res.status(500).json({ error: error.message });
311
+ }
312
+ });
313
+
314
+ // Initialize and start server
315
+ initTicketsFile().then(async () => {
316
+ const availablePort = await findAvailablePort(PORT);
317
+
318
+ app.listen(availablePort, () => {
319
+ console.log(`šŸŽ« Ticket Tracker API running on http://localhost:${availablePort}`);
320
+ console.log(`šŸ“Š Open http://localhost:${availablePort}/ticket-tracker.html to view the UI`);
321
+ console.log(`šŸ“ Tickets stored at: ${TICKETS_FILE}`);
322
+
323
+ if (availablePort !== PORT) {
324
+ console.log(`āš ļø Port ${PORT} was busy, using port ${availablePort} instead`);
325
+ }
326
+ });
327
+ });