@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,400 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
createProposal,
|
|
6
|
+
getProposal,
|
|
7
|
+
listProposals,
|
|
8
|
+
updateProposal,
|
|
9
|
+
deleteProposal,
|
|
10
|
+
sendProposal,
|
|
11
|
+
acceptProposal,
|
|
12
|
+
declineProposal,
|
|
13
|
+
convertToInvoice,
|
|
14
|
+
listExpiring,
|
|
15
|
+
getProposalStats,
|
|
16
|
+
searchProposals,
|
|
17
|
+
createTemplate,
|
|
18
|
+
listTemplates,
|
|
19
|
+
useTemplate,
|
|
20
|
+
} from "../db/proposals.js";
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.name("microservice-proposals")
|
|
26
|
+
.description("Proposal management microservice")
|
|
27
|
+
.version("0.0.1");
|
|
28
|
+
|
|
29
|
+
// --- Proposals ---
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command("create")
|
|
33
|
+
.description("Create a new proposal")
|
|
34
|
+
.requiredOption("--title <title>", "Proposal title")
|
|
35
|
+
.requiredOption("--client-name <name>", "Client name")
|
|
36
|
+
.option("--client-email <email>", "Client email")
|
|
37
|
+
.option("--items <json>", "Items as JSON array")
|
|
38
|
+
.option("--tax-rate <rate>", "Tax rate percentage")
|
|
39
|
+
.option("--discount <amount>", "Discount amount")
|
|
40
|
+
.option("--currency <code>", "Currency code", "USD")
|
|
41
|
+
.option("--valid-until <date>", "Valid until date (YYYY-MM-DD)")
|
|
42
|
+
.option("--notes <notes>", "Notes")
|
|
43
|
+
.option("--terms <terms>", "Terms and conditions")
|
|
44
|
+
.option("--json", "Output as JSON", false)
|
|
45
|
+
.action((opts) => {
|
|
46
|
+
const proposal = createProposal({
|
|
47
|
+
title: opts.title,
|
|
48
|
+
client_name: opts.clientName,
|
|
49
|
+
client_email: opts.clientEmail,
|
|
50
|
+
items: opts.items ? JSON.parse(opts.items) : undefined,
|
|
51
|
+
tax_rate: opts.taxRate ? parseFloat(opts.taxRate) : undefined,
|
|
52
|
+
discount: opts.discount ? parseFloat(opts.discount) : undefined,
|
|
53
|
+
currency: opts.currency,
|
|
54
|
+
valid_until: opts.validUntil,
|
|
55
|
+
notes: opts.notes,
|
|
56
|
+
terms: opts.terms,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (opts.json) {
|
|
60
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
console.log(`Created proposal: ${proposal.title} (${proposal.id})`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("get")
|
|
68
|
+
.description("Get a proposal by ID")
|
|
69
|
+
.argument("<id>", "Proposal ID")
|
|
70
|
+
.option("--json", "Output as JSON", false)
|
|
71
|
+
.action((id, opts) => {
|
|
72
|
+
const proposal = getProposal(id);
|
|
73
|
+
if (!proposal) {
|
|
74
|
+
console.error(`Proposal '${id}' not found.`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (opts.json) {
|
|
79
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`${proposal.title}`);
|
|
82
|
+
console.log(` Client: ${proposal.client_name}${proposal.client_email ? ` <${proposal.client_email}>` : ""}`);
|
|
83
|
+
console.log(` Status: ${proposal.status}`);
|
|
84
|
+
console.log(` Total: ${proposal.currency} ${proposal.total.toFixed(2)}`);
|
|
85
|
+
if (proposal.valid_until) console.log(` Valid until: ${proposal.valid_until}`);
|
|
86
|
+
if (proposal.notes) console.log(` Notes: ${proposal.notes}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
program
|
|
91
|
+
.command("list")
|
|
92
|
+
.description("List proposals")
|
|
93
|
+
.option("--status <status>", "Filter by status")
|
|
94
|
+
.option("--client <name>", "Filter by client name")
|
|
95
|
+
.option("--search <query>", "Search proposals")
|
|
96
|
+
.option("--limit <n>", "Limit results")
|
|
97
|
+
.option("--json", "Output as JSON", false)
|
|
98
|
+
.action((opts) => {
|
|
99
|
+
const proposals = listProposals({
|
|
100
|
+
status: opts.status,
|
|
101
|
+
client_name: opts.client,
|
|
102
|
+
search: opts.search,
|
|
103
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (opts.json) {
|
|
107
|
+
console.log(JSON.stringify(proposals, null, 2));
|
|
108
|
+
} else {
|
|
109
|
+
if (proposals.length === 0) {
|
|
110
|
+
console.log("No proposals found.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
for (const p of proposals) {
|
|
114
|
+
const status = `[${p.status}]`;
|
|
115
|
+
console.log(` ${p.title} - ${p.client_name} ${status} ${p.currency} ${p.total.toFixed(2)}`);
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n${proposals.length} proposal(s)`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
program
|
|
122
|
+
.command("update")
|
|
123
|
+
.description("Update a proposal")
|
|
124
|
+
.argument("<id>", "Proposal ID")
|
|
125
|
+
.option("--title <title>", "Title")
|
|
126
|
+
.option("--client-name <name>", "Client name")
|
|
127
|
+
.option("--client-email <email>", "Client email")
|
|
128
|
+
.option("--items <json>", "Items as JSON array")
|
|
129
|
+
.option("--tax-rate <rate>", "Tax rate")
|
|
130
|
+
.option("--discount <amount>", "Discount")
|
|
131
|
+
.option("--currency <code>", "Currency code")
|
|
132
|
+
.option("--valid-until <date>", "Valid until date")
|
|
133
|
+
.option("--notes <notes>", "Notes")
|
|
134
|
+
.option("--terms <terms>", "Terms")
|
|
135
|
+
.option("--json", "Output as JSON", false)
|
|
136
|
+
.action((id, opts) => {
|
|
137
|
+
const input: Record<string, unknown> = {};
|
|
138
|
+
if (opts.title !== undefined) input.title = opts.title;
|
|
139
|
+
if (opts.clientName !== undefined) input.client_name = opts.clientName;
|
|
140
|
+
if (opts.clientEmail !== undefined) input.client_email = opts.clientEmail;
|
|
141
|
+
if (opts.items !== undefined) input.items = JSON.parse(opts.items);
|
|
142
|
+
if (opts.taxRate !== undefined) input.tax_rate = parseFloat(opts.taxRate);
|
|
143
|
+
if (opts.discount !== undefined) input.discount = parseFloat(opts.discount);
|
|
144
|
+
if (opts.currency !== undefined) input.currency = opts.currency;
|
|
145
|
+
if (opts.validUntil !== undefined) input.valid_until = opts.validUntil;
|
|
146
|
+
if (opts.notes !== undefined) input.notes = opts.notes;
|
|
147
|
+
if (opts.terms !== undefined) input.terms = opts.terms;
|
|
148
|
+
|
|
149
|
+
const proposal = updateProposal(id, input);
|
|
150
|
+
if (!proposal) {
|
|
151
|
+
console.error(`Proposal '${id}' not found.`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (opts.json) {
|
|
156
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
157
|
+
} else {
|
|
158
|
+
console.log(`Updated: ${proposal.title}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
program
|
|
163
|
+
.command("delete")
|
|
164
|
+
.description("Delete a proposal")
|
|
165
|
+
.argument("<id>", "Proposal ID")
|
|
166
|
+
.action((id) => {
|
|
167
|
+
const deleted = deleteProposal(id);
|
|
168
|
+
if (deleted) {
|
|
169
|
+
console.log(`Deleted proposal ${id}`);
|
|
170
|
+
} else {
|
|
171
|
+
console.error(`Proposal '${id}' not found.`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command("send")
|
|
178
|
+
.description("Send a proposal (sets status to sent)")
|
|
179
|
+
.argument("<id>", "Proposal ID")
|
|
180
|
+
.option("--json", "Output as JSON", false)
|
|
181
|
+
.action((id, opts) => {
|
|
182
|
+
const proposal = sendProposal(id);
|
|
183
|
+
if (!proposal) {
|
|
184
|
+
console.error(`Proposal '${id}' not found.`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (opts.json) {
|
|
189
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
190
|
+
} else {
|
|
191
|
+
console.log(`Sent proposal: ${proposal.title} to ${proposal.client_name}`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
program
|
|
196
|
+
.command("accept")
|
|
197
|
+
.description("Accept a proposal")
|
|
198
|
+
.argument("<id>", "Proposal ID")
|
|
199
|
+
.option("--json", "Output as JSON", false)
|
|
200
|
+
.action((id, opts) => {
|
|
201
|
+
const proposal = acceptProposal(id);
|
|
202
|
+
if (!proposal) {
|
|
203
|
+
console.error(`Proposal '${id}' not found.`);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (opts.json) {
|
|
208
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
209
|
+
} else {
|
|
210
|
+
console.log(`Accepted proposal: ${proposal.title}`);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
program
|
|
215
|
+
.command("decline")
|
|
216
|
+
.description("Decline a proposal")
|
|
217
|
+
.argument("<id>", "Proposal ID")
|
|
218
|
+
.option("--reason <reason>", "Decline reason")
|
|
219
|
+
.option("--json", "Output as JSON", false)
|
|
220
|
+
.action((id, opts) => {
|
|
221
|
+
const proposal = declineProposal(id, opts.reason);
|
|
222
|
+
if (!proposal) {
|
|
223
|
+
console.error(`Proposal '${id}' not found.`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (opts.json) {
|
|
228
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
console.log(`Declined proposal: ${proposal.title}`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
program
|
|
235
|
+
.command("convert")
|
|
236
|
+
.description("Convert an accepted proposal to invoice data")
|
|
237
|
+
.argument("<id>", "Proposal ID")
|
|
238
|
+
.option("--json", "Output as JSON", false)
|
|
239
|
+
.action((id, opts) => {
|
|
240
|
+
const invoiceData = convertToInvoice(id);
|
|
241
|
+
if (!invoiceData) {
|
|
242
|
+
console.error(`Proposal '${id}' not found.`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (opts.json) {
|
|
247
|
+
console.log(JSON.stringify(invoiceData, null, 2));
|
|
248
|
+
} else {
|
|
249
|
+
console.log(`Invoice data for: ${invoiceData.client_name}`);
|
|
250
|
+
console.log(` Total: ${invoiceData.currency} ${invoiceData.total.toFixed(2)}`);
|
|
251
|
+
console.log(` Items: ${invoiceData.items.length}`);
|
|
252
|
+
console.log(` Proposal ID: ${invoiceData.proposal_id}`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
program
|
|
257
|
+
.command("search")
|
|
258
|
+
.description("Search proposals")
|
|
259
|
+
.argument("<query>", "Search term")
|
|
260
|
+
.option("--json", "Output as JSON", false)
|
|
261
|
+
.action((query, opts) => {
|
|
262
|
+
const results = searchProposals(query);
|
|
263
|
+
|
|
264
|
+
if (opts.json) {
|
|
265
|
+
console.log(JSON.stringify(results, null, 2));
|
|
266
|
+
} else {
|
|
267
|
+
if (results.length === 0) {
|
|
268
|
+
console.log(`No proposals matching "${query}".`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
for (const p of results) {
|
|
272
|
+
console.log(` ${p.title} - ${p.client_name} [${p.status}]`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
program
|
|
278
|
+
.command("expiring")
|
|
279
|
+
.description("List proposals expiring within N days")
|
|
280
|
+
.option("--days <n>", "Number of days", "30")
|
|
281
|
+
.option("--json", "Output as JSON", false)
|
|
282
|
+
.action((opts) => {
|
|
283
|
+
const days = parseInt(opts.days);
|
|
284
|
+
const proposals = listExpiring(days);
|
|
285
|
+
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
console.log(JSON.stringify(proposals, null, 2));
|
|
288
|
+
} else {
|
|
289
|
+
if (proposals.length === 0) {
|
|
290
|
+
console.log(`No proposals expiring within ${days} days.`);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
for (const p of proposals) {
|
|
294
|
+
console.log(` ${p.title} - ${p.client_name} (expires: ${p.valid_until})`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
program
|
|
300
|
+
.command("stats")
|
|
301
|
+
.description("Get proposal statistics")
|
|
302
|
+
.option("--json", "Output as JSON", false)
|
|
303
|
+
.action((opts) => {
|
|
304
|
+
const stats = getProposalStats();
|
|
305
|
+
|
|
306
|
+
if (opts.json) {
|
|
307
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
308
|
+
} else {
|
|
309
|
+
console.log(`Proposal Statistics:`);
|
|
310
|
+
console.log(` Total: ${stats.total}`);
|
|
311
|
+
console.log(` Total Value: $${stats.total_value.toFixed(2)}`);
|
|
312
|
+
console.log(` Average Value: $${stats.average_value.toFixed(2)}`);
|
|
313
|
+
console.log(` Conversion Rate: ${stats.conversion_rate.toFixed(1)}%`);
|
|
314
|
+
console.log(` Accepted Value: $${stats.accepted_value.toFixed(2)}`);
|
|
315
|
+
console.log(` By Status:`);
|
|
316
|
+
for (const [status, count] of Object.entries(stats.by_status)) {
|
|
317
|
+
console.log(` ${status}: ${count}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// --- Templates ---
|
|
323
|
+
|
|
324
|
+
const templateCmd = program
|
|
325
|
+
.command("template")
|
|
326
|
+
.description("Proposal template management");
|
|
327
|
+
|
|
328
|
+
templateCmd
|
|
329
|
+
.command("create")
|
|
330
|
+
.description("Create a proposal template")
|
|
331
|
+
.requiredOption("--name <name>", "Template name")
|
|
332
|
+
.option("--items <json>", "Items as JSON array")
|
|
333
|
+
.option("--terms <terms>", "Default terms")
|
|
334
|
+
.option("--notes <notes>", "Default notes")
|
|
335
|
+
.option("--json", "Output as JSON", false)
|
|
336
|
+
.action((opts) => {
|
|
337
|
+
const template = createTemplate({
|
|
338
|
+
name: opts.name,
|
|
339
|
+
items: opts.items ? JSON.parse(opts.items) : undefined,
|
|
340
|
+
terms: opts.terms,
|
|
341
|
+
notes: opts.notes,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (opts.json) {
|
|
345
|
+
console.log(JSON.stringify(template, null, 2));
|
|
346
|
+
} else {
|
|
347
|
+
console.log(`Created template: ${template.name} (${template.id})`);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
templateCmd
|
|
352
|
+
.command("list")
|
|
353
|
+
.description("List all templates")
|
|
354
|
+
.option("--json", "Output as JSON", false)
|
|
355
|
+
.action((opts) => {
|
|
356
|
+
const templates = listTemplates();
|
|
357
|
+
|
|
358
|
+
if (opts.json) {
|
|
359
|
+
console.log(JSON.stringify(templates, null, 2));
|
|
360
|
+
} else {
|
|
361
|
+
if (templates.length === 0) {
|
|
362
|
+
console.log("No templates found.");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
for (const t of templates) {
|
|
366
|
+
console.log(` ${t.name} (${t.items.length} items) — ${t.id}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
templateCmd
|
|
372
|
+
.command("use")
|
|
373
|
+
.description("Create a proposal from a template")
|
|
374
|
+
.argument("<template-id>", "Template ID")
|
|
375
|
+
.requiredOption("--title <title>", "Proposal title")
|
|
376
|
+
.requiredOption("--client-name <name>", "Client name")
|
|
377
|
+
.option("--client-email <email>", "Client email")
|
|
378
|
+
.option("--valid-until <date>", "Valid until date")
|
|
379
|
+
.option("--json", "Output as JSON", false)
|
|
380
|
+
.action((templateId, opts) => {
|
|
381
|
+
const proposal = useTemplate(templateId, {
|
|
382
|
+
title: opts.title,
|
|
383
|
+
client_name: opts.clientName,
|
|
384
|
+
client_email: opts.clientEmail,
|
|
385
|
+
valid_until: opts.validUntil,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
if (!proposal) {
|
|
389
|
+
console.error(`Template '${templateId}' not found.`);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (opts.json) {
|
|
394
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
395
|
+
} else {
|
|
396
|
+
console.log(`Created proposal from template: ${proposal.title} (${proposal.id})`);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database connection for microservice-proposals
|
|
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-proposals", "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-proposals", "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-proposals", "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,52 @@
|
|
|
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 proposals (
|
|
13
|
+
id TEXT PRIMARY KEY,
|
|
14
|
+
title TEXT NOT NULL,
|
|
15
|
+
client_name TEXT NOT NULL,
|
|
16
|
+
client_email TEXT,
|
|
17
|
+
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sent', 'viewed', 'accepted', 'declined', 'expired')),
|
|
18
|
+
items TEXT NOT NULL DEFAULT '[]',
|
|
19
|
+
subtotal REAL NOT NULL DEFAULT 0,
|
|
20
|
+
tax_rate REAL NOT NULL DEFAULT 0,
|
|
21
|
+
tax_amount REAL NOT NULL DEFAULT 0,
|
|
22
|
+
discount REAL NOT NULL DEFAULT 0,
|
|
23
|
+
total REAL NOT NULL DEFAULT 0,
|
|
24
|
+
currency TEXT NOT NULL DEFAULT 'USD',
|
|
25
|
+
valid_until TEXT,
|
|
26
|
+
notes TEXT,
|
|
27
|
+
terms TEXT,
|
|
28
|
+
sent_at TEXT,
|
|
29
|
+
viewed_at TEXT,
|
|
30
|
+
responded_at TEXT,
|
|
31
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
32
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
33
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS proposal_templates (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
name TEXT NOT NULL,
|
|
39
|
+
items TEXT NOT NULL DEFAULT '[]',
|
|
40
|
+
terms TEXT,
|
|
41
|
+
notes TEXT,
|
|
42
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_status ON proposals(status);
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_client_name ON proposals(client_name);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_client_email ON proposals(client_email);
|
|
48
|
+
CREATE INDEX IF NOT EXISTS idx_proposals_valid_until ON proposals(valid_until);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_proposal_templates_name ON proposal_templates(name);
|
|
50
|
+
`,
|
|
51
|
+
},
|
|
52
|
+
];
|