@hasna/microservices 0.0.10 → 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 +86 -1
- package/bin/mcp.js +86 -1
- package/dist/index.js +86 -1
- 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-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,484 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
createMetric,
|
|
6
|
+
listMetrics,
|
|
7
|
+
getMetricTrend,
|
|
8
|
+
deleteMetric,
|
|
9
|
+
} from "../db/health.js";
|
|
10
|
+
import {
|
|
11
|
+
createMedication,
|
|
12
|
+
getMedication,
|
|
13
|
+
listMedications,
|
|
14
|
+
updateMedication,
|
|
15
|
+
deactivateMedication,
|
|
16
|
+
} from "../db/health.js";
|
|
17
|
+
import {
|
|
18
|
+
createAppointment,
|
|
19
|
+
listAppointments,
|
|
20
|
+
completeAppointment,
|
|
21
|
+
cancelAppointment,
|
|
22
|
+
getUpcomingAppointments,
|
|
23
|
+
} from "../db/health.js";
|
|
24
|
+
import {
|
|
25
|
+
createFitnessLog,
|
|
26
|
+
listFitnessLogs,
|
|
27
|
+
getFitnessStats,
|
|
28
|
+
} from "../db/health.js";
|
|
29
|
+
import { getHealthSummary } from "../db/health.js";
|
|
30
|
+
|
|
31
|
+
const program = new Command();
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.name("microservice-health")
|
|
35
|
+
.description("Health tracking microservice")
|
|
36
|
+
.version("0.0.1");
|
|
37
|
+
|
|
38
|
+
// --- Metrics ---
|
|
39
|
+
|
|
40
|
+
const metricCmd = program
|
|
41
|
+
.command("metric")
|
|
42
|
+
.description("Health metrics tracking");
|
|
43
|
+
|
|
44
|
+
metricCmd
|
|
45
|
+
.command("record")
|
|
46
|
+
.description("Record a health metric")
|
|
47
|
+
.requiredOption("--type <type>", "Metric type (e.g. weight, blood_pressure, heart_rate)")
|
|
48
|
+
.requiredOption("--value <value>", "Metric value")
|
|
49
|
+
.option("--unit <unit>", "Unit of measurement")
|
|
50
|
+
.option("--notes <notes>", "Notes")
|
|
51
|
+
.option("--recorded-at <datetime>", "When recorded (ISO 8601)")
|
|
52
|
+
.option("--json", "Output as JSON", false)
|
|
53
|
+
.action((opts) => {
|
|
54
|
+
const metric = createMetric({
|
|
55
|
+
type: opts.type,
|
|
56
|
+
value: parseFloat(opts.value),
|
|
57
|
+
unit: opts.unit,
|
|
58
|
+
notes: opts.notes,
|
|
59
|
+
recorded_at: opts.recordedAt,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (opts.json) {
|
|
63
|
+
console.log(JSON.stringify(metric, null, 2));
|
|
64
|
+
} else {
|
|
65
|
+
console.log(`Recorded ${metric.type}: ${metric.value}${metric.unit ? ` ${metric.unit}` : ""} (${metric.id})`);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
metricCmd
|
|
70
|
+
.command("list")
|
|
71
|
+
.description("List health metrics")
|
|
72
|
+
.option("--type <type>", "Filter by type")
|
|
73
|
+
.option("--limit <n>", "Limit results")
|
|
74
|
+
.option("--json", "Output as JSON", false)
|
|
75
|
+
.action((opts) => {
|
|
76
|
+
const metrics = listMetrics({
|
|
77
|
+
type: opts.type,
|
|
78
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (opts.json) {
|
|
82
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
83
|
+
} else {
|
|
84
|
+
if (metrics.length === 0) {
|
|
85
|
+
console.log("No metrics found.");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
for (const m of metrics) {
|
|
89
|
+
const unit = m.unit ? ` ${m.unit}` : "";
|
|
90
|
+
console.log(` ${m.type}: ${m.value}${unit} (${m.recorded_at})`);
|
|
91
|
+
}
|
|
92
|
+
console.log(`\n${metrics.length} metric(s)`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
metricCmd
|
|
97
|
+
.command("trend")
|
|
98
|
+
.description("Show metric trend over time")
|
|
99
|
+
.requiredOption("--type <type>", "Metric type")
|
|
100
|
+
.option("--days <n>", "Number of days", "30")
|
|
101
|
+
.option("--json", "Output as JSON", false)
|
|
102
|
+
.action((opts) => {
|
|
103
|
+
const trend = getMetricTrend(opts.type, parseInt(opts.days));
|
|
104
|
+
|
|
105
|
+
if (opts.json) {
|
|
106
|
+
console.log(JSON.stringify(trend, null, 2));
|
|
107
|
+
} else {
|
|
108
|
+
if (trend.length === 0) {
|
|
109
|
+
console.log(`No data for ${opts.type} in the last ${opts.days} days.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(`Trend for ${opts.type} (last ${opts.days} days):`);
|
|
113
|
+
for (const point of trend) {
|
|
114
|
+
console.log(` ${point.date}: ${point.value}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// --- Medications ---
|
|
120
|
+
|
|
121
|
+
const medCmd = program
|
|
122
|
+
.command("medication")
|
|
123
|
+
.alias("med")
|
|
124
|
+
.description("Medication management");
|
|
125
|
+
|
|
126
|
+
medCmd
|
|
127
|
+
.command("add")
|
|
128
|
+
.description("Add a medication")
|
|
129
|
+
.requiredOption("--name <name>", "Medication name")
|
|
130
|
+
.option("--dosage <dosage>", "Dosage (e.g. 10mg)")
|
|
131
|
+
.option("--frequency <frequency>", "Frequency (e.g. twice daily)")
|
|
132
|
+
.option("--start-date <date>", "Start date")
|
|
133
|
+
.option("--end-date <date>", "End date")
|
|
134
|
+
.option("--refill-date <date>", "Next refill date")
|
|
135
|
+
.option("--notes <notes>", "Notes")
|
|
136
|
+
.option("--json", "Output as JSON", false)
|
|
137
|
+
.action((opts) => {
|
|
138
|
+
const med = createMedication({
|
|
139
|
+
name: opts.name,
|
|
140
|
+
dosage: opts.dosage,
|
|
141
|
+
frequency: opts.frequency,
|
|
142
|
+
start_date: opts.startDate,
|
|
143
|
+
end_date: opts.endDate,
|
|
144
|
+
refill_date: opts.refillDate,
|
|
145
|
+
notes: opts.notes,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (opts.json) {
|
|
149
|
+
console.log(JSON.stringify(med, null, 2));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(`Added medication: ${med.name}${med.dosage ? ` (${med.dosage})` : ""} (${med.id})`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
medCmd
|
|
156
|
+
.command("list")
|
|
157
|
+
.description("List medications")
|
|
158
|
+
.option("--active", "Show only active medications")
|
|
159
|
+
.option("--all", "Show all medications including inactive")
|
|
160
|
+
.option("--search <query>", "Search by name")
|
|
161
|
+
.option("--json", "Output as JSON", false)
|
|
162
|
+
.action((opts) => {
|
|
163
|
+
const meds = listMedications({
|
|
164
|
+
active: opts.all ? undefined : (opts.active !== undefined ? true : undefined),
|
|
165
|
+
search: opts.search,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (opts.json) {
|
|
169
|
+
console.log(JSON.stringify(meds, null, 2));
|
|
170
|
+
} else {
|
|
171
|
+
if (meds.length === 0) {
|
|
172
|
+
console.log("No medications found.");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
for (const m of meds) {
|
|
176
|
+
const dosage = m.dosage ? ` ${m.dosage}` : "";
|
|
177
|
+
const freq = m.frequency ? ` (${m.frequency})` : "";
|
|
178
|
+
const status = m.active ? "" : " [INACTIVE]";
|
|
179
|
+
console.log(` ${m.name}${dosage}${freq}${status}`);
|
|
180
|
+
}
|
|
181
|
+
console.log(`\n${meds.length} medication(s)`);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
medCmd
|
|
186
|
+
.command("get")
|
|
187
|
+
.description("Get medication details")
|
|
188
|
+
.argument("<id>", "Medication ID")
|
|
189
|
+
.option("--json", "Output as JSON", false)
|
|
190
|
+
.action((id, opts) => {
|
|
191
|
+
const med = getMedication(id);
|
|
192
|
+
if (!med) {
|
|
193
|
+
console.error(`Medication '${id}' not found.`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (opts.json) {
|
|
198
|
+
console.log(JSON.stringify(med, null, 2));
|
|
199
|
+
} else {
|
|
200
|
+
console.log(`${med.name}`);
|
|
201
|
+
if (med.dosage) console.log(` Dosage: ${med.dosage}`);
|
|
202
|
+
if (med.frequency) console.log(` Frequency: ${med.frequency}`);
|
|
203
|
+
if (med.start_date) console.log(` Start: ${med.start_date}`);
|
|
204
|
+
if (med.end_date) console.log(` End: ${med.end_date}`);
|
|
205
|
+
if (med.refill_date) console.log(` Refill: ${med.refill_date}`);
|
|
206
|
+
console.log(` Active: ${med.active ? "Yes" : "No"}`);
|
|
207
|
+
if (med.notes) console.log(` Notes: ${med.notes}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
medCmd
|
|
212
|
+
.command("update")
|
|
213
|
+
.description("Update a medication")
|
|
214
|
+
.argument("<id>", "Medication ID")
|
|
215
|
+
.option("--name <name>", "Name")
|
|
216
|
+
.option("--dosage <dosage>", "Dosage")
|
|
217
|
+
.option("--frequency <frequency>", "Frequency")
|
|
218
|
+
.option("--start-date <date>", "Start date")
|
|
219
|
+
.option("--end-date <date>", "End date")
|
|
220
|
+
.option("--refill-date <date>", "Refill date")
|
|
221
|
+
.option("--notes <notes>", "Notes")
|
|
222
|
+
.option("--json", "Output as JSON", false)
|
|
223
|
+
.action((id, opts) => {
|
|
224
|
+
const input: Record<string, unknown> = {};
|
|
225
|
+
if (opts.name !== undefined) input.name = opts.name;
|
|
226
|
+
if (opts.dosage !== undefined) input.dosage = opts.dosage;
|
|
227
|
+
if (opts.frequency !== undefined) input.frequency = opts.frequency;
|
|
228
|
+
if (opts.startDate !== undefined) input.start_date = opts.startDate;
|
|
229
|
+
if (opts.endDate !== undefined) input.end_date = opts.endDate;
|
|
230
|
+
if (opts.refillDate !== undefined) input.refill_date = opts.refillDate;
|
|
231
|
+
if (opts.notes !== undefined) input.notes = opts.notes;
|
|
232
|
+
|
|
233
|
+
const med = updateMedication(id, input);
|
|
234
|
+
if (!med) {
|
|
235
|
+
console.error(`Medication '${id}' not found.`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (opts.json) {
|
|
240
|
+
console.log(JSON.stringify(med, null, 2));
|
|
241
|
+
} else {
|
|
242
|
+
console.log(`Updated: ${med.name}`);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
medCmd
|
|
247
|
+
.command("deactivate")
|
|
248
|
+
.description("Deactivate a medication")
|
|
249
|
+
.argument("<id>", "Medication ID")
|
|
250
|
+
.option("--json", "Output as JSON", false)
|
|
251
|
+
.action((id, opts) => {
|
|
252
|
+
const med = deactivateMedication(id);
|
|
253
|
+
if (!med) {
|
|
254
|
+
console.error(`Medication '${id}' not found.`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (opts.json) {
|
|
259
|
+
console.log(JSON.stringify(med, null, 2));
|
|
260
|
+
} else {
|
|
261
|
+
console.log(`Deactivated: ${med.name}`);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// --- Appointments ---
|
|
266
|
+
|
|
267
|
+
const apptCmd = program
|
|
268
|
+
.command("appointment")
|
|
269
|
+
.alias("appt")
|
|
270
|
+
.description("Appointment management");
|
|
271
|
+
|
|
272
|
+
apptCmd
|
|
273
|
+
.command("schedule")
|
|
274
|
+
.description("Schedule an appointment")
|
|
275
|
+
.requiredOption("--provider <provider>", "Provider name")
|
|
276
|
+
.requiredOption("--scheduled-at <datetime>", "Date/time (ISO 8601)")
|
|
277
|
+
.option("--specialty <specialty>", "Specialty")
|
|
278
|
+
.option("--location <location>", "Location")
|
|
279
|
+
.option("--notes <notes>", "Notes")
|
|
280
|
+
.option("--follow-up <date>", "Follow-up date")
|
|
281
|
+
.option("--json", "Output as JSON", false)
|
|
282
|
+
.action((opts) => {
|
|
283
|
+
const appt = createAppointment({
|
|
284
|
+
provider: opts.provider,
|
|
285
|
+
scheduled_at: opts.scheduledAt,
|
|
286
|
+
specialty: opts.specialty,
|
|
287
|
+
location: opts.location,
|
|
288
|
+
notes: opts.notes,
|
|
289
|
+
follow_up_date: opts.followUp,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
if (opts.json) {
|
|
293
|
+
console.log(JSON.stringify(appt, null, 2));
|
|
294
|
+
} else {
|
|
295
|
+
console.log(`Scheduled: ${appt.provider} on ${appt.scheduled_at} (${appt.id})`);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
apptCmd
|
|
300
|
+
.command("list")
|
|
301
|
+
.description("List appointments")
|
|
302
|
+
.option("--status <status>", "Filter by status")
|
|
303
|
+
.option("--provider <provider>", "Filter by provider")
|
|
304
|
+
.option("--upcoming <days>", "Show upcoming appointments within N days")
|
|
305
|
+
.option("--limit <n>", "Limit results")
|
|
306
|
+
.option("--json", "Output as JSON", false)
|
|
307
|
+
.action((opts) => {
|
|
308
|
+
let appointments;
|
|
309
|
+
if (opts.upcoming) {
|
|
310
|
+
appointments = getUpcomingAppointments(parseInt(opts.upcoming));
|
|
311
|
+
} else {
|
|
312
|
+
appointments = listAppointments({
|
|
313
|
+
status: opts.status,
|
|
314
|
+
provider: opts.provider,
|
|
315
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (opts.json) {
|
|
320
|
+
console.log(JSON.stringify(appointments, null, 2));
|
|
321
|
+
} else {
|
|
322
|
+
if (appointments.length === 0) {
|
|
323
|
+
console.log("No appointments found.");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
for (const a of appointments) {
|
|
327
|
+
const specialty = a.specialty ? ` (${a.specialty})` : "";
|
|
328
|
+
const location = a.location ? ` @ ${a.location}` : "";
|
|
329
|
+
console.log(` ${a.provider}${specialty}${location} - ${a.scheduled_at} [${a.status}]`);
|
|
330
|
+
}
|
|
331
|
+
console.log(`\n${appointments.length} appointment(s)`);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
apptCmd
|
|
336
|
+
.command("complete")
|
|
337
|
+
.description("Mark an appointment as completed")
|
|
338
|
+
.argument("<id>", "Appointment ID")
|
|
339
|
+
.option("--json", "Output as JSON", false)
|
|
340
|
+
.action((id, opts) => {
|
|
341
|
+
const appt = completeAppointment(id);
|
|
342
|
+
if (!appt) {
|
|
343
|
+
console.error(`Appointment '${id}' not found.`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (opts.json) {
|
|
348
|
+
console.log(JSON.stringify(appt, null, 2));
|
|
349
|
+
} else {
|
|
350
|
+
console.log(`Completed: ${appt.provider} (${appt.scheduled_at})`);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
apptCmd
|
|
355
|
+
.command("cancel")
|
|
356
|
+
.description("Cancel an appointment")
|
|
357
|
+
.argument("<id>", "Appointment ID")
|
|
358
|
+
.option("--json", "Output as JSON", false)
|
|
359
|
+
.action((id, opts) => {
|
|
360
|
+
const appt = cancelAppointment(id);
|
|
361
|
+
if (!appt) {
|
|
362
|
+
console.error(`Appointment '${id}' not found.`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (opts.json) {
|
|
367
|
+
console.log(JSON.stringify(appt, null, 2));
|
|
368
|
+
} else {
|
|
369
|
+
console.log(`Cancelled: ${appt.provider} (${appt.scheduled_at})`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// --- Fitness ---
|
|
374
|
+
|
|
375
|
+
const fitnessCmd = program
|
|
376
|
+
.command("fitness")
|
|
377
|
+
.description("Fitness log tracking");
|
|
378
|
+
|
|
379
|
+
fitnessCmd
|
|
380
|
+
.command("log")
|
|
381
|
+
.description("Log a fitness activity")
|
|
382
|
+
.requiredOption("--activity <activity>", "Activity type (e.g. running, swimming)")
|
|
383
|
+
.option("--duration <min>", "Duration in minutes")
|
|
384
|
+
.option("--calories <cal>", "Calories burned")
|
|
385
|
+
.option("--distance <dist>", "Distance")
|
|
386
|
+
.option("--notes <notes>", "Notes")
|
|
387
|
+
.option("--logged-at <datetime>", "When logged (ISO 8601)")
|
|
388
|
+
.option("--json", "Output as JSON", false)
|
|
389
|
+
.action((opts) => {
|
|
390
|
+
const log = createFitnessLog({
|
|
391
|
+
activity: opts.activity,
|
|
392
|
+
duration_min: opts.duration ? parseInt(opts.duration) : undefined,
|
|
393
|
+
calories_burned: opts.calories ? parseInt(opts.calories) : undefined,
|
|
394
|
+
distance: opts.distance ? parseFloat(opts.distance) : undefined,
|
|
395
|
+
notes: opts.notes,
|
|
396
|
+
logged_at: opts.loggedAt,
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
if (opts.json) {
|
|
400
|
+
console.log(JSON.stringify(log, null, 2));
|
|
401
|
+
} else {
|
|
402
|
+
const dur = log.duration_min ? ` ${log.duration_min}min` : "";
|
|
403
|
+
const cal = log.calories_burned ? ` ${log.calories_burned}cal` : "";
|
|
404
|
+
console.log(`Logged: ${log.activity}${dur}${cal} (${log.id})`);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
fitnessCmd
|
|
409
|
+
.command("list")
|
|
410
|
+
.description("List fitness logs")
|
|
411
|
+
.option("--activity <activity>", "Filter by activity")
|
|
412
|
+
.option("--limit <n>", "Limit results")
|
|
413
|
+
.option("--json", "Output as JSON", false)
|
|
414
|
+
.action((opts) => {
|
|
415
|
+
const logs = listFitnessLogs({
|
|
416
|
+
activity: opts.activity,
|
|
417
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
if (opts.json) {
|
|
421
|
+
console.log(JSON.stringify(logs, null, 2));
|
|
422
|
+
} else {
|
|
423
|
+
if (logs.length === 0) {
|
|
424
|
+
console.log("No fitness logs found.");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
for (const l of logs) {
|
|
428
|
+
const dur = l.duration_min ? ` ${l.duration_min}min` : "";
|
|
429
|
+
const cal = l.calories_burned ? ` ${l.calories_burned}cal` : "";
|
|
430
|
+
const dist = l.distance ? ` ${l.distance}` : "";
|
|
431
|
+
console.log(` ${l.activity}${dur}${cal}${dist} (${l.logged_at})`);
|
|
432
|
+
}
|
|
433
|
+
console.log(`\n${logs.length} log(s)`);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
fitnessCmd
|
|
438
|
+
.command("stats")
|
|
439
|
+
.description("Show fitness statistics")
|
|
440
|
+
.option("--days <n>", "Number of days", "30")
|
|
441
|
+
.option("--json", "Output as JSON", false)
|
|
442
|
+
.action((opts) => {
|
|
443
|
+
const stats = getFitnessStats(parseInt(opts.days));
|
|
444
|
+
|
|
445
|
+
if (opts.json) {
|
|
446
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
447
|
+
} else {
|
|
448
|
+
console.log(`Fitness stats (last ${opts.days} days):`);
|
|
449
|
+
console.log(` Total sessions: ${stats.total_sessions}`);
|
|
450
|
+
console.log(` Total minutes: ${stats.total_minutes}`);
|
|
451
|
+
console.log(` Total calories: ${stats.total_calories}`);
|
|
452
|
+
console.log(` Avg duration: ${stats.avg_duration} min`);
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// --- Summary ---
|
|
457
|
+
|
|
458
|
+
program
|
|
459
|
+
.command("summary")
|
|
460
|
+
.description("Show overall health summary")
|
|
461
|
+
.option("--json", "Output as JSON", false)
|
|
462
|
+
.action((opts) => {
|
|
463
|
+
const summary = getHealthSummary();
|
|
464
|
+
|
|
465
|
+
if (opts.json) {
|
|
466
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
467
|
+
} else {
|
|
468
|
+
console.log("Health Summary:");
|
|
469
|
+
console.log(`\n Metrics: ${summary.metrics.total} recorded`);
|
|
470
|
+
if (summary.metrics.types.length) {
|
|
471
|
+
console.log(` Types: ${summary.metrics.types.join(", ")}`);
|
|
472
|
+
}
|
|
473
|
+
console.log(`\n Medications: ${summary.medications.total} total, ${summary.medications.active} active`);
|
|
474
|
+
console.log(`\n Appointments: ${summary.appointments.total} total`);
|
|
475
|
+
console.log(` Upcoming: ${summary.appointments.upcoming}`);
|
|
476
|
+
console.log(` Completed: ${summary.appointments.completed}`);
|
|
477
|
+
console.log(`\n Fitness (last 30 days):`);
|
|
478
|
+
console.log(` Sessions: ${summary.fitness.recent_stats.total_sessions}`);
|
|
479
|
+
console.log(` Total minutes: ${summary.fitness.recent_stats.total_minutes}`);
|
|
480
|
+
console.log(` Total calories: ${summary.fitness.recent_stats.total_calories}`);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection for microservice-health
|
|
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-health", "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-health", "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-health", "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
|
+
}
|