@simonfestl/husky-cli 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +3 -116
  2. package/dist/commands/biz/customers.d.ts +8 -0
  3. package/dist/commands/biz/customers.js +181 -0
  4. package/dist/commands/biz/orders.d.ts +8 -0
  5. package/dist/commands/biz/orders.js +226 -0
  6. package/dist/commands/biz/products.d.ts +8 -0
  7. package/dist/commands/biz/products.js +255 -0
  8. package/dist/commands/biz/qdrant.d.ts +8 -0
  9. package/dist/commands/biz/qdrant.js +170 -0
  10. package/dist/commands/biz/seatable.d.ts +8 -0
  11. package/dist/commands/biz/seatable.js +449 -0
  12. package/dist/commands/biz/tickets.d.ts +8 -0
  13. package/dist/commands/biz/tickets.js +600 -0
  14. package/dist/commands/biz.d.ts +9 -0
  15. package/dist/commands/biz.js +22 -0
  16. package/dist/commands/config.d.ts +13 -0
  17. package/dist/commands/config.js +43 -16
  18. package/dist/commands/explain.js +12 -595
  19. package/dist/commands/idea.js +2 -50
  20. package/dist/commands/project.js +2 -47
  21. package/dist/commands/roadmap.js +0 -107
  22. package/dist/commands/task.js +11 -17
  23. package/dist/commands/vm.js +0 -225
  24. package/dist/commands/workflow.js +4 -60
  25. package/dist/index.js +5 -1
  26. package/dist/lib/biz/billbee-types.d.ts +259 -0
  27. package/dist/lib/biz/billbee-types.js +41 -0
  28. package/dist/lib/biz/billbee.d.ts +37 -0
  29. package/dist/lib/biz/billbee.js +165 -0
  30. package/dist/lib/biz/embeddings.d.ts +45 -0
  31. package/dist/lib/biz/embeddings.js +115 -0
  32. package/dist/lib/biz/index.d.ts +13 -0
  33. package/dist/lib/biz/index.js +11 -0
  34. package/dist/lib/biz/qdrant.d.ts +52 -0
  35. package/dist/lib/biz/qdrant.js +158 -0
  36. package/dist/lib/biz/seatable-types.d.ts +115 -0
  37. package/dist/lib/biz/seatable-types.js +27 -0
  38. package/dist/lib/biz/seatable.d.ts +49 -0
  39. package/dist/lib/biz/seatable.js +210 -0
  40. package/dist/lib/biz/zendesk-types.d.ts +136 -0
  41. package/dist/lib/biz/zendesk-types.js +28 -0
  42. package/dist/lib/biz/zendesk.d.ts +45 -0
  43. package/dist/lib/biz/zendesk.js +206 -0
  44. package/package.json +2 -2
@@ -0,0 +1,600 @@
1
+ /**
2
+ * Husky Biz Tickets Command
3
+ *
4
+ * Manages support tickets via Zendesk API
5
+ */
6
+ import { Command } from "commander";
7
+ import { ZendeskClient } from "../../lib/biz/index.js";
8
+ import * as fs from "fs";
9
+ import * as path from "path";
10
+ export const ticketsCommand = new Command("tickets")
11
+ .description("Manage support tickets (Zendesk)");
12
+ // husky biz tickets list
13
+ ticketsCommand
14
+ .command("list")
15
+ .description("List tickets")
16
+ .option("-s, --status <status>", "Filter by status (new, open, pending, solved, closed)")
17
+ .option("-l, --limit <num>", "Number of tickets", "25")
18
+ .option("--json", "Output as JSON")
19
+ .action(async (options) => {
20
+ try {
21
+ const client = ZendeskClient.fromConfig();
22
+ const tickets = await client.listTickets({
23
+ per_page: parseInt(options.limit, 10),
24
+ status: options.status,
25
+ });
26
+ if (options.json) {
27
+ console.log(JSON.stringify(tickets, null, 2));
28
+ return;
29
+ }
30
+ console.log(`\n 🎫 Tickets (${tickets.length})\n`);
31
+ if (tickets.length === 0) {
32
+ console.log(" No tickets found.");
33
+ return;
34
+ }
35
+ for (const ticket of tickets) {
36
+ const statusIcon = getStatusIcon(ticket.status);
37
+ const date = new Date(ticket.updated_at).toLocaleDateString("de-DE");
38
+ console.log(` ${statusIcon} #${String(ticket.id).padEnd(8)} │ ${ticket.status.padEnd(8)} │ ${date} │ ${ticket.subject.slice(0, 45)}`);
39
+ }
40
+ console.log("");
41
+ }
42
+ catch (error) {
43
+ console.error("Error:", error.message);
44
+ process.exit(1);
45
+ }
46
+ });
47
+ // husky biz tickets get <id>
48
+ ticketsCommand
49
+ .command("get <id>")
50
+ .description("Get ticket with conversation history")
51
+ .option("--json", "Output as JSON")
52
+ .action(async (id, options) => {
53
+ try {
54
+ const client = ZendeskClient.fromConfig();
55
+ const ticketId = parseInt(id, 10);
56
+ const [ticket, comments] = await Promise.all([
57
+ client.getTicket(ticketId),
58
+ client.getTicketComments(ticketId),
59
+ ]);
60
+ if (options.json) {
61
+ console.log(JSON.stringify({ ticket, comments }, null, 2));
62
+ return;
63
+ }
64
+ console.log(`\n Ticket #${ticket.id}: ${ticket.subject}`);
65
+ console.log(" " + "─".repeat(60));
66
+ console.log(` Status: ${ticket.status}`);
67
+ console.log(` Priority: ${ticket.priority || "normal"}`);
68
+ console.log(` Created: ${new Date(ticket.created_at).toLocaleString("de-DE")}`);
69
+ console.log(` Updated: ${new Date(ticket.updated_at).toLocaleString("de-DE")}`);
70
+ if (ticket.tags && ticket.tags.length > 0) {
71
+ console.log(` Tags: ${ticket.tags.join(", ")}`);
72
+ }
73
+ console.log(`\n Conversation (${comments.length} messages):`);
74
+ console.log(" " + "─".repeat(60));
75
+ for (const comment of comments) {
76
+ const date = new Date(comment.created_at).toLocaleString("de-DE");
77
+ const visibility = comment.public ? "Public" : "Internal";
78
+ console.log(`\n [${date}] (${visibility})`);
79
+ console.log(` ${comment.body.slice(0, 500)}${comment.body.length > 500 ? "..." : ""}`);
80
+ if (comment.attachments && comment.attachments.length > 0) {
81
+ console.log(` 📎 ${comment.attachments.length} attachment(s)`);
82
+ }
83
+ }
84
+ console.log("");
85
+ }
86
+ catch (error) {
87
+ console.error("Error:", error.message);
88
+ process.exit(1);
89
+ }
90
+ });
91
+ // husky biz tickets reply <id> -m <message>
92
+ ticketsCommand
93
+ .command("reply <id>")
94
+ .description("Reply to a ticket (public)")
95
+ .requiredOption("-m, --message <text>", "Reply message")
96
+ .action(async (id, options) => {
97
+ try {
98
+ const client = ZendeskClient.fromConfig();
99
+ const ticket = await client.addComment(parseInt(id, 10), options.message, true);
100
+ console.log(`✓ Public reply added to ticket #${ticket.id}`);
101
+ }
102
+ catch (error) {
103
+ console.error("Error:", error.message);
104
+ process.exit(1);
105
+ }
106
+ });
107
+ // husky biz tickets note <id> -m <message>
108
+ ticketsCommand
109
+ .command("note <id>")
110
+ .description("Add internal note to ticket")
111
+ .requiredOption("-m, --message <text>", "Note content")
112
+ .action(async (id, options) => {
113
+ try {
114
+ const client = ZendeskClient.fromConfig();
115
+ const ticket = await client.addInternalNote(parseInt(id, 10), options.message);
116
+ console.log(`✓ Internal note added to ticket #${ticket.id}`);
117
+ }
118
+ catch (error) {
119
+ console.error("Error:", error.message);
120
+ process.exit(1);
121
+ }
122
+ });
123
+ // husky biz tickets update <id>
124
+ ticketsCommand
125
+ .command("update <id>")
126
+ .description("Update ticket properties")
127
+ .option("--status <status>", "New status (open, pending, solved)")
128
+ .option("--priority <priority>", "New priority (low, normal, high, urgent)")
129
+ .action(async (id, options) => {
130
+ try {
131
+ const client = ZendeskClient.fromConfig();
132
+ const updates = {};
133
+ if (options.status)
134
+ updates.status = options.status;
135
+ if (options.priority)
136
+ updates.priority = options.priority;
137
+ if (Object.keys(updates).length === 0) {
138
+ console.error("Error: Provide --status or --priority");
139
+ process.exit(1);
140
+ }
141
+ const ticket = await client.updateTicket(parseInt(id, 10), updates);
142
+ console.log(`✓ Updated ticket #${ticket.id}`);
143
+ console.log(` Status: ${ticket.status}, Priority: ${ticket.priority || "normal"}`);
144
+ }
145
+ catch (error) {
146
+ console.error("Error:", error.message);
147
+ process.exit(1);
148
+ }
149
+ });
150
+ // husky biz tickets close <id>
151
+ ticketsCommand
152
+ .command("close <id>")
153
+ .description("Close/solve a ticket")
154
+ .action(async (id) => {
155
+ try {
156
+ const client = ZendeskClient.fromConfig();
157
+ const ticket = await client.closeTicket(parseInt(id, 10));
158
+ console.log(`✓ Ticket #${ticket.id} marked as solved`);
159
+ }
160
+ catch (error) {
161
+ console.error("Error:", error.message);
162
+ process.exit(1);
163
+ }
164
+ });
165
+ // husky biz tickets attachments <id>
166
+ ticketsCommand
167
+ .command("attachments <id>")
168
+ .description("List attachments for a ticket")
169
+ .option("--json", "Output as JSON")
170
+ .action(async (id, options) => {
171
+ try {
172
+ const client = ZendeskClient.fromConfig();
173
+ const attachments = await client.getTicketAttachments(parseInt(id, 10));
174
+ if (options.json) {
175
+ console.log(JSON.stringify(attachments, null, 2));
176
+ return;
177
+ }
178
+ console.log(`\n 📎 Attachments for ticket #${id} (${attachments.length})\n`);
179
+ if (attachments.length === 0) {
180
+ console.log(" No attachments found.");
181
+ return;
182
+ }
183
+ for (const att of attachments) {
184
+ const size = (att.size / 1024).toFixed(1);
185
+ console.log(` ${att.id} │ ${att.file_name} │ ${size} KB │ ${att.content_type}`);
186
+ }
187
+ console.log("");
188
+ }
189
+ catch (error) {
190
+ console.error("Error:", error.message);
191
+ process.exit(1);
192
+ }
193
+ });
194
+ // husky biz tickets download <id> <filename>
195
+ ticketsCommand
196
+ .command("download <ticketId> <filename>")
197
+ .description("Download an attachment")
198
+ .option("-o, --output <path>", "Output path")
199
+ .action(async (ticketId, filename, options) => {
200
+ try {
201
+ const client = ZendeskClient.fromConfig();
202
+ const attachments = await client.getTicketAttachments(parseInt(ticketId, 10));
203
+ const attachment = attachments.find(a => a.file_name === filename || a.id === parseInt(filename, 10));
204
+ if (!attachment) {
205
+ console.error(`Attachment "${filename}" not found in ticket #${ticketId}`);
206
+ process.exit(1);
207
+ }
208
+ const data = await client.downloadAttachment(attachment.content_url);
209
+ const outputPath = options.output || path.join(process.cwd(), attachment.file_name);
210
+ fs.writeFileSync(outputPath, Buffer.from(data));
211
+ console.log(`✓ Downloaded: ${outputPath}`);
212
+ }
213
+ catch (error) {
214
+ console.error("Error:", error.message);
215
+ process.exit(1);
216
+ }
217
+ });
218
+ // Helper
219
+ function getStatusIcon(status) {
220
+ switch (status) {
221
+ case "new": return "🆕";
222
+ case "open": return "📬";
223
+ case "pending": return "⏳";
224
+ case "hold": return "⏸️";
225
+ case "solved": return "✅";
226
+ case "closed": return "🔒";
227
+ default: return "○";
228
+ }
229
+ }
230
+ // husky biz tickets create
231
+ ticketsCommand
232
+ .command("create")
233
+ .description("Create a new ticket")
234
+ .requiredOption("-s, --subject <text>", "Ticket subject")
235
+ .requiredOption("-m, --message <text>", "Initial message/description")
236
+ .option("-e, --email <email>", "Requester email")
237
+ .option("-p, --priority <priority>", "Priority (low, normal, high, urgent)")
238
+ .option("--json", "Output as JSON")
239
+ .action(async (options) => {
240
+ try {
241
+ const client = ZendeskClient.fromConfig();
242
+ const ticketData = {
243
+ subject: options.subject,
244
+ comment: { body: options.message },
245
+ };
246
+ if (options.email) {
247
+ ticketData.requester = { email: options.email };
248
+ }
249
+ if (options.priority) {
250
+ ticketData.priority = options.priority;
251
+ }
252
+ const ticket = await client.createTicket(ticketData);
253
+ if (options.json) {
254
+ console.log(JSON.stringify(ticket, null, 2));
255
+ return;
256
+ }
257
+ console.log(`✓ Ticket created: #${ticket.id}`);
258
+ console.log(` Subject: ${ticket.subject}`);
259
+ console.log(` Status: ${ticket.status}`);
260
+ }
261
+ catch (error) {
262
+ console.error("Error:", error.message);
263
+ process.exit(1);
264
+ }
265
+ });
266
+ // husky biz tickets search <query>
267
+ ticketsCommand
268
+ .command("search <query>")
269
+ .description("Search tickets (Zendesk search syntax)")
270
+ .option("--json", "Output as JSON")
271
+ .action(async (query, options) => {
272
+ try {
273
+ const client = ZendeskClient.fromConfig();
274
+ const tickets = await client.searchTickets(query);
275
+ if (options.json) {
276
+ console.log(JSON.stringify(tickets, null, 2));
277
+ return;
278
+ }
279
+ console.log(`\n 🔍 Search: "${query}" (${tickets.length} results)\n`);
280
+ if (tickets.length === 0) {
281
+ console.log(" No tickets found.");
282
+ return;
283
+ }
284
+ for (const ticket of tickets) {
285
+ const statusIcon = getStatusIcon(String(ticket.status));
286
+ console.log(` ${statusIcon} #${String(ticket.id).padEnd(8)} │ ${String(ticket.status).padEnd(8)} │ ${ticket.subject.slice(0, 45)}`);
287
+ }
288
+ console.log("");
289
+ }
290
+ catch (error) {
291
+ console.error("Error:", error.message);
292
+ process.exit(1);
293
+ }
294
+ });
295
+ // husky biz tickets delete <id>
296
+ ticketsCommand
297
+ .command("delete <id>")
298
+ .description("Delete a ticket (soft delete)")
299
+ .action(async (id) => {
300
+ try {
301
+ const client = ZendeskClient.fromConfig();
302
+ await client.deleteTicket(parseInt(id, 10));
303
+ console.log(`✓ Ticket #${id} deleted`);
304
+ }
305
+ catch (error) {
306
+ console.error("Error:", error.message);
307
+ process.exit(1);
308
+ }
309
+ });
310
+ // husky biz tickets assign <id> <userId>
311
+ ticketsCommand
312
+ .command("assign <id> <userId>")
313
+ .description("Assign ticket to a user")
314
+ .action(async (id, userId) => {
315
+ try {
316
+ const client = ZendeskClient.fromConfig();
317
+ const ticket = await client.assignTicket(parseInt(id, 10), parseInt(userId, 10));
318
+ console.log(`✓ Ticket #${ticket.id} assigned to user #${userId}`);
319
+ }
320
+ catch (error) {
321
+ console.error("Error:", error.message);
322
+ process.exit(1);
323
+ }
324
+ });
325
+ // husky biz tickets tags <id> add/remove <tag>
326
+ const tagsCommand = ticketsCommand
327
+ .command("tags <id>")
328
+ .description("Manage ticket tags");
329
+ tagsCommand
330
+ .command("add <tag>")
331
+ .description("Add a tag to ticket")
332
+ .action(async (tag, options, command) => {
333
+ try {
334
+ const client = ZendeskClient.fromConfig();
335
+ const ticketId = parseInt(command.parent.args[0], 10);
336
+ const ticket = await client.addTags(ticketId, [tag]);
337
+ console.log(`✓ Tag "${tag}" added to ticket #${ticket.id}`);
338
+ }
339
+ catch (error) {
340
+ console.error("Error:", error.message);
341
+ process.exit(1);
342
+ }
343
+ });
344
+ tagsCommand
345
+ .command("remove <tag>")
346
+ .description("Remove a tag from ticket")
347
+ .action(async (tag, options, command) => {
348
+ try {
349
+ const client = ZendeskClient.fromConfig();
350
+ const ticketId = parseInt(command.parent.args[0], 10);
351
+ const ticket = await client.removeTags(ticketId, [tag]);
352
+ console.log(`✓ Tag "${tag}" removed from ticket #${ticket.id}`);
353
+ }
354
+ catch (error) {
355
+ console.error("Error:", error.message);
356
+ process.exit(1);
357
+ }
358
+ });
359
+ // husky biz tickets macros
360
+ const macrosCommand = ticketsCommand
361
+ .command("macros")
362
+ .description("Manage Zendesk macros (templates)");
363
+ macrosCommand
364
+ .command("list")
365
+ .description("List available macros")
366
+ .option("--json", "Output as JSON")
367
+ .action(async (options) => {
368
+ try {
369
+ const client = ZendeskClient.fromConfig();
370
+ const macros = await client.listMacros({ active: true });
371
+ if (options.json) {
372
+ console.log(JSON.stringify(macros, null, 2));
373
+ return;
374
+ }
375
+ console.log(`\n 📋 Macros (${macros.length})\n`);
376
+ if (macros.length === 0) {
377
+ console.log(" No macros found.");
378
+ return;
379
+ }
380
+ for (const macro of macros) {
381
+ console.log(` #${String(macro.id).padEnd(8)} │ ${macro.title.slice(0, 50)}`);
382
+ }
383
+ console.log("");
384
+ }
385
+ catch (error) {
386
+ console.error("Error:", error.message);
387
+ process.exit(1);
388
+ }
389
+ });
390
+ macrosCommand
391
+ .command("get <id>")
392
+ .description("Get macro details")
393
+ .option("--json", "Output as JSON")
394
+ .action(async (id, options) => {
395
+ try {
396
+ const client = ZendeskClient.fromConfig();
397
+ const macro = await client.getMacro(parseInt(id, 10));
398
+ if (options.json) {
399
+ console.log(JSON.stringify(macro, null, 2));
400
+ return;
401
+ }
402
+ console.log(`\n Macro #${macro.id}: ${macro.title}`);
403
+ console.log(" " + "─".repeat(50));
404
+ console.log(` Active: ${macro.active ? "Yes" : "No"}`);
405
+ if (macro.description) {
406
+ console.log(` Description: ${macro.description}`);
407
+ }
408
+ console.log(`\n Actions:`);
409
+ for (const action of macro.actions) {
410
+ console.log(` ${action.field}: ${typeof action.value === 'string' ? action.value.slice(0, 60) : JSON.stringify(action.value)}`);
411
+ }
412
+ console.log("");
413
+ }
414
+ catch (error) {
415
+ console.error("Error:", error.message);
416
+ process.exit(1);
417
+ }
418
+ });
419
+ macrosCommand
420
+ .command("apply <ticketId> <macroId>")
421
+ .description("Apply a macro to a ticket")
422
+ .action(async (ticketId, macroId) => {
423
+ try {
424
+ const client = ZendeskClient.fromConfig();
425
+ const ticket = await client.applyMacro(parseInt(ticketId, 10), parseInt(macroId, 10));
426
+ console.log(`✓ Macro #${macroId} applied to ticket #${ticket.id}`);
427
+ }
428
+ catch (error) {
429
+ console.error("Error:", error.message);
430
+ process.exit(1);
431
+ }
432
+ });
433
+ // ============================================================================
434
+ // PREBUILD SEMANTIC SEARCH COMMANDS
435
+ // ============================================================================
436
+ import { EmbeddingService, QdrantClient } from "../../lib/biz/index.js";
437
+ const TICKET_COLLECTION = "zendesk_tickets_01";
438
+ const LEARNED_COLLECTION = "tickets_learned";
439
+ // husky biz tickets similar <id>
440
+ ticketsCommand
441
+ .command("similar <id>")
442
+ .description("[PREBUILD] Find tickets similar to given ticket")
443
+ .option("-l, --limit <num>", "Number of results", "5")
444
+ .option("-c, --collection <name>", "Qdrant collection", TICKET_COLLECTION)
445
+ .option("--json", "Output as JSON")
446
+ .action(async (id, options) => {
447
+ try {
448
+ const zendesk = ZendeskClient.fromConfig();
449
+ const embeddings = EmbeddingService.fromConfig();
450
+ const qdrant = QdrantClient.fromConfig();
451
+ // 1. Fetch ticket from Zendesk
452
+ console.log(` Fetching ticket #${id}...`);
453
+ const ticket = await zendesk.getTicket(parseInt(id, 10));
454
+ // 2. Create embedding from subject + description
455
+ const text = `${ticket.subject}\n${ticket.description || ''}`;
456
+ console.log(` Generating embedding...`);
457
+ const vector = await embeddings.embed(text);
458
+ // 3. Search Qdrant
459
+ console.log(` Searching ${options.collection}...`);
460
+ const results = await qdrant.search(options.collection, vector, parseInt(options.limit, 10) + 1 // +1 to exclude self
461
+ );
462
+ // Filter out the same ticket
463
+ const filteredResults = results.filter(r => {
464
+ const payload = r.payload;
465
+ return payload?.ticket_id !== ticket.id && payload?.id !== ticket.id;
466
+ }).slice(0, parseInt(options.limit, 10));
467
+ if (options.json) {
468
+ console.log(JSON.stringify({
469
+ success: true,
470
+ source_ticket: { id: ticket.id, subject: ticket.subject },
471
+ similar: filteredResults.map(r => ({
472
+ id: r.payload?.ticket_id || r.id,
473
+ score: r.score,
474
+ subject: r.payload?.subject,
475
+ status: r.payload?.status,
476
+ })),
477
+ }, null, 2));
478
+ return;
479
+ }
480
+ console.log(`\n 🔍 Tickets similar to #${ticket.id}: "${ticket.subject}"\n`);
481
+ if (filteredResults.length === 0) {
482
+ console.log(" No similar tickets found.");
483
+ return;
484
+ }
485
+ for (const result of filteredResults) {
486
+ const payload = result.payload;
487
+ const ticketId = payload?.ticket_id || payload?.id || result.id;
488
+ const subject = payload?.subject || '(no subject)';
489
+ const status = payload?.status || '';
490
+ console.log(` [${(result.score * 100).toFixed(1)}%] #${ticketId} │ ${status} │ ${String(subject).slice(0, 45)}`);
491
+ }
492
+ console.log("");
493
+ }
494
+ catch (error) {
495
+ console.error("Error:", error.message);
496
+ process.exit(1);
497
+ }
498
+ });
499
+ // husky biz tickets find-similar "<query>"
500
+ ticketsCommand
501
+ .command("find-similar <query>")
502
+ .description("[PREBUILD] Semantic search tickets by text query")
503
+ .option("-l, --limit <num>", "Number of results", "5")
504
+ .option("-c, --collection <name>", "Qdrant collection", TICKET_COLLECTION)
505
+ .option("--json", "Output as JSON")
506
+ .action(async (query, options) => {
507
+ try {
508
+ const embeddings = EmbeddingService.fromConfig();
509
+ const qdrant = QdrantClient.fromConfig();
510
+ // 1. Create embedding from query
511
+ console.log(` Generating embedding for query...`);
512
+ const vector = await embeddings.embed(query);
513
+ // 2. Search Qdrant
514
+ console.log(` Searching ${options.collection}...`);
515
+ const results = await qdrant.search(options.collection, vector, parseInt(options.limit, 10));
516
+ if (options.json) {
517
+ console.log(JSON.stringify({
518
+ success: true,
519
+ query,
520
+ results: results.map(r => ({
521
+ id: r.payload?.ticket_id || r.id,
522
+ score: r.score,
523
+ subject: r.payload?.subject,
524
+ status: r.payload?.status,
525
+ })),
526
+ }, null, 2));
527
+ return;
528
+ }
529
+ console.log(`\n 🔍 Semantic search: "${query}"\n`);
530
+ if (results.length === 0) {
531
+ console.log(" No matching tickets found.");
532
+ return;
533
+ }
534
+ for (const result of results) {
535
+ const payload = result.payload;
536
+ const ticketId = payload?.ticket_id || payload?.id || result.id;
537
+ const subject = payload?.subject || '(no subject)';
538
+ const status = payload?.status || '';
539
+ console.log(` [${(result.score * 100).toFixed(1)}%] #${ticketId} │ ${status} │ ${String(subject).slice(0, 45)}`);
540
+ }
541
+ console.log("");
542
+ }
543
+ catch (error) {
544
+ console.error("Error:", error.message);
545
+ process.exit(1);
546
+ }
547
+ });
548
+ // husky biz tickets knowledge "<query>"
549
+ ticketsCommand
550
+ .command("knowledge <query>")
551
+ .description("[PREBUILD] Search resolved tickets for solutions")
552
+ .option("-l, --limit <num>", "Number of results", "3")
553
+ .option("--json", "Output as JSON")
554
+ .action(async (query, options) => {
555
+ try {
556
+ const embeddings = EmbeddingService.fromConfig();
557
+ const qdrant = QdrantClient.fromConfig();
558
+ // 1. Create embedding from query
559
+ console.log(` Generating embedding...`);
560
+ const vector = await embeddings.embed(query);
561
+ // 2. Search learned tickets collection
562
+ console.log(` Searching ${LEARNED_COLLECTION}...`);
563
+ const results = await qdrant.search(LEARNED_COLLECTION, vector, parseInt(options.limit, 10));
564
+ if (options.json) {
565
+ console.log(JSON.stringify({
566
+ success: true,
567
+ query,
568
+ knowledge: results.map(r => ({
569
+ id: r.payload?.ticket_id || r.id,
570
+ score: r.score,
571
+ subject: r.payload?.subject,
572
+ resolution: r.payload?.resolution || r.payload?.solution,
573
+ })),
574
+ }, null, 2));
575
+ return;
576
+ }
577
+ console.log(`\n 📚 Knowledge search: "${query}"\n`);
578
+ if (results.length === 0) {
579
+ console.log(" No relevant knowledge found.");
580
+ return;
581
+ }
582
+ for (const result of results) {
583
+ const payload = result.payload;
584
+ const ticketId = payload?.ticket_id || payload?.id || result.id;
585
+ const subject = payload?.subject || '(no subject)';
586
+ const resolution = payload?.resolution || payload?.solution || '';
587
+ console.log(` [${(result.score * 100).toFixed(1)}%] #${ticketId}`);
588
+ console.log(` Subject: ${String(subject).slice(0, 60)}`);
589
+ if (resolution) {
590
+ console.log(` Resolution: ${String(resolution).slice(0, 100)}...`);
591
+ }
592
+ console.log("");
593
+ }
594
+ }
595
+ catch (error) {
596
+ console.error("Error:", error.message);
597
+ process.exit(1);
598
+ }
599
+ });
600
+ export default ticketsCommand;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Husky Biz Command
3
+ *
4
+ * Business operations for autonomous agents
5
+ * Integrates with Billbee, Zendesk, SeaTable, and Qdrant
6
+ */
7
+ import { Command } from "commander";
8
+ export declare const bizCommand: Command;
9
+ export default bizCommand;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Husky Biz Command
3
+ *
4
+ * Business operations for autonomous agents
5
+ * Integrates with Billbee, Zendesk, SeaTable, and Qdrant
6
+ */
7
+ import { Command } from "commander";
8
+ import { ordersCommand } from "./biz/orders.js";
9
+ import { productsCommand } from "./biz/products.js";
10
+ import { ticketsCommand } from "./biz/tickets.js";
11
+ import { customersCommand } from "./biz/customers.js";
12
+ import { seatableCommand } from "./biz/seatable.js";
13
+ import { qdrantCommand } from "./biz/qdrant.js";
14
+ export const bizCommand = new Command("biz")
15
+ .description("Business operations for autonomous agents")
16
+ .addCommand(ordersCommand)
17
+ .addCommand(productsCommand)
18
+ .addCommand(ticketsCommand)
19
+ .addCommand(customersCommand)
20
+ .addCommand(seatableCommand)
21
+ .addCommand(qdrantCommand);
22
+ export default bizCommand;
@@ -4,6 +4,19 @@ interface Config {
4
4
  apiKey?: string;
5
5
  workerId?: string;
6
6
  workerName?: string;
7
+ billbeeApiKey?: string;
8
+ billbeeUsername?: string;
9
+ billbeePassword?: string;
10
+ billbeeBaseUrl?: string;
11
+ zendeskSubdomain?: string;
12
+ zendeskEmail?: string;
13
+ zendeskApiToken?: string;
14
+ seatableApiToken?: string;
15
+ seatableServerUrl?: string;
16
+ qdrantUrl?: string;
17
+ qdrantApiKey?: string;
18
+ gcpProjectId?: string;
19
+ gcpLocation?: string;
7
20
  }
8
21
  export declare function getConfig(): Config;
9
22
  export declare function setConfig(key: "apiUrl" | "apiKey" | "workerId" | "workerName", value: string): void;