@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
package/dist/index.js CHANGED
@@ -13,17 +13,19 @@ import { workflowCommand } from "./commands/workflow.js";
13
13
  import { julesCommand } from "./commands/jules.js";
14
14
  import { vmCommand } from "./commands/vm.js";
15
15
  import { vmConfigCommand } from "./commands/vm-config.js";
16
+ import { processCommand } from "./commands/process.js";
16
17
  import { settingsCommand } from "./commands/settings.js";
17
18
  import { strategyCommand } from "./commands/strategy.js";
18
19
  import { completionCommand } from "./commands/completion.js";
19
20
  import { worktreeCommand } from "./commands/worktree.js";
20
21
  import { workerCommand } from "./commands/worker.js";
22
+ import { bizCommand } from "./commands/biz.js";
21
23
  import { runInteractiveMode } from "./commands/interactive.js";
22
24
  const program = new Command();
23
25
  program
24
26
  .name("husky")
25
27
  .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
26
- .version("0.8.1");
28
+ .version("0.6.0");
27
29
  program.addCommand(taskCommand);
28
30
  program.addCommand(configCommand);
29
31
  program.addCommand(agentCommand);
@@ -37,11 +39,13 @@ program.addCommand(workflowCommand);
37
39
  program.addCommand(julesCommand);
38
40
  program.addCommand(vmCommand);
39
41
  program.addCommand(vmConfigCommand);
42
+ program.addCommand(processCommand);
40
43
  program.addCommand(settingsCommand);
41
44
  program.addCommand(strategyCommand);
42
45
  program.addCommand(completionCommand);
43
46
  program.addCommand(worktreeCommand);
44
47
  program.addCommand(workerCommand);
48
+ program.addCommand(bizCommand);
45
49
  // Check if no command was provided - run interactive mode
46
50
  if (process.argv.length <= 2) {
47
51
  runInteractiveMode();
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Billbee API Types
3
+ * Full coverage from TigerV0 integrations
4
+ */
5
+ export interface BillbeeConfig {
6
+ API_KEY: string;
7
+ USERNAME: string;
8
+ PASSWORD: string;
9
+ BASE_URL: string;
10
+ }
11
+ export interface Paging {
12
+ Page: number;
13
+ TotalPages: number;
14
+ TotalRows: number;
15
+ PageSize: number;
16
+ }
17
+ export interface BillbeePaginatedResponse<T> {
18
+ Paging: Paging;
19
+ Data: T[];
20
+ }
21
+ export interface BillbeeResponse<T> {
22
+ Data: T;
23
+ }
24
+ export interface Address {
25
+ BillbeeId?: number;
26
+ FirstName?: string;
27
+ LastName?: string;
28
+ Company?: string;
29
+ NameAddition?: string;
30
+ Street?: string;
31
+ HouseNumber?: string;
32
+ Zip?: string;
33
+ City?: string;
34
+ State?: string;
35
+ CountryISO2?: string;
36
+ Country?: string;
37
+ Email?: string;
38
+ Phone?: string;
39
+ }
40
+ export interface OrderItem {
41
+ BillbeeId?: number;
42
+ TransactionId?: string;
43
+ Product?: {
44
+ Id?: string;
45
+ Title?: string;
46
+ Weight?: number;
47
+ SKU?: string;
48
+ EAN?: string;
49
+ };
50
+ Quantity: number;
51
+ TotalPrice: number;
52
+ TaxAmount?: number;
53
+ TaxIndex?: number;
54
+ Discount?: number;
55
+ Attributes?: Array<{
56
+ Id?: string;
57
+ Name?: string;
58
+ Value?: string;
59
+ }>;
60
+ }
61
+ export interface Payment {
62
+ BillbeeId?: number;
63
+ TransactionId?: string;
64
+ PayDate?: string;
65
+ PaymentType?: number;
66
+ SourceTechnology?: string;
67
+ SourceText?: string;
68
+ PayValue?: number;
69
+ Purpose?: string;
70
+ Name?: string;
71
+ }
72
+ export interface Order {
73
+ BillbeeId?: number;
74
+ Id: number | null;
75
+ BillBeeOrderId?: number;
76
+ OrderNumber: string | null;
77
+ State?: number;
78
+ VatMode?: number;
79
+ CreatedAt?: string;
80
+ ShippedAt?: string;
81
+ ConfirmedAt?: string;
82
+ PayedAt?: string;
83
+ SellerComment?: string;
84
+ Comments?: Array<{
85
+ Id?: number;
86
+ FromCustomer?: boolean;
87
+ Text?: string;
88
+ Name?: string;
89
+ Created?: string;
90
+ }>;
91
+ InvoiceNumber?: string;
92
+ InvoiceCreatedAt?: string;
93
+ InvoiceDate?: string;
94
+ Currency?: string;
95
+ UpdatedAt?: string;
96
+ TaxRate1?: number;
97
+ TaxRate2?: number;
98
+ TotalCost?: number;
99
+ ShippingCost?: number;
100
+ OrderItems?: OrderItem[];
101
+ Seller?: {
102
+ Platform?: string;
103
+ BillbeeShopName?: string;
104
+ BillbeeShopId?: number;
105
+ };
106
+ Buyer?: {
107
+ BillbeeId?: number;
108
+ Email?: string;
109
+ };
110
+ InvoiceAddress?: Address;
111
+ ShippingAddress?: Address;
112
+ Payments?: Payment[];
113
+ Tags?: string[];
114
+ }
115
+ export interface OrdersQueryParams {
116
+ page?: number;
117
+ pageSize?: number;
118
+ minOrderDate?: string;
119
+ maxOrderDate?: string;
120
+ shopId?: number[];
121
+ orderStateId?: number[];
122
+ tag?: string[];
123
+ minPayDate?: string;
124
+ maxPayDate?: string;
125
+ includePositions?: boolean;
126
+ }
127
+ export interface Stock {
128
+ Name: string;
129
+ StockId: number;
130
+ StockCurrent: number;
131
+ StockWarning?: number;
132
+ StockCode?: string;
133
+ UnfulfilledAmount?: number;
134
+ StockDesired?: number;
135
+ }
136
+ export interface ProductImage {
137
+ Url: string;
138
+ Id?: number;
139
+ IsDefaultImage?: boolean;
140
+ Position?: number;
141
+ }
142
+ export interface ProductSource {
143
+ Id?: number;
144
+ Source?: string;
145
+ SourceId?: string;
146
+ ApiAccountName?: string;
147
+ ApiAccountId?: number;
148
+ ExportFactor?: number;
149
+ StockSyncInactive?: boolean;
150
+ StockSyncMin?: number;
151
+ StockSyncMax?: number;
152
+ UnitsPerItem?: number;
153
+ }
154
+ export interface Product {
155
+ Id?: number;
156
+ Title?: string;
157
+ InvoiceText?: string;
158
+ ShortDescription?: string;
159
+ Description?: string;
160
+ SKU?: string;
161
+ EAN?: string;
162
+ TaricNumber?: string;
163
+ CountryOfOrigin?: string;
164
+ Price?: number;
165
+ CostPrice?: number;
166
+ VAT?: string;
167
+ VATIndex?: number;
168
+ Weight?: number;
169
+ WeightNet?: number;
170
+ LowStock?: boolean;
171
+ StockCurrent?: number;
172
+ StockDesired?: number;
173
+ StockWarning?: number;
174
+ Stocks?: Stock[];
175
+ Images?: ProductImage[];
176
+ Manufacturer?: string;
177
+ Type?: number;
178
+ Category1?: string;
179
+ Category2?: string;
180
+ Category3?: string;
181
+ Unit?: number;
182
+ UnitsPerItem?: number;
183
+ SoldAmountLast30Days?: number;
184
+ Sources?: ProductSource[];
185
+ IsDigital?: boolean;
186
+ IsCustomizable?: boolean;
187
+ DeliveryTime?: number;
188
+ Recipient?: number;
189
+ Occasion?: number;
190
+ Condition?: number;
191
+ WidthCm?: number;
192
+ LengthCm?: number;
193
+ HeightCm?: number;
194
+ BillOfMaterial?: Array<{
195
+ ProductId?: number;
196
+ Amount?: number;
197
+ ArticleId?: number;
198
+ SKU?: string;
199
+ }>;
200
+ }
201
+ export interface ProductsQueryParams {
202
+ page?: number;
203
+ pageSize?: number;
204
+ minCreatedAt?: string;
205
+ type?: number;
206
+ includeCategories?: boolean;
207
+ }
208
+ export interface ProductUpdate {
209
+ Title?: string;
210
+ InvoiceText?: string;
211
+ ShortDescription?: string;
212
+ Description?: string;
213
+ SKU?: string;
214
+ EAN?: string;
215
+ TaricNumber?: string;
216
+ CountryOfOrigin?: string;
217
+ Price?: number;
218
+ CostPrice?: number;
219
+ VAT?: string;
220
+ VATIndex?: number;
221
+ Weight?: number;
222
+ WeightNet?: number;
223
+ Manufacturer?: string;
224
+ Type?: number;
225
+ Category1?: string;
226
+ Category2?: string;
227
+ Category3?: string;
228
+ Unit?: number;
229
+ UnitsPerItem?: number;
230
+ Images?: ProductImage[];
231
+ IsDigital?: boolean;
232
+ IsCustomizable?: boolean;
233
+ DeliveryTime?: number;
234
+ WidthCm?: number;
235
+ LengthCm?: number;
236
+ HeightCm?: number;
237
+ LowStock?: boolean;
238
+ StockDesired?: number;
239
+ StockWarning?: number;
240
+ }
241
+ export declare const OrderState: {
242
+ readonly Ordered: 1;
243
+ readonly Confirmed: 2;
244
+ readonly Paid: 3;
245
+ readonly Shipped: 4;
246
+ readonly ReclamationOrReturn: 5;
247
+ readonly Deleted: 6;
248
+ readonly Completed: 7;
249
+ readonly Cancelled: 8;
250
+ readonly Archived: 9;
251
+ readonly NotUsed: 10;
252
+ readonly Demanded: 11;
253
+ readonly PackingStarted: 12;
254
+ readonly Ready: 13;
255
+ readonly Clarification: 14;
256
+ readonly Warning: 15;
257
+ };
258
+ export type OrderStateId = (typeof OrderState)[keyof typeof OrderState];
259
+ export declare const OrderStateLabels: Record<number, string>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Billbee API Types
3
+ * Full coverage from TigerV0 integrations
4
+ */
5
+ // ============================================================================
6
+ // Order States
7
+ // ============================================================================
8
+ export const OrderState = {
9
+ Ordered: 1,
10
+ Confirmed: 2,
11
+ Paid: 3,
12
+ Shipped: 4,
13
+ ReclamationOrReturn: 5,
14
+ Deleted: 6,
15
+ Completed: 7,
16
+ Cancelled: 8,
17
+ Archived: 9,
18
+ NotUsed: 10,
19
+ Demanded: 11,
20
+ PackingStarted: 12,
21
+ Ready: 13,
22
+ Clarification: 14,
23
+ Warning: 15,
24
+ };
25
+ export const OrderStateLabels = {
26
+ 1: 'ordered',
27
+ 2: 'confirmed',
28
+ 3: 'paid',
29
+ 4: 'shipped',
30
+ 5: 'return',
31
+ 6: 'deleted',
32
+ 7: 'completed',
33
+ 8: 'cancelled',
34
+ 9: 'archived',
35
+ 10: 'not_used',
36
+ 11: 'demanded',
37
+ 12: 'packing',
38
+ 13: 'ready',
39
+ 14: 'clarification',
40
+ 15: 'warning',
41
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Billbee API Client
3
+ * Extended for v0.9 with full products support
4
+ */
5
+ import { BillbeeConfig, BillbeePaginatedResponse, BillbeeResponse, Order, OrdersQueryParams, Product, ProductsQueryParams, ProductUpdate } from './billbee-types.js';
6
+ export declare class BillbeeClient {
7
+ private config;
8
+ constructor(config: BillbeeConfig);
9
+ /**
10
+ * Create client from Husky config
11
+ */
12
+ static fromConfig(): BillbeeClient;
13
+ /**
14
+ * Make authenticated request (with rate limit awareness: 2 req/sec max)
15
+ */
16
+ private request;
17
+ listOrders(params?: OrdersQueryParams): Promise<BillbeePaginatedResponse<Order>>;
18
+ getOrder(id: number | string): Promise<BillbeeResponse<Order>>;
19
+ updateOrder(id: number | string, data: Partial<Order>): Promise<BillbeeResponse<Order>>;
20
+ addOrderTags(orderId: number, tags: string[]): Promise<void>;
21
+ removeOrderTags(orderId: number, tags: string[]): Promise<void>;
22
+ listProducts(params?: ProductsQueryParams): Promise<BillbeePaginatedResponse<Product>>;
23
+ getProduct(id: number | string): Promise<BillbeeResponse<Product>>;
24
+ getProductBySku(sku: string): Promise<Product | null>;
25
+ updateProduct(id: number | string, data: ProductUpdate): Promise<BillbeeResponse<Product>>;
26
+ updateProductBySku(sku: string, data: ProductUpdate): Promise<BillbeeResponse<Product>>;
27
+ /**
28
+ * Search customers by email (scans orders for matching buyers)
29
+ */
30
+ findCustomerByEmail(email: string): Promise<{
31
+ buyer: Order['Buyer'];
32
+ address: Order['InvoiceAddress'];
33
+ orders: Order[];
34
+ } | null>;
35
+ }
36
+ export type { Address } from './billbee-types.js';
37
+ export default BillbeeClient;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Billbee API Client
3
+ * Extended for v0.9 with full products support
4
+ */
5
+ import { getConfig } from '../../commands/config.js';
6
+ export class BillbeeClient {
7
+ config;
8
+ constructor(config) {
9
+ this.config = config;
10
+ }
11
+ /**
12
+ * Create client from Husky config
13
+ */
14
+ static fromConfig() {
15
+ const config = getConfig();
16
+ const billbeeConfig = {
17
+ API_KEY: config.billbeeApiKey || process.env.BILLBEE_API_KEY || '',
18
+ USERNAME: config.billbeeUsername || process.env.BILLBEE_USERNAME || '',
19
+ PASSWORD: config.billbeePassword || process.env.BILLBEE_PASSWORD || '',
20
+ BASE_URL: config.billbeeBaseUrl || process.env.BILLBEE_BASE_URL || 'https://app.billbee.io/api/v1',
21
+ };
22
+ if (!billbeeConfig.API_KEY || !billbeeConfig.USERNAME || !billbeeConfig.PASSWORD) {
23
+ throw new Error('Missing Billbee credentials. Configure with:\n' +
24
+ ' husky config set billbee-api-key <key>\n' +
25
+ ' husky config set billbee-username <email>\n' +
26
+ ' husky config set billbee-password <password>');
27
+ }
28
+ return new BillbeeClient(billbeeConfig);
29
+ }
30
+ /**
31
+ * Make authenticated request (with rate limit awareness: 2 req/sec max)
32
+ */
33
+ async request(endpoint, options = {}) {
34
+ const url = `${this.config.BASE_URL}${endpoint}`;
35
+ const auth = Buffer.from(`${this.config.USERNAME}:${this.config.PASSWORD}`).toString('base64');
36
+ const response = await fetch(url, {
37
+ ...options,
38
+ headers: {
39
+ 'X-Billbee-Api-Key': this.config.API_KEY,
40
+ 'Authorization': `Basic ${auth}`,
41
+ 'Content-Type': 'application/json',
42
+ ...options.headers,
43
+ },
44
+ });
45
+ if (!response.ok) {
46
+ const error = await response.text();
47
+ throw new Error(`Billbee API Error ${response.status}: ${error}`);
48
+ }
49
+ return response.json();
50
+ }
51
+ // =========================================================================
52
+ // ORDERS
53
+ // =========================================================================
54
+ async listOrders(params) {
55
+ const query = new URLSearchParams();
56
+ if (params?.page)
57
+ query.set('page', String(params.page));
58
+ if (params?.pageSize)
59
+ query.set('pageSize', String(params.pageSize));
60
+ if (params?.minOrderDate)
61
+ query.set('minOrderDate', params.minOrderDate);
62
+ if (params?.maxOrderDate)
63
+ query.set('maxOrderDate', params.maxOrderDate);
64
+ if (params?.includePositions !== undefined) {
65
+ query.set('includePositions', String(params.includePositions));
66
+ }
67
+ if (params?.orderStateId && params.orderStateId.length > 0) {
68
+ params.orderStateId.forEach(id => query.append('orderStateId', String(id)));
69
+ }
70
+ if (params?.tag && params.tag.length > 0) {
71
+ params.tag.forEach(t => query.append('tag', t));
72
+ }
73
+ const endpoint = `/orders${query.toString() ? `?${query}` : ''}`;
74
+ return this.request(endpoint);
75
+ }
76
+ async getOrder(id) {
77
+ return this.request(`/orders/${id}`);
78
+ }
79
+ async updateOrder(id, data) {
80
+ return this.request(`/orders/${id}`, {
81
+ method: 'PUT',
82
+ body: JSON.stringify(data),
83
+ });
84
+ }
85
+ async addOrderTags(orderId, tags) {
86
+ await this.request(`/orders/${orderId}/tags`, {
87
+ method: 'POST',
88
+ body: JSON.stringify({ Tags: tags }),
89
+ });
90
+ }
91
+ async removeOrderTags(orderId, tags) {
92
+ await this.request(`/orders/${orderId}/tags`, {
93
+ method: 'DELETE',
94
+ body: JSON.stringify({ Tags: tags }),
95
+ });
96
+ }
97
+ // =========================================================================
98
+ // PRODUCTS
99
+ // =========================================================================
100
+ async listProducts(params) {
101
+ const query = new URLSearchParams();
102
+ if (params?.page)
103
+ query.set('page', String(params.page));
104
+ if (params?.pageSize)
105
+ query.set('pageSize', String(params.pageSize));
106
+ if (params?.minCreatedAt)
107
+ query.set('minCreatedAt', params.minCreatedAt);
108
+ if (params?.type !== undefined)
109
+ query.set('type', String(params.type));
110
+ const endpoint = `/products${query.toString() ? `?${query}` : ''}`;
111
+ return this.request(endpoint);
112
+ }
113
+ async getProduct(id) {
114
+ return this.request(`/products/${id}`);
115
+ }
116
+ async getProductBySku(sku) {
117
+ try {
118
+ const response = await this.request(`/products/query?LookupBy=sku&LookupTerm=${encodeURIComponent(sku)}`);
119
+ return response.Data;
120
+ }
121
+ catch (error) {
122
+ if (error instanceof Error && error.message.includes('404')) {
123
+ return null;
124
+ }
125
+ throw error;
126
+ }
127
+ }
128
+ async updateProduct(id, data) {
129
+ return this.request(`/products/${id}`, {
130
+ method: 'PATCH',
131
+ body: JSON.stringify(data),
132
+ });
133
+ }
134
+ async updateProductBySku(sku, data) {
135
+ const product = await this.getProductBySku(sku);
136
+ if (!product || !product.Id) {
137
+ throw new Error(`Product with SKU "${sku}" not found`);
138
+ }
139
+ return this.updateProduct(product.Id, data);
140
+ }
141
+ // =========================================================================
142
+ // CUSTOMERS (from Orders)
143
+ // =========================================================================
144
+ /**
145
+ * Search customers by email (scans orders for matching buyers)
146
+ */
147
+ async findCustomerByEmail(email) {
148
+ const response = await this.listOrders({
149
+ pageSize: 100,
150
+ includePositions: false,
151
+ });
152
+ const matchingOrders = response.Data.filter(order => order.Buyer?.Email?.toLowerCase() === email.toLowerCase() ||
153
+ order.InvoiceAddress?.Email?.toLowerCase() === email.toLowerCase());
154
+ if (matchingOrders.length === 0) {
155
+ return null;
156
+ }
157
+ const latestOrder = matchingOrders[0];
158
+ return {
159
+ buyer: latestOrder.Buyer,
160
+ address: latestOrder.InvoiceAddress || {},
161
+ orders: matchingOrders,
162
+ };
163
+ }
164
+ }
165
+ export default BillbeeClient;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Vertex AI Embedding Service
3
+ * Ported from TigerV0 for Husky Biz CLI
4
+ *
5
+ * Uses Google Application Default Credentials (ADC)
6
+ * Run: gcloud auth application-default login
7
+ */
8
+ export declare const EMBEDDING_MODELS: {
9
+ readonly TEXT_EMBEDDING_004: "text-embedding-004";
10
+ readonly TEXT_MULTILINGUAL_002: "text-multilingual-embedding-002";
11
+ };
12
+ export interface EmbeddingConfig {
13
+ projectId: string;
14
+ location?: string;
15
+ model?: string;
16
+ }
17
+ export interface EmbeddingResult {
18
+ values: number[];
19
+ text: string;
20
+ }
21
+ export declare class EmbeddingService {
22
+ private projectId;
23
+ private location;
24
+ private model;
25
+ private accessToken;
26
+ private tokenExpiry;
27
+ constructor(config: EmbeddingConfig);
28
+ /**
29
+ * Create service from Husky config
30
+ */
31
+ static fromConfig(): EmbeddingService;
32
+ /**
33
+ * Get access token using gcloud CLI
34
+ */
35
+ private getAccessToken;
36
+ /**
37
+ * Generate embedding for a single text
38
+ */
39
+ embed(text: string): Promise<number[]>;
40
+ /**
41
+ * Generate embeddings for multiple texts (batch)
42
+ */
43
+ embedBatch(texts: string[]): Promise<EmbeddingResult[]>;
44
+ }
45
+ export default EmbeddingService;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Vertex AI Embedding Service
3
+ * Ported from TigerV0 for Husky Biz CLI
4
+ *
5
+ * Uses Google Application Default Credentials (ADC)
6
+ * Run: gcloud auth application-default login
7
+ */
8
+ import { getConfig } from '../../commands/config.js';
9
+ export const EMBEDDING_MODELS = {
10
+ TEXT_EMBEDDING_004: 'text-embedding-004',
11
+ TEXT_MULTILINGUAL_002: 'text-multilingual-embedding-002',
12
+ };
13
+ export class EmbeddingService {
14
+ projectId;
15
+ location;
16
+ model;
17
+ accessToken = null;
18
+ tokenExpiry = null;
19
+ constructor(config) {
20
+ this.projectId = config.projectId;
21
+ this.location = config.location || 'europe-west1';
22
+ this.model = config.model || EMBEDDING_MODELS.TEXT_EMBEDDING_004;
23
+ }
24
+ /**
25
+ * Create service from Husky config
26
+ */
27
+ static fromConfig() {
28
+ const config = getConfig();
29
+ const embeddingConfig = {
30
+ projectId: config.gcpProjectId || process.env.GOOGLE_PROJECT_ID || process.env.GCP_PROJECT_ID || '',
31
+ location: config.gcpLocation || process.env.GOOGLE_LOCATION || 'europe-west1',
32
+ model: process.env.EMBEDDING_MODEL || EMBEDDING_MODELS.TEXT_EMBEDDING_004,
33
+ };
34
+ if (!embeddingConfig.projectId) {
35
+ throw new Error('Missing GCP Project ID. Configure with:\n' +
36
+ ' husky config set gcp-project-id <project-id>\n\n' +
37
+ 'Also ensure ADC is set up: gcloud auth application-default login');
38
+ }
39
+ return new EmbeddingService(embeddingConfig);
40
+ }
41
+ /**
42
+ * Get access token using gcloud CLI
43
+ */
44
+ async getAccessToken() {
45
+ const now = Date.now();
46
+ if (this.accessToken && this.tokenExpiry && now < this.tokenExpiry) {
47
+ return this.accessToken;
48
+ }
49
+ // Use gcloud CLI to get access token
50
+ const { exec } = await import('child_process');
51
+ const { promisify } = await import('util');
52
+ const execAsync = promisify(exec);
53
+ try {
54
+ const { stdout } = await execAsync('gcloud auth print-access-token');
55
+ this.accessToken = stdout.trim();
56
+ // Token valid for ~1 hour, refresh after 50 minutes
57
+ this.tokenExpiry = now + (50 * 60 * 1000);
58
+ return this.accessToken;
59
+ }
60
+ catch (error) {
61
+ throw new Error('Failed to get GCP access token. Ensure gcloud is installed and run:\n' +
62
+ ' gcloud auth application-default login');
63
+ }
64
+ }
65
+ /**
66
+ * Generate embedding for a single text
67
+ */
68
+ async embed(text) {
69
+ const token = await this.getAccessToken();
70
+ const endpoint = `https://${this.location}-aiplatform.googleapis.com/v1/projects/${this.projectId}/locations/${this.location}/publishers/google/models/${this.model}:predict`;
71
+ const response = await fetch(endpoint, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Authorization': `Bearer ${token}`,
75
+ 'Content-Type': 'application/json',
76
+ },
77
+ body: JSON.stringify({
78
+ instances: [{ content: text }],
79
+ }),
80
+ });
81
+ if (!response.ok) {
82
+ const error = await response.text();
83
+ throw new Error(`Embedding API Error ${response.status}: ${error}`);
84
+ }
85
+ const data = await response.json();
86
+ return data.predictions[0].embeddings.values;
87
+ }
88
+ /**
89
+ * Generate embeddings for multiple texts (batch)
90
+ */
91
+ async embedBatch(texts) {
92
+ const token = await this.getAccessToken();
93
+ const endpoint = `https://${this.location}-aiplatform.googleapis.com/v1/projects/${this.projectId}/locations/${this.location}/publishers/google/models/${this.model}:predict`;
94
+ const response = await fetch(endpoint, {
95
+ method: 'POST',
96
+ headers: {
97
+ 'Authorization': `Bearer ${token}`,
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ body: JSON.stringify({
101
+ instances: texts.map((text) => ({ content: text })),
102
+ }),
103
+ });
104
+ if (!response.ok) {
105
+ const error = await response.text();
106
+ throw new Error(`Embedding API Error ${response.status}: ${error}`);
107
+ }
108
+ const data = await response.json();
109
+ return texts.map((text, i) => ({
110
+ text,
111
+ values: data.predictions[i].embeddings.values,
112
+ }));
113
+ }
114
+ }
115
+ export default EmbeddingService;