@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.
- package/bin/index.js +236 -36
- package/bin/mcp.js +153 -4
- package/dist/index.js +120 -3
- package/microservices/microservice-analytics/package.json +27 -0
- package/microservices/microservice-analytics/src/cli/index.ts +373 -0
- package/microservices/microservice-analytics/src/db/analytics.ts +564 -0
- package/microservices/microservice-analytics/src/db/database.ts +93 -0
- package/microservices/microservice-analytics/src/db/migrations.ts +50 -0
- package/microservices/microservice-analytics/src/index.ts +37 -0
- package/microservices/microservice-analytics/src/mcp/index.ts +334 -0
- package/microservices/microservice-assets/package.json +27 -0
- package/microservices/microservice-assets/src/cli/index.ts +375 -0
- package/microservices/microservice-assets/src/db/assets.ts +370 -0
- package/microservices/microservice-assets/src/db/database.ts +93 -0
- package/microservices/microservice-assets/src/db/migrations.ts +51 -0
- package/microservices/microservice-assets/src/index.ts +32 -0
- package/microservices/microservice-assets/src/mcp/index.ts +346 -0
- package/microservices/microservice-compliance/package.json +27 -0
- package/microservices/microservice-compliance/src/cli/index.ts +467 -0
- package/microservices/microservice-compliance/src/db/compliance.ts +633 -0
- package/microservices/microservice-compliance/src/db/database.ts +93 -0
- package/microservices/microservice-compliance/src/db/migrations.ts +63 -0
- package/microservices/microservice-compliance/src/index.ts +46 -0
- package/microservices/microservice-compliance/src/mcp/index.ts +438 -0
- package/microservices/microservice-habits/package.json +27 -0
- package/microservices/microservice-habits/src/cli/index.ts +315 -0
- package/microservices/microservice-habits/src/db/database.ts +93 -0
- package/microservices/microservice-habits/src/db/habits.ts +451 -0
- package/microservices/microservice-habits/src/db/migrations.ts +46 -0
- package/microservices/microservice-habits/src/index.ts +31 -0
- package/microservices/microservice-habits/src/mcp/index.ts +313 -0
- package/microservices/microservice-health/package.json +27 -0
- package/microservices/microservice-health/src/cli/index.ts +484 -0
- package/microservices/microservice-health/src/db/database.ts +93 -0
- package/microservices/microservice-health/src/db/health.ts +708 -0
- package/microservices/microservice-health/src/db/migrations.ts +70 -0
- package/microservices/microservice-health/src/index.ts +63 -0
- package/microservices/microservice-health/src/mcp/index.ts +437 -0
- package/microservices/microservice-leads/package.json +27 -0
- package/microservices/microservice-leads/src/cli/index.ts +596 -0
- package/microservices/microservice-leads/src/db/database.ts +93 -0
- package/microservices/microservice-leads/src/db/leads.ts +520 -0
- package/microservices/microservice-leads/src/db/lists.ts +151 -0
- package/microservices/microservice-leads/src/db/migrations.ts +93 -0
- package/microservices/microservice-leads/src/index.ts +65 -0
- package/microservices/microservice-leads/src/lib/enrichment.ts +202 -0
- package/microservices/microservice-leads/src/lib/scoring.ts +134 -0
- package/microservices/microservice-leads/src/mcp/index.ts +533 -0
- package/microservices/microservice-notifications/package.json +27 -0
- package/microservices/microservice-notifications/src/cli/index.ts +349 -0
- package/microservices/microservice-notifications/src/db/database.ts +93 -0
- package/microservices/microservice-notifications/src/db/migrations.ts +62 -0
- package/microservices/microservice-notifications/src/db/notifications.ts +509 -0
- package/microservices/microservice-notifications/src/index.ts +41 -0
- package/microservices/microservice-notifications/src/mcp/index.ts +422 -0
- package/microservices/microservice-products/package.json +27 -0
- package/microservices/microservice-products/src/cli/index.ts +416 -0
- package/microservices/microservice-products/src/db/categories.ts +154 -0
- package/microservices/microservice-products/src/db/database.ts +93 -0
- package/microservices/microservice-products/src/db/migrations.ts +58 -0
- package/microservices/microservice-products/src/db/pricing-tiers.ts +66 -0
- package/microservices/microservice-products/src/db/products.ts +452 -0
- package/microservices/microservice-products/src/index.ts +53 -0
- package/microservices/microservice-products/src/mcp/index.ts +453 -0
- package/microservices/microservice-projects/package.json +27 -0
- package/microservices/microservice-projects/src/cli/index.ts +480 -0
- package/microservices/microservice-projects/src/db/database.ts +93 -0
- package/microservices/microservice-projects/src/db/migrations.ts +65 -0
- package/microservices/microservice-projects/src/db/projects.ts +715 -0
- package/microservices/microservice-projects/src/index.ts +57 -0
- package/microservices/microservice-projects/src/mcp/index.ts +501 -0
- package/microservices/microservice-proposals/package.json +27 -0
- package/microservices/microservice-proposals/src/cli/index.ts +400 -0
- package/microservices/microservice-proposals/src/db/database.ts +93 -0
- package/microservices/microservice-proposals/src/db/migrations.ts +52 -0
- package/microservices/microservice-proposals/src/db/proposals.ts +532 -0
- package/microservices/microservice-proposals/src/index.ts +37 -0
- package/microservices/microservice-proposals/src/mcp/index.ts +375 -0
- package/microservices/microservice-reading/package.json +27 -0
- package/microservices/microservice-reading/src/cli/index.ts +464 -0
- package/microservices/microservice-reading/src/db/database.ts +93 -0
- package/microservices/microservice-reading/src/db/migrations.ts +59 -0
- package/microservices/microservice-reading/src/db/reading.ts +524 -0
- package/microservices/microservice-reading/src/index.ts +51 -0
- package/microservices/microservice-reading/src/mcp/index.ts +368 -0
- package/microservices/microservice-travel/package.json +27 -0
- package/microservices/microservice-travel/src/cli/index.ts +505 -0
- package/microservices/microservice-travel/src/db/database.ts +93 -0
- package/microservices/microservice-travel/src/db/migrations.ts +77 -0
- package/microservices/microservice-travel/src/db/travel.ts +802 -0
- package/microservices/microservice-travel/src/index.ts +60 -0
- package/microservices/microservice-travel/src/mcp/index.ts +495 -0
- package/microservices/microservice-wiki/package.json +27 -0
- package/microservices/microservice-wiki/src/cli/index.ts +345 -0
- package/microservices/microservice-wiki/src/db/database.ts +93 -0
- package/microservices/microservice-wiki/src/db/migrations.ts +55 -0
- package/microservices/microservice-wiki/src/db/wiki.ts +395 -0
- package/microservices/microservice-wiki/src/index.ts +32 -0
- package/microservices/microservice-wiki/src/mcp/index.ts +344 -0
- 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
|
+
];
|