@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.
- package/dist/commands/biz/invoices.d.ts +11 -0
- package/dist/commands/biz/invoices.js +661 -0
- package/dist/commands/biz/shopify.d.ts +3 -0
- package/dist/commands/biz/shopify.js +592 -0
- package/dist/commands/biz/supplier-feed.d.ts +3 -0
- package/dist/commands/biz/supplier-feed.js +168 -0
- package/dist/commands/biz.js +5 -1
- package/dist/commands/config.d.ts +3 -2
- package/dist/commands/config.js +4 -3
- package/dist/commands/sop.d.ts +3 -0
- package/dist/commands/sop.js +458 -0
- package/dist/commands/task.js +7 -0
- package/dist/lib/biz/gcs-upload.d.ts +86 -0
- package/dist/lib/biz/gcs-upload.js +189 -0
- package/dist/lib/biz/index.d.ts +5 -0
- package/dist/lib/biz/index.js +3 -0
- package/dist/lib/biz/invoice-extractor-registry.d.ts +22 -0
- package/dist/lib/biz/invoice-extractor-registry.js +416 -0
- package/dist/lib/biz/invoice-extractor-types.d.ts +127 -0
- package/dist/lib/biz/invoice-extractor-types.js +6 -0
- package/dist/lib/biz/pattern-detection.d.ts +48 -0
- package/dist/lib/biz/pattern-detection.js +205 -0
- package/dist/lib/biz/resolved-tickets.d.ts +86 -0
- package/dist/lib/biz/resolved-tickets.js +250 -0
- package/dist/lib/biz/shopify.d.ts +196 -0
- package/dist/lib/biz/shopify.js +429 -0
- package/dist/lib/biz/supplier-feed-types.d.ts +96 -0
- package/dist/lib/biz/supplier-feed-types.js +46 -0
- package/dist/lib/biz/supplier-feed.d.ts +32 -0
- package/dist/lib/biz/supplier-feed.js +244 -0
- package/dist/lib/permissions.d.ts +2 -1
- package/dist/types/roles.d.ts +3 -0
- package/dist/types/roles.js +14 -0
- package/package.json +1 -1
package/dist/commands/task.js
CHANGED
|
@@ -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;
|
package/dist/lib/biz/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/lib/biz/index.js
CHANGED
|
@@ -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;
|