@simonfestl/husky-cli 1.26.0 → 1.29.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 (38) hide show
  1. package/dist/commands/biz/invoices.d.ts +11 -0
  2. package/dist/commands/biz/invoices.js +661 -0
  3. package/dist/commands/biz/shopify.d.ts +3 -0
  4. package/dist/commands/biz/shopify.js +592 -0
  5. package/dist/commands/biz/supplier-feed.d.ts +3 -0
  6. package/dist/commands/biz/supplier-feed.js +168 -0
  7. package/dist/commands/biz.js +5 -1
  8. package/dist/commands/config.d.ts +3 -2
  9. package/dist/commands/config.js +4 -3
  10. package/dist/commands/e2e.js +1 -1
  11. package/dist/commands/plan.d.ts +14 -0
  12. package/dist/commands/plan.js +219 -0
  13. package/dist/commands/sop.d.ts +3 -0
  14. package/dist/commands/sop.js +458 -0
  15. package/dist/commands/task.js +7 -0
  16. package/dist/index.js +2 -0
  17. package/dist/lib/biz/gcs-upload.d.ts +86 -0
  18. package/dist/lib/biz/gcs-upload.js +189 -0
  19. package/dist/lib/biz/index.d.ts +5 -0
  20. package/dist/lib/biz/index.js +3 -0
  21. package/dist/lib/biz/invoice-extractor-registry.d.ts +22 -0
  22. package/dist/lib/biz/invoice-extractor-registry.js +416 -0
  23. package/dist/lib/biz/invoice-extractor-types.d.ts +127 -0
  24. package/dist/lib/biz/invoice-extractor-types.js +6 -0
  25. package/dist/lib/biz/pattern-detection.d.ts +48 -0
  26. package/dist/lib/biz/pattern-detection.js +205 -0
  27. package/dist/lib/biz/resolved-tickets.d.ts +86 -0
  28. package/dist/lib/biz/resolved-tickets.js +250 -0
  29. package/dist/lib/biz/shopify.d.ts +196 -0
  30. package/dist/lib/biz/shopify.js +429 -0
  31. package/dist/lib/biz/supplier-feed-types.d.ts +96 -0
  32. package/dist/lib/biz/supplier-feed-types.js +46 -0
  33. package/dist/lib/biz/supplier-feed.d.ts +32 -0
  34. package/dist/lib/biz/supplier-feed.js +244 -0
  35. package/dist/lib/permissions.d.ts +2 -1
  36. package/dist/types/roles.d.ts +3 -0
  37. package/dist/types/roles.js +14 -0
  38. package/package.json +1 -1
@@ -0,0 +1,250 @@
1
+ import { QdrantClient } from './qdrant.js';
2
+ import { EmbeddingService } from './embeddings.js';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ // ============================================================================
5
+ // Types
6
+ // ============================================================================
7
+ export const RESOLUTION_OUTCOMES = ['positive', 'negative', 'neutral', 'escalated'];
8
+ // ============================================================================
9
+ // Constants
10
+ // ============================================================================
11
+ const RESOLVED_COLLECTION = 'tickets_resolved';
12
+ // ============================================================================
13
+ // Resolved Tickets Service
14
+ // ============================================================================
15
+ export class ResolvedTicketsService {
16
+ qdrant;
17
+ embeddings;
18
+ constructor() {
19
+ this.qdrant = QdrantClient.fromConfig();
20
+ this.embeddings = EmbeddingService.fromConfig();
21
+ }
22
+ async log(input) {
23
+ const id = `rt_${input.ticket_id}_${uuidv4().slice(0, 6)}`;
24
+ const now = new Date().toISOString();
25
+ const ticket = {
26
+ id,
27
+ ticket_id: input.ticket_id,
28
+ problem: input.problem,
29
+ problem_category: input.problem_category,
30
+ resolution: {
31
+ outcome: input.outcome,
32
+ summary: input.summary,
33
+ steps: input.steps.map((s, i) => ({
34
+ ...s,
35
+ order: s.order || i + 1,
36
+ })),
37
+ sop_reference: input.sop_reference,
38
+ },
39
+ agent_id: input.agent_id,
40
+ files: input.files || [],
41
+ tags: input.tags || [],
42
+ created_at: now,
43
+ customer_satisfaction: input.customer_satisfaction,
44
+ metadata: input.metadata,
45
+ };
46
+ const embeddingText = this.buildEmbeddingText(ticket);
47
+ const vector = await this.embeddings.embed(embeddingText);
48
+ await this.qdrant.upsertOne(RESOLVED_COLLECTION, id, vector, {
49
+ type: 'resolved_ticket',
50
+ ...this.ticketToPayload(ticket),
51
+ });
52
+ return ticket;
53
+ }
54
+ async get(id) {
55
+ const point = await this.qdrant.getPoint(RESOLVED_COLLECTION, id);
56
+ if (!point || !point.payload)
57
+ return null;
58
+ const payload = point.payload;
59
+ if (payload.type !== 'resolved_ticket')
60
+ return null;
61
+ return this.payloadToTicket(payload);
62
+ }
63
+ async getByTicketId(ticketId) {
64
+ const results = await this.qdrant.scroll(RESOLVED_COLLECTION, {
65
+ filter: {
66
+ must: [
67
+ { key: 'type', match: { value: 'resolved_ticket' } },
68
+ { key: 'ticket_id', match: { value: ticketId } },
69
+ ],
70
+ },
71
+ limit: 1,
72
+ with_payload: true,
73
+ });
74
+ if (results.length === 0)
75
+ return null;
76
+ return this.payloadToTicket(results[0].payload);
77
+ }
78
+ async search(query, options = {}) {
79
+ const limit = options.limit || 5;
80
+ const minScore = options.minScore || 0.5;
81
+ const vector = await this.embeddings.embed(query);
82
+ const mustConditions = [
83
+ { key: 'type', match: { value: 'resolved_ticket' } }
84
+ ];
85
+ if (options.category) {
86
+ mustConditions.push({ key: 'problem_category', match: { value: options.category } });
87
+ }
88
+ if (options.outcome) {
89
+ mustConditions.push({ key: 'outcome', match: { value: options.outcome } });
90
+ }
91
+ const results = await this.qdrant.search(RESOLVED_COLLECTION, vector, limit, {
92
+ filter: { must: mustConditions },
93
+ scoreThreshold: minScore,
94
+ });
95
+ return results
96
+ .map(r => {
97
+ const ticket = this.payloadToTicket(r.payload);
98
+ if (!ticket)
99
+ return null;
100
+ return { ticket, score: r.score };
101
+ })
102
+ .filter((r) => r !== null);
103
+ }
104
+ async list(options = {}) {
105
+ const limit = options.limit || 50;
106
+ const mustConditions = [
107
+ { key: 'type', match: { value: 'resolved_ticket' } }
108
+ ];
109
+ if (options.category) {
110
+ mustConditions.push({ key: 'problem_category', match: { value: options.category } });
111
+ }
112
+ if (options.outcome) {
113
+ mustConditions.push({ key: 'outcome', match: { value: options.outcome } });
114
+ }
115
+ if (options.agent_id) {
116
+ mustConditions.push({ key: 'agent_id', match: { value: options.agent_id } });
117
+ }
118
+ const results = await this.qdrant.scroll(RESOLVED_COLLECTION, {
119
+ filter: { must: mustConditions },
120
+ limit,
121
+ with_payload: true,
122
+ });
123
+ return results
124
+ .map(r => this.payloadToTicket(r.payload))
125
+ .filter((t) => t !== null);
126
+ }
127
+ async findSimilarProblems(problem, options = {}) {
128
+ const searchOptions = {
129
+ limit: options.limit || 5,
130
+ minScore: options.minScore || 0.6,
131
+ };
132
+ if (options.onlyPositive) {
133
+ searchOptions.outcome = 'positive';
134
+ }
135
+ return this.search(problem, searchOptions);
136
+ }
137
+ async getCategories() {
138
+ const tickets = await this.list({ limit: 1000 });
139
+ const categoryMap = new Map();
140
+ for (const t of tickets) {
141
+ const count = categoryMap.get(t.problem_category) || 0;
142
+ categoryMap.set(t.problem_category, count + 1);
143
+ }
144
+ return Array.from(categoryMap.entries())
145
+ .map(([category, count]) => ({ category, count }))
146
+ .sort((a, b) => b.count - a.count);
147
+ }
148
+ async stats() {
149
+ const tickets = await this.list({ limit: 1000 });
150
+ const byOutcome = {
151
+ positive: 0,
152
+ negative: 0,
153
+ neutral: 0,
154
+ escalated: 0,
155
+ };
156
+ const byCategory = {};
157
+ let totalSteps = 0;
158
+ for (const t of tickets) {
159
+ byOutcome[t.resolution.outcome]++;
160
+ byCategory[t.problem_category] = (byCategory[t.problem_category] || 0) + 1;
161
+ totalSteps += t.resolution.steps.length;
162
+ }
163
+ return {
164
+ total: tickets.length,
165
+ byOutcome,
166
+ byCategory,
167
+ avgSteps: tickets.length > 0 ? totalSteps / tickets.length : 0,
168
+ };
169
+ }
170
+ async delete(id) {
171
+ await this.qdrant.deletePoints(RESOLVED_COLLECTION, [id]);
172
+ }
173
+ buildEmbeddingText(ticket) {
174
+ const stepsText = ticket.resolution.steps
175
+ .map(s => `${s.order}. ${s.action}: ${s.description}`)
176
+ .join('\n');
177
+ return [
178
+ `Problem: ${ticket.problem}`,
179
+ `Category: ${ticket.problem_category}`,
180
+ `Resolution: ${ticket.resolution.summary}`,
181
+ `Outcome: ${ticket.resolution.outcome}`,
182
+ `Steps:\n${stepsText}`,
183
+ ].join('\n');
184
+ }
185
+ ticketToPayload(ticket) {
186
+ return {
187
+ id: ticket.id,
188
+ ticket_id: ticket.ticket_id,
189
+ problem: ticket.problem,
190
+ problem_category: ticket.problem_category,
191
+ outcome: ticket.resolution.outcome,
192
+ summary: ticket.resolution.summary,
193
+ steps: JSON.stringify(ticket.resolution.steps),
194
+ sop_reference: ticket.resolution.sop_reference || null,
195
+ agent_id: ticket.agent_id,
196
+ files: ticket.files || [],
197
+ tags: ticket.tags,
198
+ created_at: ticket.created_at,
199
+ customer_satisfaction: ticket.customer_satisfaction ?? null,
200
+ metadata: ticket.metadata ? JSON.stringify(ticket.metadata) : null,
201
+ };
202
+ }
203
+ payloadToTicket(payload) {
204
+ if (!payload || payload.type !== 'resolved_ticket')
205
+ return null;
206
+ let steps = [];
207
+ try {
208
+ if (typeof payload.steps === 'string') {
209
+ steps = JSON.parse(payload.steps);
210
+ }
211
+ else if (Array.isArray(payload.steps)) {
212
+ steps = payload.steps;
213
+ }
214
+ }
215
+ catch {
216
+ steps = [];
217
+ }
218
+ let metadata;
219
+ try {
220
+ if (typeof payload.metadata === 'string') {
221
+ metadata = JSON.parse(payload.metadata);
222
+ }
223
+ else if (payload.metadata && typeof payload.metadata === 'object') {
224
+ metadata = payload.metadata;
225
+ }
226
+ }
227
+ catch {
228
+ metadata = undefined;
229
+ }
230
+ return {
231
+ id: String(payload.id),
232
+ ticket_id: Number(payload.ticket_id),
233
+ problem: String(payload.problem || ''),
234
+ problem_category: String(payload.problem_category || 'general'),
235
+ resolution: {
236
+ outcome: payload.outcome || 'neutral',
237
+ summary: String(payload.summary || ''),
238
+ steps,
239
+ sop_reference: payload.sop_reference ? String(payload.sop_reference) : undefined,
240
+ },
241
+ agent_id: String(payload.agent_id || ''),
242
+ files: payload.files || [],
243
+ tags: payload.tags || [],
244
+ created_at: String(payload.created_at || new Date().toISOString()),
245
+ customer_satisfaction: payload.customer_satisfaction ? Number(payload.customer_satisfaction) : undefined,
246
+ metadata,
247
+ };
248
+ }
249
+ }
250
+ export default ResolvedTicketsService;
@@ -0,0 +1,196 @@
1
+ export interface ShopifyConfig {
2
+ shopDomain: string;
3
+ accessToken: string;
4
+ apiVersion?: string;
5
+ }
6
+ export interface Order {
7
+ id: string;
8
+ name: string;
9
+ email: string;
10
+ createdAt: Date;
11
+ updatedAt: Date;
12
+ fulfillmentStatus: string | null;
13
+ financialStatus: string;
14
+ totalPrice: string;
15
+ currency: string;
16
+ customer: Customer;
17
+ lineItems: LineItem[];
18
+ fulfillments: Fulfillment[];
19
+ shippingAddress?: Address;
20
+ billingAddress?: Address;
21
+ tags: string[];
22
+ note: string | null;
23
+ }
24
+ export interface Customer {
25
+ id: string;
26
+ email: string;
27
+ firstName: string | null;
28
+ lastName: string | null;
29
+ phone: string | null;
30
+ ordersCount: number;
31
+ totalSpent: string;
32
+ createdAt: Date;
33
+ updatedAt: Date;
34
+ tags: string[];
35
+ acceptsMarketing: boolean;
36
+ defaultAddress?: Address;
37
+ }
38
+ export interface Product {
39
+ id: string;
40
+ title: string;
41
+ handle: string;
42
+ descriptionHtml: string;
43
+ vendor: string;
44
+ productType: string;
45
+ status: 'ACTIVE' | 'ARCHIVED' | 'DRAFT';
46
+ tags: string[];
47
+ variants: ProductVariant[];
48
+ images: ProductImage[];
49
+ createdAt: Date;
50
+ updatedAt: Date;
51
+ }
52
+ export interface ProductVariant {
53
+ id: string;
54
+ title: string;
55
+ sku: string | null;
56
+ price: string;
57
+ compareAtPrice: string | null;
58
+ inventoryQuantity: number;
59
+ weight: number | null;
60
+ weightUnit: string;
61
+ barcode: string | null;
62
+ }
63
+ export interface ProductImage {
64
+ id: string;
65
+ url: string;
66
+ altText: string | null;
67
+ width: number;
68
+ height: number;
69
+ }
70
+ export interface LineItem {
71
+ id: string;
72
+ title: string;
73
+ quantity: number;
74
+ price: string;
75
+ sku: string | null;
76
+ variantId: string | null;
77
+ productId: string | null;
78
+ }
79
+ export interface Fulfillment {
80
+ id: string;
81
+ status: string;
82
+ createdAt: Date;
83
+ updatedAt: Date;
84
+ trackingCompany: string | null;
85
+ trackingNumber: string | null;
86
+ trackingUrl: string | null;
87
+ }
88
+ export interface Address {
89
+ address1: string | null;
90
+ address2: string | null;
91
+ city: string | null;
92
+ province: string | null;
93
+ country: string | null;
94
+ zip: string | null;
95
+ phone: string | null;
96
+ firstName: string | null;
97
+ lastName: string | null;
98
+ company: string | null;
99
+ }
100
+ export interface OrderQueryParams {
101
+ status?: 'any' | 'open' | 'closed' | 'cancelled';
102
+ fulfillmentStatus?: 'any' | 'shipped' | 'partial' | 'unshipped' | 'unfulfilled';
103
+ financialStatus?: 'any' | 'authorized' | 'pending' | 'paid' | 'partially_paid' | 'refunded' | 'voided' | 'partially_refunded';
104
+ createdAtMin?: Date;
105
+ createdAtMax?: Date;
106
+ updatedAtMin?: Date;
107
+ updatedAtMax?: Date;
108
+ limit?: number;
109
+ sinceId?: string;
110
+ ids?: string[];
111
+ }
112
+ export interface CustomerQueryParams {
113
+ email?: string;
114
+ phone?: string;
115
+ createdAtMin?: Date;
116
+ createdAtMax?: Date;
117
+ updatedAtMin?: Date;
118
+ updatedAtMax?: Date;
119
+ limit?: number;
120
+ sinceId?: string;
121
+ ids?: string[];
122
+ tags?: string[];
123
+ }
124
+ export interface ProductQueryParams {
125
+ status?: 'ACTIVE' | 'ARCHIVED' | 'DRAFT';
126
+ vendor?: string;
127
+ productType?: string;
128
+ createdAtMin?: Date;
129
+ createdAtMax?: Date;
130
+ updatedAtMin?: Date;
131
+ updatedAtMax?: Date;
132
+ limit?: number;
133
+ sinceId?: string;
134
+ ids?: string[];
135
+ handle?: string;
136
+ }
137
+ export interface ProductUpdate {
138
+ title?: string;
139
+ descriptionHtml?: string;
140
+ vendor?: string;
141
+ productType?: string;
142
+ status?: 'ACTIVE' | 'ARCHIVED' | 'DRAFT';
143
+ tags?: string[];
144
+ }
145
+ export interface ShopMetrics {
146
+ ordersToday: number;
147
+ revenueToday: number;
148
+ averageOrderValue: number;
149
+ newCustomersToday: number;
150
+ returningCustomersToday: number;
151
+ topProducts: Array<{
152
+ productId: string;
153
+ title: string;
154
+ quantity: number;
155
+ revenue: number;
156
+ }>;
157
+ currencyCode: string;
158
+ }
159
+ export interface DateRange {
160
+ start: Date;
161
+ end: Date;
162
+ }
163
+ export declare class ShopifyClient {
164
+ private shopDomain;
165
+ private accessToken;
166
+ private apiVersion;
167
+ private baseUrl;
168
+ constructor(config: ShopifyConfig);
169
+ static fromConfig(): ShopifyClient;
170
+ private request;
171
+ getOrders(params?: OrderQueryParams): Promise<Order[]>;
172
+ getOrder(id: string): Promise<Order>;
173
+ searchOrders(query: string, limit?: number): Promise<Order[]>;
174
+ getCustomers(params?: CustomerQueryParams): Promise<Customer[]>;
175
+ getCustomer(id: string): Promise<Customer>;
176
+ getCustomerOrderCount(customerId: string): Promise<number>;
177
+ getCustomerOrders(customerId: string, limit?: number): Promise<Order[]>;
178
+ searchCustomers(query: string): Promise<Customer[]>;
179
+ getProducts(params?: ProductQueryParams): Promise<Product[]>;
180
+ getProduct(id: string): Promise<Product>;
181
+ updateProduct(id: string, updates: ProductUpdate): Promise<Product>;
182
+ searchProducts(query: string): Promise<Product[]>;
183
+ getLowStockProducts(threshold?: number): Promise<Product[]>;
184
+ getShopMetrics(dateRange: DateRange): Promise<ShopMetrics>;
185
+ getDailyMetrics(): Promise<ShopMetrics>;
186
+ getWeeklyMetrics(): Promise<ShopMetrics>;
187
+ testConnection(): Promise<{
188
+ success: boolean;
189
+ shopName?: string;
190
+ error?: string;
191
+ }>;
192
+ private mapOrder;
193
+ private mapCustomer;
194
+ private mapProduct;
195
+ }
196
+ export default ShopifyClient;