@hasna/microservices 0.0.9 → 0.0.11

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 (100) hide show
  1. package/bin/index.js +236 -36
  2. package/bin/mcp.js +153 -4
  3. package/dist/index.js +120 -3
  4. package/microservices/microservice-analytics/package.json +27 -0
  5. package/microservices/microservice-analytics/src/cli/index.ts +373 -0
  6. package/microservices/microservice-analytics/src/db/analytics.ts +564 -0
  7. package/microservices/microservice-analytics/src/db/database.ts +93 -0
  8. package/microservices/microservice-analytics/src/db/migrations.ts +50 -0
  9. package/microservices/microservice-analytics/src/index.ts +37 -0
  10. package/microservices/microservice-analytics/src/mcp/index.ts +334 -0
  11. package/microservices/microservice-assets/package.json +27 -0
  12. package/microservices/microservice-assets/src/cli/index.ts +375 -0
  13. package/microservices/microservice-assets/src/db/assets.ts +370 -0
  14. package/microservices/microservice-assets/src/db/database.ts +93 -0
  15. package/microservices/microservice-assets/src/db/migrations.ts +51 -0
  16. package/microservices/microservice-assets/src/index.ts +32 -0
  17. package/microservices/microservice-assets/src/mcp/index.ts +346 -0
  18. package/microservices/microservice-compliance/package.json +27 -0
  19. package/microservices/microservice-compliance/src/cli/index.ts +467 -0
  20. package/microservices/microservice-compliance/src/db/compliance.ts +633 -0
  21. package/microservices/microservice-compliance/src/db/database.ts +93 -0
  22. package/microservices/microservice-compliance/src/db/migrations.ts +63 -0
  23. package/microservices/microservice-compliance/src/index.ts +46 -0
  24. package/microservices/microservice-compliance/src/mcp/index.ts +438 -0
  25. package/microservices/microservice-habits/package.json +27 -0
  26. package/microservices/microservice-habits/src/cli/index.ts +315 -0
  27. package/microservices/microservice-habits/src/db/database.ts +93 -0
  28. package/microservices/microservice-habits/src/db/habits.ts +451 -0
  29. package/microservices/microservice-habits/src/db/migrations.ts +46 -0
  30. package/microservices/microservice-habits/src/index.ts +31 -0
  31. package/microservices/microservice-habits/src/mcp/index.ts +313 -0
  32. package/microservices/microservice-health/package.json +27 -0
  33. package/microservices/microservice-health/src/cli/index.ts +484 -0
  34. package/microservices/microservice-health/src/db/database.ts +93 -0
  35. package/microservices/microservice-health/src/db/health.ts +708 -0
  36. package/microservices/microservice-health/src/db/migrations.ts +70 -0
  37. package/microservices/microservice-health/src/index.ts +63 -0
  38. package/microservices/microservice-health/src/mcp/index.ts +437 -0
  39. package/microservices/microservice-leads/package.json +27 -0
  40. package/microservices/microservice-leads/src/cli/index.ts +596 -0
  41. package/microservices/microservice-leads/src/db/database.ts +93 -0
  42. package/microservices/microservice-leads/src/db/leads.ts +520 -0
  43. package/microservices/microservice-leads/src/db/lists.ts +151 -0
  44. package/microservices/microservice-leads/src/db/migrations.ts +93 -0
  45. package/microservices/microservice-leads/src/index.ts +65 -0
  46. package/microservices/microservice-leads/src/lib/enrichment.ts +202 -0
  47. package/microservices/microservice-leads/src/lib/scoring.ts +134 -0
  48. package/microservices/microservice-leads/src/mcp/index.ts +533 -0
  49. package/microservices/microservice-notifications/package.json +27 -0
  50. package/microservices/microservice-notifications/src/cli/index.ts +349 -0
  51. package/microservices/microservice-notifications/src/db/database.ts +93 -0
  52. package/microservices/microservice-notifications/src/db/migrations.ts +62 -0
  53. package/microservices/microservice-notifications/src/db/notifications.ts +509 -0
  54. package/microservices/microservice-notifications/src/index.ts +41 -0
  55. package/microservices/microservice-notifications/src/mcp/index.ts +422 -0
  56. package/microservices/microservice-products/package.json +27 -0
  57. package/microservices/microservice-products/src/cli/index.ts +416 -0
  58. package/microservices/microservice-products/src/db/categories.ts +154 -0
  59. package/microservices/microservice-products/src/db/database.ts +93 -0
  60. package/microservices/microservice-products/src/db/migrations.ts +58 -0
  61. package/microservices/microservice-products/src/db/pricing-tiers.ts +66 -0
  62. package/microservices/microservice-products/src/db/products.ts +452 -0
  63. package/microservices/microservice-products/src/index.ts +53 -0
  64. package/microservices/microservice-products/src/mcp/index.ts +453 -0
  65. package/microservices/microservice-projects/package.json +27 -0
  66. package/microservices/microservice-projects/src/cli/index.ts +480 -0
  67. package/microservices/microservice-projects/src/db/database.ts +93 -0
  68. package/microservices/microservice-projects/src/db/migrations.ts +65 -0
  69. package/microservices/microservice-projects/src/db/projects.ts +715 -0
  70. package/microservices/microservice-projects/src/index.ts +57 -0
  71. package/microservices/microservice-projects/src/mcp/index.ts +501 -0
  72. package/microservices/microservice-proposals/package.json +27 -0
  73. package/microservices/microservice-proposals/src/cli/index.ts +400 -0
  74. package/microservices/microservice-proposals/src/db/database.ts +93 -0
  75. package/microservices/microservice-proposals/src/db/migrations.ts +52 -0
  76. package/microservices/microservice-proposals/src/db/proposals.ts +532 -0
  77. package/microservices/microservice-proposals/src/index.ts +37 -0
  78. package/microservices/microservice-proposals/src/mcp/index.ts +375 -0
  79. package/microservices/microservice-reading/package.json +27 -0
  80. package/microservices/microservice-reading/src/cli/index.ts +464 -0
  81. package/microservices/microservice-reading/src/db/database.ts +93 -0
  82. package/microservices/microservice-reading/src/db/migrations.ts +59 -0
  83. package/microservices/microservice-reading/src/db/reading.ts +524 -0
  84. package/microservices/microservice-reading/src/index.ts +51 -0
  85. package/microservices/microservice-reading/src/mcp/index.ts +368 -0
  86. package/microservices/microservice-travel/package.json +27 -0
  87. package/microservices/microservice-travel/src/cli/index.ts +505 -0
  88. package/microservices/microservice-travel/src/db/database.ts +93 -0
  89. package/microservices/microservice-travel/src/db/migrations.ts +77 -0
  90. package/microservices/microservice-travel/src/db/travel.ts +802 -0
  91. package/microservices/microservice-travel/src/index.ts +60 -0
  92. package/microservices/microservice-travel/src/mcp/index.ts +495 -0
  93. package/microservices/microservice-wiki/package.json +27 -0
  94. package/microservices/microservice-wiki/src/cli/index.ts +345 -0
  95. package/microservices/microservice-wiki/src/db/database.ts +93 -0
  96. package/microservices/microservice-wiki/src/db/migrations.ts +55 -0
  97. package/microservices/microservice-wiki/src/db/wiki.ts +395 -0
  98. package/microservices/microservice-wiki/src/index.ts +32 -0
  99. package/microservices/microservice-wiki/src/mcp/index.ts +344 -0
  100. package/package.json +1 -1
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createProject,
6
+ getProject,
7
+ listProjects,
8
+ updateProject,
9
+ deleteProject,
10
+ searchProjects,
11
+ getProjectTimeline,
12
+ getBudgetVsActual,
13
+ getOverdueProjects,
14
+ getOverdueMilestones,
15
+ getProjectStats,
16
+ getMilestoneProgress,
17
+ } from "../db/projects.js";
18
+ import {
19
+ createMilestone,
20
+ listMilestones,
21
+ completeMilestone,
22
+ } from "../db/projects.js";
23
+ import {
24
+ createDeliverable,
25
+ listDeliverables,
26
+ completeDeliverable,
27
+ } from "../db/projects.js";
28
+
29
+ const program = new Command();
30
+
31
+ program
32
+ .name("microservice-projects")
33
+ .description("Project management microservice")
34
+ .version("0.0.1");
35
+
36
+ // --- Projects ---
37
+
38
+ program
39
+ .command("create")
40
+ .description("Create a new project")
41
+ .requiredOption("--name <name>", "Project name")
42
+ .option("--description <desc>", "Description")
43
+ .option("--client <client>", "Client name")
44
+ .option("--status <status>", "Status (planning|active|on_hold|completed|cancelled)")
45
+ .option("--budget <amount>", "Budget amount")
46
+ .option("--currency <currency>", "Currency (default: USD)")
47
+ .option("--start-date <date>", "Start date (ISO)")
48
+ .option("--end-date <date>", "End date (ISO)")
49
+ .option("--owner <owner>", "Owner name")
50
+ .option("--tags <tags>", "Comma-separated tags")
51
+ .option("--json", "Output as JSON", false)
52
+ .action((opts) => {
53
+ const project = createProject({
54
+ name: opts.name,
55
+ description: opts.description,
56
+ client: opts.client,
57
+ status: opts.status,
58
+ budget: opts.budget ? parseFloat(opts.budget) : undefined,
59
+ currency: opts.currency,
60
+ start_date: opts.startDate,
61
+ end_date: opts.endDate,
62
+ owner: opts.owner,
63
+ tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
64
+ });
65
+
66
+ if (opts.json) {
67
+ console.log(JSON.stringify(project, null, 2));
68
+ } else {
69
+ console.log(`Created project: ${project.name} (${project.id})`);
70
+ }
71
+ });
72
+
73
+ program
74
+ .command("list")
75
+ .description("List projects")
76
+ .option("--status <status>", "Filter by status")
77
+ .option("--client <client>", "Filter by client")
78
+ .option("--owner <owner>", "Filter by owner")
79
+ .option("--limit <n>", "Limit results")
80
+ .option("--json", "Output as JSON", false)
81
+ .action((opts) => {
82
+ const projects = listProjects({
83
+ status: opts.status,
84
+ client: opts.client,
85
+ owner: opts.owner,
86
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
87
+ });
88
+
89
+ if (opts.json) {
90
+ console.log(JSON.stringify(projects, null, 2));
91
+ } else {
92
+ if (projects.length === 0) {
93
+ console.log("No projects found.");
94
+ return;
95
+ }
96
+ for (const p of projects) {
97
+ const status = `[${p.status}]`;
98
+ const client = p.client ? ` (${p.client})` : "";
99
+ console.log(` ${p.name}${client} ${status}`);
100
+ }
101
+ console.log(`\n${projects.length} project(s)`);
102
+ }
103
+ });
104
+
105
+ program
106
+ .command("get")
107
+ .description("Get a project by ID")
108
+ .argument("<id>", "Project ID")
109
+ .option("--json", "Output as JSON", false)
110
+ .action((id, opts) => {
111
+ const project = getProject(id);
112
+ if (!project) {
113
+ console.error(`Project '${id}' not found.`);
114
+ process.exit(1);
115
+ }
116
+
117
+ if (opts.json) {
118
+ console.log(JSON.stringify(project, null, 2));
119
+ } else {
120
+ console.log(`${project.name} [${project.status}]`);
121
+ if (project.description) console.log(` Description: ${project.description}`);
122
+ if (project.client) console.log(` Client: ${project.client}`);
123
+ if (project.owner) console.log(` Owner: ${project.owner}`);
124
+ if (project.budget !== null) console.log(` Budget: ${project.currency} ${project.budget}`);
125
+ console.log(` Spent: ${project.currency} ${project.spent}`);
126
+ if (project.start_date) console.log(` Start: ${project.start_date}`);
127
+ if (project.end_date) console.log(` End: ${project.end_date}`);
128
+ if (project.tags.length) console.log(` Tags: ${project.tags.join(", ")}`);
129
+ }
130
+ });
131
+
132
+ program
133
+ .command("update")
134
+ .description("Update a project")
135
+ .argument("<id>", "Project ID")
136
+ .option("--name <name>", "Name")
137
+ .option("--description <desc>", "Description")
138
+ .option("--client <client>", "Client")
139
+ .option("--status <status>", "Status")
140
+ .option("--budget <amount>", "Budget")
141
+ .option("--spent <amount>", "Spent")
142
+ .option("--currency <currency>", "Currency")
143
+ .option("--start-date <date>", "Start date")
144
+ .option("--end-date <date>", "End date")
145
+ .option("--owner <owner>", "Owner")
146
+ .option("--tags <tags>", "Comma-separated tags")
147
+ .option("--json", "Output as JSON", false)
148
+ .action((id, opts) => {
149
+ const input: Record<string, unknown> = {};
150
+ if (opts.name !== undefined) input.name = opts.name;
151
+ if (opts.description !== undefined) input.description = opts.description;
152
+ if (opts.client !== undefined) input.client = opts.client;
153
+ if (opts.status !== undefined) input.status = opts.status;
154
+ if (opts.budget !== undefined) input.budget = parseFloat(opts.budget);
155
+ if (opts.spent !== undefined) input.spent = parseFloat(opts.spent);
156
+ if (opts.currency !== undefined) input.currency = opts.currency;
157
+ if (opts.startDate !== undefined) input.start_date = opts.startDate;
158
+ if (opts.endDate !== undefined) input.end_date = opts.endDate;
159
+ if (opts.owner !== undefined) input.owner = opts.owner;
160
+ if (opts.tags !== undefined) input.tags = opts.tags.split(",").map((t: string) => t.trim());
161
+
162
+ const project = updateProject(id, input);
163
+ if (!project) {
164
+ console.error(`Project '${id}' not found.`);
165
+ process.exit(1);
166
+ }
167
+
168
+ if (opts.json) {
169
+ console.log(JSON.stringify(project, null, 2));
170
+ } else {
171
+ console.log(`Updated: ${project.name}`);
172
+ }
173
+ });
174
+
175
+ program
176
+ .command("delete")
177
+ .description("Delete a project")
178
+ .argument("<id>", "Project ID")
179
+ .action((id) => {
180
+ const deleted = deleteProject(id);
181
+ if (deleted) {
182
+ console.log(`Deleted project ${id}`);
183
+ } else {
184
+ console.error(`Project '${id}' not found.`);
185
+ process.exit(1);
186
+ }
187
+ });
188
+
189
+ program
190
+ .command("search")
191
+ .description("Search projects")
192
+ .argument("<query>", "Search term")
193
+ .option("--json", "Output as JSON", false)
194
+ .action((query, opts) => {
195
+ const results = searchProjects(query);
196
+
197
+ if (opts.json) {
198
+ console.log(JSON.stringify(results, null, 2));
199
+ } else {
200
+ if (results.length === 0) {
201
+ console.log(`No projects matching "${query}".`);
202
+ return;
203
+ }
204
+ for (const p of results) {
205
+ console.log(` ${p.name} [${p.status}]`);
206
+ }
207
+ }
208
+ });
209
+
210
+ // --- Milestones ---
211
+
212
+ const milestoneCmd = program
213
+ .command("milestone")
214
+ .description("Milestone management");
215
+
216
+ milestoneCmd
217
+ .command("create")
218
+ .description("Create a milestone")
219
+ .requiredOption("--project <id>", "Project ID")
220
+ .requiredOption("--name <name>", "Milestone name")
221
+ .option("--description <desc>", "Description")
222
+ .option("--due-date <date>", "Due date (ISO)")
223
+ .option("--json", "Output as JSON", false)
224
+ .action((opts) => {
225
+ const milestone = createMilestone({
226
+ project_id: opts.project,
227
+ name: opts.name,
228
+ description: opts.description,
229
+ due_date: opts.dueDate,
230
+ });
231
+
232
+ if (opts.json) {
233
+ console.log(JSON.stringify(milestone, null, 2));
234
+ } else {
235
+ console.log(`Created milestone: ${milestone.name} (${milestone.id})`);
236
+ }
237
+ });
238
+
239
+ milestoneCmd
240
+ .command("list")
241
+ .description("List milestones")
242
+ .option("--project <id>", "Filter by project ID")
243
+ .option("--status <status>", "Filter by status")
244
+ .option("--json", "Output as JSON", false)
245
+ .action((opts) => {
246
+ const milestones = listMilestones({
247
+ project_id: opts.project,
248
+ status: opts.status,
249
+ });
250
+
251
+ if (opts.json) {
252
+ console.log(JSON.stringify(milestones, null, 2));
253
+ } else {
254
+ if (milestones.length === 0) {
255
+ console.log("No milestones found.");
256
+ return;
257
+ }
258
+ for (const m of milestones) {
259
+ const due = m.due_date ? ` (due: ${m.due_date})` : "";
260
+ console.log(` ${m.name} [${m.status}]${due}`);
261
+ }
262
+ console.log(`\n${milestones.length} milestone(s)`);
263
+ }
264
+ });
265
+
266
+ milestoneCmd
267
+ .command("complete")
268
+ .description("Mark a milestone as completed")
269
+ .argument("<id>", "Milestone ID")
270
+ .option("--json", "Output as JSON", false)
271
+ .action((id, opts) => {
272
+ const milestone = completeMilestone(id);
273
+ if (!milestone) {
274
+ console.error(`Milestone '${id}' not found.`);
275
+ process.exit(1);
276
+ }
277
+
278
+ if (opts.json) {
279
+ console.log(JSON.stringify(milestone, null, 2));
280
+ } else {
281
+ console.log(`Completed milestone: ${milestone.name}`);
282
+ }
283
+ });
284
+
285
+ // --- Deliverables ---
286
+
287
+ const deliverableCmd = program
288
+ .command("deliverable")
289
+ .description("Deliverable management");
290
+
291
+ deliverableCmd
292
+ .command("create")
293
+ .description("Create a deliverable")
294
+ .requiredOption("--milestone <id>", "Milestone ID")
295
+ .requiredOption("--name <name>", "Deliverable name")
296
+ .option("--description <desc>", "Description")
297
+ .option("--assignee <assignee>", "Assignee")
298
+ .option("--due-date <date>", "Due date (ISO)")
299
+ .option("--json", "Output as JSON", false)
300
+ .action((opts) => {
301
+ const deliverable = createDeliverable({
302
+ milestone_id: opts.milestone,
303
+ name: opts.name,
304
+ description: opts.description,
305
+ assignee: opts.assignee,
306
+ due_date: opts.dueDate,
307
+ });
308
+
309
+ if (opts.json) {
310
+ console.log(JSON.stringify(deliverable, null, 2));
311
+ } else {
312
+ console.log(`Created deliverable: ${deliverable.name} (${deliverable.id})`);
313
+ }
314
+ });
315
+
316
+ deliverableCmd
317
+ .command("list")
318
+ .description("List deliverables")
319
+ .option("--milestone <id>", "Filter by milestone ID")
320
+ .option("--status <status>", "Filter by status")
321
+ .option("--assignee <assignee>", "Filter by assignee")
322
+ .option("--json", "Output as JSON", false)
323
+ .action((opts) => {
324
+ const deliverables = listDeliverables({
325
+ milestone_id: opts.milestone,
326
+ status: opts.status,
327
+ assignee: opts.assignee,
328
+ });
329
+
330
+ if (opts.json) {
331
+ console.log(JSON.stringify(deliverables, null, 2));
332
+ } else {
333
+ if (deliverables.length === 0) {
334
+ console.log("No deliverables found.");
335
+ return;
336
+ }
337
+ for (const d of deliverables) {
338
+ const assignee = d.assignee ? ` -> ${d.assignee}` : "";
339
+ console.log(` ${d.name} [${d.status}]${assignee}`);
340
+ }
341
+ console.log(`\n${deliverables.length} deliverable(s)`);
342
+ }
343
+ });
344
+
345
+ deliverableCmd
346
+ .command("complete")
347
+ .description("Mark a deliverable as completed")
348
+ .argument("<id>", "Deliverable ID")
349
+ .option("--json", "Output as JSON", false)
350
+ .action((id, opts) => {
351
+ const deliverable = completeDeliverable(id);
352
+ if (!deliverable) {
353
+ console.error(`Deliverable '${id}' not found.`);
354
+ process.exit(1);
355
+ }
356
+
357
+ if (opts.json) {
358
+ console.log(JSON.stringify(deliverable, null, 2));
359
+ } else {
360
+ console.log(`Completed deliverable: ${deliverable.name}`);
361
+ }
362
+ });
363
+
364
+ // --- Advanced Commands ---
365
+
366
+ program
367
+ .command("timeline")
368
+ .description("Show project timeline with milestones and deliverables")
369
+ .argument("<project-id>", "Project ID")
370
+ .option("--json", "Output as JSON", false)
371
+ .action((projectId, opts) => {
372
+ const project = getProject(projectId);
373
+ if (!project) {
374
+ console.error(`Project '${projectId}' not found.`);
375
+ process.exit(1);
376
+ }
377
+
378
+ const timeline = getProjectTimeline(projectId);
379
+
380
+ if (opts.json) {
381
+ console.log(JSON.stringify({ project: project.name, timeline }, null, 2));
382
+ } else {
383
+ console.log(`Timeline for: ${project.name}\n`);
384
+ if (timeline.length === 0) {
385
+ console.log(" No milestones or deliverables.");
386
+ return;
387
+ }
388
+ for (const entry of timeline) {
389
+ const due = entry.due_date ? ` (due: ${entry.due_date})` : "";
390
+ if (entry.type === "milestone") {
391
+ console.log(` [M] ${entry.name} [${entry.status}]${due}`);
392
+ } else {
393
+ console.log(` [D] ${entry.name} [${entry.status}]${due}`);
394
+ }
395
+ }
396
+ }
397
+ });
398
+
399
+ program
400
+ .command("budget")
401
+ .description("Show budget vs actual for a project")
402
+ .argument("<project-id>", "Project ID")
403
+ .option("--json", "Output as JSON", false)
404
+ .action((projectId, opts) => {
405
+ const report = getBudgetVsActual(projectId);
406
+ if (!report) {
407
+ console.error(`Project '${projectId}' not found.`);
408
+ process.exit(1);
409
+ }
410
+
411
+ if (opts.json) {
412
+ console.log(JSON.stringify(report, null, 2));
413
+ } else {
414
+ console.log(`Budget Report: ${report.project_name}\n`);
415
+ if (report.budget !== null) {
416
+ console.log(` Budget: ${report.currency} ${report.budget}`);
417
+ } else {
418
+ console.log(` Budget: Not set`);
419
+ }
420
+ console.log(` Spent: ${report.currency} ${report.spent}`);
421
+ if (report.remaining !== null) {
422
+ console.log(` Remaining: ${report.currency} ${report.remaining}`);
423
+ }
424
+ if (report.utilization_pct !== null) {
425
+ console.log(` Utilization: ${report.utilization_pct}%`);
426
+ }
427
+ }
428
+ });
429
+
430
+ program
431
+ .command("overdue")
432
+ .description("Show overdue projects and milestones")
433
+ .option("--json", "Output as JSON", false)
434
+ .action((opts) => {
435
+ const projects = getOverdueProjects();
436
+ const milestones = getOverdueMilestones();
437
+
438
+ if (opts.json) {
439
+ console.log(JSON.stringify({ overdue_projects: projects, overdue_milestones: milestones }, null, 2));
440
+ } else {
441
+ console.log("Overdue Projects:");
442
+ if (projects.length === 0) {
443
+ console.log(" None");
444
+ } else {
445
+ for (const p of projects) {
446
+ console.log(` ${p.name} (end: ${p.end_date}) [${p.status}]`);
447
+ }
448
+ }
449
+ console.log("\nOverdue Milestones:");
450
+ if (milestones.length === 0) {
451
+ console.log(" None");
452
+ } else {
453
+ for (const m of milestones) {
454
+ console.log(` ${m.name} (due: ${m.due_date}) [${m.status}]`);
455
+ }
456
+ }
457
+ }
458
+ });
459
+
460
+ program
461
+ .command("stats")
462
+ .description("Show project statistics")
463
+ .option("--json", "Output as JSON", false)
464
+ .action((opts) => {
465
+ const stats = getProjectStats();
466
+
467
+ if (opts.json) {
468
+ console.log(JSON.stringify(stats, null, 2));
469
+ } else {
470
+ console.log(`Project Statistics:\n`);
471
+ console.log(` Total projects: ${stats.total}`);
472
+ for (const [status, count] of Object.entries(stats.by_status)) {
473
+ console.log(` ${status}: ${count}`);
474
+ }
475
+ console.log(` Total budget: ${stats.total_budget}`);
476
+ console.log(` Total spent: ${stats.total_spent}`);
477
+ }
478
+ });
479
+
480
+ program.parse(process.argv);
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Database connection for microservice-projects
3
+ */
4
+
5
+ import { Database } from "bun:sqlite";
6
+ import { existsSync, mkdirSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { MIGRATIONS } from "./migrations.js";
9
+
10
+ let _db: Database | null = null;
11
+
12
+ function getDbPath(): string {
13
+ // Environment variable override
14
+ if (process.env["MICROSERVICES_DIR"]) {
15
+ return join(process.env["MICROSERVICES_DIR"], "microservice-projects", "data.db");
16
+ }
17
+
18
+ // Check for .microservices in current or parent directories
19
+ let dir = resolve(process.cwd());
20
+ while (true) {
21
+ const candidate = join(dir, ".microservices", "microservice-projects", "data.db");
22
+ const msDir = join(dir, ".microservices");
23
+ if (existsSync(msDir)) return candidate;
24
+ const parent = dirname(dir);
25
+ if (parent === dir) break;
26
+ dir = parent;
27
+ }
28
+
29
+ // Global fallback
30
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || "~";
31
+ return join(home, ".microservices", "microservice-projects", "data.db");
32
+ }
33
+
34
+ function ensureDir(filePath: string): void {
35
+ const dir = dirname(resolve(filePath));
36
+ if (!existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true });
38
+ }
39
+ }
40
+
41
+ export function getDatabase(): Database {
42
+ if (_db) return _db;
43
+
44
+ const dbPath = getDbPath();
45
+ ensureDir(dbPath);
46
+
47
+ _db = new Database(dbPath);
48
+ _db.exec("PRAGMA journal_mode = WAL");
49
+ _db.exec("PRAGMA foreign_keys = ON");
50
+
51
+ // Create migrations table
52
+ _db.exec(`
53
+ CREATE TABLE IF NOT EXISTS _migrations (
54
+ id INTEGER PRIMARY KEY,
55
+ name TEXT NOT NULL,
56
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
57
+ )
58
+ `);
59
+
60
+ // Apply pending migrations
61
+ const applied = _db
62
+ .query("SELECT id FROM _migrations ORDER BY id")
63
+ .all() as { id: number }[];
64
+ const appliedIds = new Set(applied.map((r) => r.id));
65
+
66
+ for (const migration of MIGRATIONS) {
67
+ if (appliedIds.has(migration.id)) continue;
68
+
69
+ _db.exec("BEGIN");
70
+ try {
71
+ _db.exec(migration.sql);
72
+ _db.prepare("INSERT INTO _migrations (id, name) VALUES (?, ?)").run(
73
+ migration.id,
74
+ migration.name
75
+ );
76
+ _db.exec("COMMIT");
77
+ } catch (error) {
78
+ _db.exec("ROLLBACK");
79
+ throw new Error(
80
+ `Migration ${migration.id} (${migration.name}) failed: ${error instanceof Error ? error.message : String(error)}`
81
+ );
82
+ }
83
+ }
84
+
85
+ return _db;
86
+ }
87
+
88
+ export function closeDatabase(): void {
89
+ if (_db) {
90
+ _db.close();
91
+ _db = null;
92
+ }
93
+ }
@@ -0,0 +1,65 @@
1
+ export interface MigrationEntry {
2
+ id: number;
3
+ name: string;
4
+ sql: string;
5
+ }
6
+
7
+ export const MIGRATIONS: MigrationEntry[] = [
8
+ {
9
+ id: 1,
10
+ name: "initial_schema",
11
+ sql: `
12
+ CREATE TABLE IF NOT EXISTS projects (
13
+ id TEXT PRIMARY KEY,
14
+ name TEXT NOT NULL,
15
+ description TEXT,
16
+ client TEXT,
17
+ status TEXT NOT NULL DEFAULT 'planning' CHECK(status IN ('planning','active','on_hold','completed','cancelled')),
18
+ budget REAL,
19
+ spent REAL NOT NULL DEFAULT 0,
20
+ currency TEXT NOT NULL DEFAULT 'USD',
21
+ start_date TEXT,
22
+ end_date TEXT,
23
+ owner TEXT,
24
+ tags TEXT NOT NULL DEFAULT '[]',
25
+ metadata TEXT NOT NULL DEFAULT '{}',
26
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
27
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
28
+ );
29
+
30
+ CREATE TABLE IF NOT EXISTS milestones (
31
+ id TEXT PRIMARY KEY,
32
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
33
+ name TEXT NOT NULL,
34
+ description TEXT,
35
+ due_date TEXT,
36
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','in_progress','completed','missed')),
37
+ completed_at TEXT,
38
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS deliverables (
42
+ id TEXT PRIMARY KEY,
43
+ milestone_id TEXT NOT NULL REFERENCES milestones(id) ON DELETE CASCADE,
44
+ name TEXT NOT NULL,
45
+ description TEXT,
46
+ status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','in_progress','review','completed')),
47
+ assignee TEXT,
48
+ due_date TEXT,
49
+ completed_at TEXT,
50
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
51
+ );
52
+
53
+ CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name);
54
+ CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
55
+ CREATE INDEX IF NOT EXISTS idx_projects_client ON projects(client);
56
+ CREATE INDEX IF NOT EXISTS idx_projects_owner ON projects(owner);
57
+ CREATE INDEX IF NOT EXISTS idx_milestones_project ON milestones(project_id);
58
+ CREATE INDEX IF NOT EXISTS idx_milestones_status ON milestones(status);
59
+ CREATE INDEX IF NOT EXISTS idx_milestones_due_date ON milestones(due_date);
60
+ CREATE INDEX IF NOT EXISTS idx_deliverables_milestone ON deliverables(milestone_id);
61
+ CREATE INDEX IF NOT EXISTS idx_deliverables_status ON deliverables(status);
62
+ CREATE INDEX IF NOT EXISTS idx_deliverables_assignee ON deliverables(assignee);
63
+ `,
64
+ },
65
+ ];