@simonfestl/husky-cli 1.27.1 → 1.29.1

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 (34) 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/sop.d.ts +3 -0
  11. package/dist/commands/sop.js +458 -0
  12. package/dist/commands/task.js +7 -0
  13. package/dist/lib/biz/gcs-upload.d.ts +86 -0
  14. package/dist/lib/biz/gcs-upload.js +189 -0
  15. package/dist/lib/biz/index.d.ts +5 -0
  16. package/dist/lib/biz/index.js +3 -0
  17. package/dist/lib/biz/invoice-extractor-registry.d.ts +22 -0
  18. package/dist/lib/biz/invoice-extractor-registry.js +416 -0
  19. package/dist/lib/biz/invoice-extractor-types.d.ts +127 -0
  20. package/dist/lib/biz/invoice-extractor-types.js +6 -0
  21. package/dist/lib/biz/pattern-detection.d.ts +48 -0
  22. package/dist/lib/biz/pattern-detection.js +205 -0
  23. package/dist/lib/biz/resolved-tickets.d.ts +86 -0
  24. package/dist/lib/biz/resolved-tickets.js +250 -0
  25. package/dist/lib/biz/shopify.d.ts +196 -0
  26. package/dist/lib/biz/shopify.js +429 -0
  27. package/dist/lib/biz/supplier-feed-types.d.ts +96 -0
  28. package/dist/lib/biz/supplier-feed-types.js +46 -0
  29. package/dist/lib/biz/supplier-feed.d.ts +32 -0
  30. package/dist/lib/biz/supplier-feed.js +244 -0
  31. package/dist/lib/permissions.d.ts +2 -1
  32. package/dist/types/roles.d.ts +3 -0
  33. package/dist/types/roles.js +14 -0
  34. package/package.json +1 -1
@@ -346,6 +346,7 @@ taskCommand
346
346
  .requiredOption("--worker <hostname>", "Target worker hostname (e.g., worker-1)")
347
347
  .option("--priority <level>", "Priority: low, normal, high, urgent", "normal")
348
348
  .option("--instructions <text>", "Additional instructions for the worker")
349
+ .option("--no-vm", "Don't create preemptible VM if worker is not running")
349
350
  .option("--json", "Output as JSON")
350
351
  .action(async (id, options) => {
351
352
  const config = ensureConfig();
@@ -361,6 +362,7 @@ taskCommand
361
362
  targetHostname: options.worker,
362
363
  priority: options.priority,
363
364
  instructions: options.instructions,
365
+ ensureVM: options.vm !== false, // --no-vm sets options.vm to false
364
366
  });
365
367
  if (options.json) {
366
368
  console.log(JSON.stringify(result, null, 2));
@@ -373,6 +375,11 @@ taskCommand
373
375
  if (options.instructions) {
374
376
  console.log(` Instructions: ${options.instructions.slice(0, 50)}...`);
375
377
  }
378
+ // Show VM info if a new VM was created
379
+ if (result.vm?.created) {
380
+ console.log(`\n 🖥️ Created preemptible VM: ${result.vm.name}`);
381
+ console.log(` VM will be ready in ${result.vm.estimatedReadyIn}`);
382
+ }
376
383
  console.log(`\n The worker-bridge on ${options.worker} will inject this task into OpenCode.`);
377
384
  }
378
385
  }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * GCS Upload Client
3
+ *
4
+ * Uploads extracted invoice PDFs to Google Cloud Storage
5
+ *
6
+ * Structure:
7
+ * gs://husky-invoices/
8
+ * └── partners/
9
+ * └── suppliers/
10
+ * └── {sourceId}/ # Firestore InvoiceSource doc ID
11
+ * └── {extractedInvoiceId}.pdf # Firestore ExtractedInvoice doc ID
12
+ */
13
+ interface UploadResult {
14
+ success: boolean;
15
+ gcsUri?: string;
16
+ gcsPath?: string;
17
+ publicUrl?: string;
18
+ error?: string;
19
+ }
20
+ interface UploadOptions {
21
+ bucket?: string;
22
+ prefix?: string;
23
+ makePublic?: boolean;
24
+ metadata?: Record<string, string>;
25
+ }
26
+ /**
27
+ * GCS Upload Client for invoice PDFs
28
+ */
29
+ export declare class GCSUploadClient {
30
+ private storage;
31
+ private bucketName;
32
+ constructor(bucketName?: string);
33
+ /**
34
+ * Upload a single file to GCS
35
+ */
36
+ uploadFile(localPath: string, options?: UploadOptions): Promise<UploadResult>;
37
+ /**
38
+ * Upload multiple files to GCS
39
+ */
40
+ uploadFiles(localPaths: string[], options?: UploadOptions): Promise<{
41
+ results: UploadResult[];
42
+ successCount: number;
43
+ failCount: number;
44
+ }>;
45
+ /**
46
+ * Upload invoice using Firestore IDs for path structure
47
+ * Path: partners/suppliers/{sourceId}/{invoiceId}.pdf
48
+ */
49
+ uploadInvoiceById(localPath: string, options: {
50
+ sourceId: string;
51
+ extractedInvoiceId: string;
52
+ metadata?: Record<string, string>;
53
+ }): Promise<UploadResult>;
54
+ /**
55
+ * Upload invoice with source name (legacy/fallback)
56
+ * Creates a temp ID if Firestore IDs not available
57
+ */
58
+ uploadInvoice(localPath: string, options: {
59
+ source: string;
60
+ orderNumber: string;
61
+ invoiceDate?: string;
62
+ sourceId?: string;
63
+ extractedInvoiceId?: string;
64
+ }): Promise<UploadResult>;
65
+ /**
66
+ * List files in bucket with prefix
67
+ */
68
+ listFiles(prefix?: string): Promise<string[]>;
69
+ /**
70
+ * Check if bucket exists and is accessible
71
+ */
72
+ checkAccess(): Promise<{
73
+ accessible: boolean;
74
+ error?: string;
75
+ }>;
76
+ /**
77
+ * Get signed URL for temporary access to a file
78
+ */
79
+ getSignedUrl(gcsPath: string, expiresInMinutes?: number): Promise<string | null>;
80
+ getBucketName(): string;
81
+ }
82
+ /**
83
+ * Create GCS upload client from config
84
+ */
85
+ export declare function createGCSClient(bucketName?: string): GCSUploadClient;
86
+ export default GCSUploadClient;
@@ -0,0 +1,189 @@
1
+ /**
2
+ * GCS Upload Client
3
+ *
4
+ * Uploads extracted invoice PDFs to Google Cloud Storage
5
+ *
6
+ * Structure:
7
+ * gs://husky-invoices/
8
+ * └── partners/
9
+ * └── suppliers/
10
+ * └── {sourceId}/ # Firestore InvoiceSource doc ID
11
+ * └── {extractedInvoiceId}.pdf # Firestore ExtractedInvoice doc ID
12
+ */
13
+ import { Storage } from "@google-cloud/storage";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import { getConfig } from "../../commands/config.js";
17
+ // Default bucket for invoice PDFs
18
+ const DEFAULT_BUCKET = "husky-invoices";
19
+ // Base path structure
20
+ const PARTNERS_SUPPLIERS_PATH = "partners/suppliers";
21
+ /**
22
+ * GCS Upload Client for invoice PDFs
23
+ */
24
+ export class GCSUploadClient {
25
+ storage;
26
+ bucketName;
27
+ constructor(bucketName) {
28
+ const config = getConfig();
29
+ this.bucketName = bucketName || config.gcsBucket || process.env.GCS_INVOICE_BUCKET || DEFAULT_BUCKET;
30
+ // Initialize storage client
31
+ // Uses Application Default Credentials (ADC) in Cloud Run
32
+ // Or GOOGLE_APPLICATION_CREDENTIALS env var locally
33
+ this.storage = new Storage();
34
+ }
35
+ /**
36
+ * Upload a single file to GCS
37
+ */
38
+ async uploadFile(localPath, options = {}) {
39
+ try {
40
+ if (!fs.existsSync(localPath)) {
41
+ return { success: false, error: `File not found: ${localPath}` };
42
+ }
43
+ const bucket = this.storage.bucket(options.bucket || this.bucketName);
44
+ const filename = path.basename(localPath);
45
+ const destination = options.prefix
46
+ ? `${options.prefix}/${filename}`
47
+ : filename;
48
+ const uploadOptions = {
49
+ destination,
50
+ };
51
+ if (options.metadata) {
52
+ uploadOptions.metadata = { metadata: options.metadata };
53
+ }
54
+ await bucket.upload(localPath, uploadOptions);
55
+ const gcsUri = `gs://${this.bucketName}/${destination}`;
56
+ let publicUrl;
57
+ if (options.makePublic) {
58
+ const file = bucket.file(destination);
59
+ await file.makePublic();
60
+ publicUrl = `https://storage.googleapis.com/${this.bucketName}/${destination}`;
61
+ }
62
+ return { success: true, gcsUri, gcsPath: destination, publicUrl };
63
+ }
64
+ catch (error) {
65
+ return { success: false, error: error.message };
66
+ }
67
+ }
68
+ /**
69
+ * Upload multiple files to GCS
70
+ */
71
+ async uploadFiles(localPaths, options = {}) {
72
+ const results = [];
73
+ let successCount = 0;
74
+ let failCount = 0;
75
+ for (const localPath of localPaths) {
76
+ const result = await this.uploadFile(localPath, options);
77
+ results.push(result);
78
+ if (result.success) {
79
+ successCount++;
80
+ }
81
+ else {
82
+ failCount++;
83
+ }
84
+ }
85
+ return { results, successCount, failCount };
86
+ }
87
+ /**
88
+ * Upload invoice using Firestore IDs for path structure
89
+ * Path: partners/suppliers/{sourceId}/{invoiceId}.pdf
90
+ */
91
+ async uploadInvoiceById(localPath, options) {
92
+ const prefix = `${PARTNERS_SUPPLIERS_PATH}/${options.sourceId}`;
93
+ const filename = `${options.extractedInvoiceId}.pdf`;
94
+ return this.uploadFile(localPath, {
95
+ prefix,
96
+ metadata: {
97
+ sourceId: options.sourceId,
98
+ extractedInvoiceId: options.extractedInvoiceId,
99
+ uploadedAt: new Date().toISOString(),
100
+ ...options.metadata,
101
+ },
102
+ });
103
+ }
104
+ /**
105
+ * Upload invoice with source name (legacy/fallback)
106
+ * Creates a temp ID if Firestore IDs not available
107
+ */
108
+ async uploadInvoice(localPath, options) {
109
+ // If we have Firestore IDs, use the ID-based path
110
+ if (options.sourceId && options.extractedInvoiceId) {
111
+ return this.uploadInvoiceById(localPath, {
112
+ sourceId: options.sourceId,
113
+ extractedInvoiceId: options.extractedInvoiceId,
114
+ metadata: {
115
+ sourceName: options.source,
116
+ orderNumber: options.orderNumber,
117
+ invoiceDate: options.invoiceDate || "",
118
+ },
119
+ });
120
+ }
121
+ // Fallback: use source name + order number as identifier
122
+ const tempId = `${options.source}-${options.orderNumber}-${Date.now()}`;
123
+ const prefix = `${PARTNERS_SUPPLIERS_PATH}/${options.source}`;
124
+ return this.uploadFile(localPath, {
125
+ prefix,
126
+ metadata: {
127
+ sourceName: options.source,
128
+ orderNumber: options.orderNumber,
129
+ invoiceDate: options.invoiceDate || "",
130
+ uploadedAt: new Date().toISOString(),
131
+ tempId,
132
+ },
133
+ });
134
+ }
135
+ /**
136
+ * List files in bucket with prefix
137
+ */
138
+ async listFiles(prefix) {
139
+ const bucket = this.storage.bucket(this.bucketName);
140
+ const [files] = await bucket.getFiles({ prefix });
141
+ return files.map((f) => f.name);
142
+ }
143
+ /**
144
+ * Check if bucket exists and is accessible
145
+ */
146
+ async checkAccess() {
147
+ try {
148
+ const bucket = this.storage.bucket(this.bucketName);
149
+ const [exists] = await bucket.exists();
150
+ if (!exists) {
151
+ return { accessible: false, error: `Bucket ${this.bucketName} does not exist` };
152
+ }
153
+ // Try to list files to verify permissions
154
+ await bucket.getFiles({ maxResults: 1 });
155
+ return { accessible: true };
156
+ }
157
+ catch (error) {
158
+ return { accessible: false, error: error.message };
159
+ }
160
+ }
161
+ /**
162
+ * Get signed URL for temporary access to a file
163
+ */
164
+ async getSignedUrl(gcsPath, expiresInMinutes = 60) {
165
+ try {
166
+ const bucket = this.storage.bucket(this.bucketName);
167
+ const file = bucket.file(gcsPath);
168
+ const [url] = await file.getSignedUrl({
169
+ action: "read",
170
+ expires: Date.now() + expiresInMinutes * 60 * 1000,
171
+ });
172
+ return url;
173
+ }
174
+ catch (error) {
175
+ console.error("Error generating signed URL:", error);
176
+ return null;
177
+ }
178
+ }
179
+ getBucketName() {
180
+ return this.bucketName;
181
+ }
182
+ }
183
+ /**
184
+ * Create GCS upload client from config
185
+ */
186
+ export function createGCSClient(bucketName) {
187
+ return new GCSUploadClient(bucketName);
188
+ }
189
+ export default GCSUploadClient;
@@ -12,3 +12,8 @@ export * from './billbee-types.js';
12
12
  export * from './zendesk-types.js';
13
13
  export * from './seatable-types.js';
14
14
  export { GotessClient } from './gotess.js';
15
+ export { ShopifyClient } from './shopify.js';
16
+ export type { Order as ShopifyOrder, Customer as ShopifyCustomer, Product as ShopifyProduct, ShopMetrics, } from './shopify.js';
17
+ export { SupplierFeedService } from './supplier-feed.js';
18
+ export type { SupplierId, SupplierProduct, SupplierProductSearchResult, SyncStats, } from './supplier-feed.js';
19
+ export * from './supplier-feed-types.js';
@@ -10,3 +10,6 @@ export * from './billbee-types.js';
10
10
  export * from './zendesk-types.js';
11
11
  export * from './seatable-types.js';
12
12
  export { GotessClient } from './gotess.js';
13
+ export { ShopifyClient } from './shopify.js';
14
+ export { SupplierFeedService } from './supplier-feed.js';
15
+ export * from './supplier-feed-types.js';
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Invoice Extractor Registry
3
+ *
4
+ * Registers available extractors and provides factory methods
5
+ */
6
+ import { InvoiceExtractor } from "./invoice-extractor-types.js";
7
+ /**
8
+ * Get an extractor by ID
9
+ */
10
+ export declare function getExtractor(extractorId: string): InvoiceExtractor | null;
11
+ /**
12
+ * Get all available extractor IDs
13
+ */
14
+ export declare function getAvailableExtractorIds(): string[];
15
+ /**
16
+ * Check if credentials are configured for an extractor
17
+ */
18
+ export declare function hasCredentialsConfigured(extractorId: string): boolean;
19
+ /**
20
+ * Get default output directory for invoices
21
+ */
22
+ export declare function getDefaultInvoiceDir(): string;