@simonfestl/husky-cli 1.24.0 → 1.25.2
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/gotess.js +39 -0
- package/dist/commands/chat.js +38 -26
- package/dist/commands/config.d.ts +1 -0
- package/dist/lib/biz/gotess.d.ts +27 -0
- package/dist/lib/biz/gotess.js +88 -0
- package/dist/lib/biz/index.d.ts +1 -0
- package/dist/lib/biz/index.js +1 -0
- package/package.json +4 -1
|
@@ -317,4 +317,43 @@ gotessCommand
|
|
|
317
317
|
process.exit(1);
|
|
318
318
|
}
|
|
319
319
|
});
|
|
320
|
+
gotessCommand
|
|
321
|
+
.command("upload <file>")
|
|
322
|
+
.description("Upload invoice PDF to Gotess")
|
|
323
|
+
.option("-s, --sender <name>", "Sender/vendor name (required)")
|
|
324
|
+
.option("-a, --amount <amount>", "Invoice amount", parseFloat)
|
|
325
|
+
.option("-d, --date <date>", "Invoice date (YYYY-MM-DD)")
|
|
326
|
+
.option("--link <transactionId>", "Auto-link to transaction after upload")
|
|
327
|
+
.action(async (file, options) => {
|
|
328
|
+
try {
|
|
329
|
+
if (!options.sender) {
|
|
330
|
+
console.error("✗ Sender name is required (-s, --sender)");
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
const client = GotessClient.fromConfig();
|
|
334
|
+
console.log(`\n 📤 Uploading ${file}...`);
|
|
335
|
+
const invoice = await client.uploadInvoicePdf(file, {
|
|
336
|
+
senderName: options.sender,
|
|
337
|
+
amount: options.amount,
|
|
338
|
+
invoiceDate: options.date,
|
|
339
|
+
});
|
|
340
|
+
console.log(` ✓ Invoice created: ${invoice.id}`);
|
|
341
|
+
console.log(` Filename: ${invoice.filename}`);
|
|
342
|
+
console.log(` Sender: ${invoice.sender_name}`);
|
|
343
|
+
if (invoice.amount) {
|
|
344
|
+
console.log(` Amount: ${invoice.amount.toFixed(2)} EUR`);
|
|
345
|
+
}
|
|
346
|
+
// Auto-link if transaction ID provided
|
|
347
|
+
if (options.link) {
|
|
348
|
+
console.log(`\n 🔗 Linking to transaction ${options.link}...`);
|
|
349
|
+
await client.linkInvoice(options.link, invoice.id);
|
|
350
|
+
console.log(` ✓ Linked successfully`);
|
|
351
|
+
}
|
|
352
|
+
console.log("");
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
console.error("Error:", error.message);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
320
359
|
export default gotessCommand;
|
package/dist/commands/chat.js
CHANGED
|
@@ -98,42 +98,54 @@ chatCommand
|
|
|
98
98
|
});
|
|
99
99
|
chatCommand
|
|
100
100
|
.command("send <message>")
|
|
101
|
-
.description("Send a message
|
|
101
|
+
.description("Send a message (to Google Chat if --space provided, otherwise to dashboard chat)")
|
|
102
102
|
.option("--task-id <id>", "Link to a specific task")
|
|
103
|
-
.option("--
|
|
104
|
-
.option("--
|
|
103
|
+
.option("--space <id>", "Google Chat space ID (e.g., spaces/ABC123 or DM space)")
|
|
104
|
+
.option("--thread <name>", "Reply in Google Chat thread")
|
|
105
105
|
.action(async (message, options) => {
|
|
106
106
|
const config = getConfig();
|
|
107
|
-
|
|
107
|
+
const huskyApiUrl = getHuskyApiUrl();
|
|
108
|
+
if (!huskyApiUrl) {
|
|
108
109
|
console.error("Error: API URL not configured.");
|
|
109
110
|
process.exit(1);
|
|
110
111
|
}
|
|
111
112
|
try {
|
|
112
|
-
let endpoint = `${config.apiUrl}/api/chat/supervisor`;
|
|
113
|
-
let payload = {
|
|
114
|
-
content: message,
|
|
115
|
-
...(options.taskId && { taskId: options.taskId }),
|
|
116
|
-
};
|
|
117
|
-
// If --space is provided, use Google Chat API instead
|
|
118
113
|
if (options.space) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
114
|
+
const res = await fetch(`${huskyApiUrl}/api/google-chat/send`, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: {
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
text: message,
|
|
122
|
+
spaceName: options.space,
|
|
123
|
+
threadName: options.thread,
|
|
124
|
+
}),
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) {
|
|
127
|
+
const error = await res.text();
|
|
128
|
+
throw new Error(`API error: ${res.status} - ${error}`);
|
|
129
|
+
}
|
|
130
|
+
console.log("✅ Message sent to Google Chat.");
|
|
124
131
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
else {
|
|
133
|
+
const res = await fetch(`${huskyApiUrl}/api/chat/supervisor`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: {
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify({
|
|
140
|
+
content: message,
|
|
141
|
+
taskId: options.taskId,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
throw new Error(`API error: ${res.status}`);
|
|
146
|
+
}
|
|
147
|
+
console.log("Message sent to dashboard chat.");
|
|
135
148
|
}
|
|
136
|
-
console.log(options.space ? "✅ Message sent to Google Chat." : "Message sent.");
|
|
137
149
|
}
|
|
138
150
|
catch (error) {
|
|
139
151
|
console.error("Error sending message:", error);
|
package/dist/lib/biz/gotess.d.ts
CHANGED
|
@@ -81,6 +81,19 @@ export declare class GotessClient {
|
|
|
81
81
|
}): Promise<GotessInvoice[]>;
|
|
82
82
|
linkInvoice(transactionId: string, invoiceId: string): Promise<void>;
|
|
83
83
|
markProofless(transactionId: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Create an invoice record in Gotess
|
|
86
|
+
* Used for auto-upload from invoice extraction
|
|
87
|
+
*/
|
|
88
|
+
createInvoice(invoice: {
|
|
89
|
+
invoiceDate?: string;
|
|
90
|
+
amount?: number;
|
|
91
|
+
senderName: string;
|
|
92
|
+
filename: string;
|
|
93
|
+
gcsUri?: string;
|
|
94
|
+
s3Uri?: string;
|
|
95
|
+
bookId?: string;
|
|
96
|
+
}): Promise<GotessInvoice>;
|
|
84
97
|
autoMatch(bookId?: string): Promise<{
|
|
85
98
|
matched: Array<{
|
|
86
99
|
transaction: GotessTransaction;
|
|
@@ -93,5 +106,19 @@ export declare class GotessClient {
|
|
|
93
106
|
setBookId(id: string): void;
|
|
94
107
|
getAccessToken(): string | undefined;
|
|
95
108
|
getBookId(): string | undefined;
|
|
109
|
+
/**
|
|
110
|
+
* Upload a PDF file to Supabase Storage and create invoice record
|
|
111
|
+
* This uploads directly to Gotess's storage system
|
|
112
|
+
*/
|
|
113
|
+
uploadInvoicePdf(filePath: string, options: {
|
|
114
|
+
invoiceDate?: string;
|
|
115
|
+
amount?: number;
|
|
116
|
+
senderName: string;
|
|
117
|
+
bookId?: string;
|
|
118
|
+
}): Promise<GotessInvoice>;
|
|
119
|
+
/**
|
|
120
|
+
* Get signed URL for private file access
|
|
121
|
+
*/
|
|
122
|
+
getSignedUrl(storagePath: string, expiresIn?: number): Promise<string>;
|
|
96
123
|
}
|
|
97
124
|
export default GotessClient;
|
package/dist/lib/biz/gotess.js
CHANGED
|
@@ -137,6 +137,34 @@ export class GotessClient {
|
|
|
137
137
|
if (!res.ok)
|
|
138
138
|
throw new Error('Failed to mark proofless');
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Create an invoice record in Gotess
|
|
142
|
+
* Used for auto-upload from invoice extraction
|
|
143
|
+
*/
|
|
144
|
+
async createInvoice(invoice) {
|
|
145
|
+
const bid = invoice.bookId || this.bookId;
|
|
146
|
+
if (!bid)
|
|
147
|
+
throw new Error('Book ID required');
|
|
148
|
+
const body = {
|
|
149
|
+
invoice_date: invoice.invoiceDate,
|
|
150
|
+
amount: invoice.amount,
|
|
151
|
+
sender_name: invoice.senderName,
|
|
152
|
+
filename: invoice.filename,
|
|
153
|
+
s3_uri: invoice.s3Uri || invoice.gcsUri, // Use GCS URI as s3_uri field
|
|
154
|
+
book_id: bid,
|
|
155
|
+
};
|
|
156
|
+
const res = await fetch(`${SUPABASE_URL}/rest/v1/Invoices`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: { ...this.headers, 'Prefer': 'return=representation' },
|
|
159
|
+
body: JSON.stringify(body),
|
|
160
|
+
});
|
|
161
|
+
if (!res.ok) {
|
|
162
|
+
const err = await res.json().catch(() => ({}));
|
|
163
|
+
throw new Error(err.message || 'Failed to create invoice');
|
|
164
|
+
}
|
|
165
|
+
const data = await res.json();
|
|
166
|
+
return Array.isArray(data) ? data[0] : data;
|
|
167
|
+
}
|
|
140
168
|
async autoMatch(bookId) {
|
|
141
169
|
const bid = bookId || this.bookId;
|
|
142
170
|
if (!bid)
|
|
@@ -198,5 +226,65 @@ export class GotessClient {
|
|
|
198
226
|
getBookId() {
|
|
199
227
|
return this.bookId;
|
|
200
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Upload a PDF file to Supabase Storage and create invoice record
|
|
231
|
+
* This uploads directly to Gotess's storage system
|
|
232
|
+
*/
|
|
233
|
+
async uploadInvoicePdf(filePath, options) {
|
|
234
|
+
const bid = options.bookId || this.bookId;
|
|
235
|
+
if (!bid)
|
|
236
|
+
throw new Error('Book ID required');
|
|
237
|
+
// Read file
|
|
238
|
+
const fs = await import('fs');
|
|
239
|
+
const path = await import('path');
|
|
240
|
+
if (!fs.existsSync(filePath)) {
|
|
241
|
+
throw new Error(`File not found: ${filePath}`);
|
|
242
|
+
}
|
|
243
|
+
const fileBuffer = fs.readFileSync(filePath);
|
|
244
|
+
const filename = path.basename(filePath);
|
|
245
|
+
// Generate unique storage path
|
|
246
|
+
const timestamp = Date.now();
|
|
247
|
+
const storagePath = `invoices/${bid}/${timestamp}-${filename}`;
|
|
248
|
+
// Upload to Supabase Storage
|
|
249
|
+
const uploadRes = await fetch(`${SUPABASE_URL}/storage/v1/object/invoices/${storagePath}`, {
|
|
250
|
+
method: 'POST',
|
|
251
|
+
headers: {
|
|
252
|
+
...this.headers,
|
|
253
|
+
'Content-Type': 'application/pdf',
|
|
254
|
+
},
|
|
255
|
+
body: fileBuffer,
|
|
256
|
+
});
|
|
257
|
+
if (!uploadRes.ok) {
|
|
258
|
+
const err = await uploadRes.json().catch(() => ({}));
|
|
259
|
+
throw new Error(err.message || `Upload failed: ${uploadRes.status}`);
|
|
260
|
+
}
|
|
261
|
+
// Get the public URL
|
|
262
|
+
const s3Uri = `${SUPABASE_URL}/storage/v1/object/public/invoices/${storagePath}`;
|
|
263
|
+
// Create invoice record
|
|
264
|
+
const invoice = await this.createInvoice({
|
|
265
|
+
invoiceDate: options.invoiceDate,
|
|
266
|
+
amount: options.amount,
|
|
267
|
+
senderName: options.senderName,
|
|
268
|
+
filename,
|
|
269
|
+
s3Uri,
|
|
270
|
+
bookId: bid,
|
|
271
|
+
});
|
|
272
|
+
return invoice;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get signed URL for private file access
|
|
276
|
+
*/
|
|
277
|
+
async getSignedUrl(storagePath, expiresIn = 3600) {
|
|
278
|
+
const res = await fetch(`${SUPABASE_URL}/storage/v1/object/sign/invoices/${storagePath}`, {
|
|
279
|
+
method: 'POST',
|
|
280
|
+
headers: this.headers,
|
|
281
|
+
body: JSON.stringify({ expiresIn }),
|
|
282
|
+
});
|
|
283
|
+
if (!res.ok) {
|
|
284
|
+
throw new Error('Failed to generate signed URL');
|
|
285
|
+
}
|
|
286
|
+
const data = await res.json();
|
|
287
|
+
return `${SUPABASE_URL}/storage/v1${data.signedURL}`;
|
|
288
|
+
}
|
|
201
289
|
}
|
|
202
290
|
export default GotessClient;
|
package/dist/lib/biz/index.d.ts
CHANGED
package/dist/lib/biz/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simonfestl/husky-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.2",
|
|
4
4
|
"description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,13 +20,16 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/claude-code": "^1.0.0",
|
|
23
|
+
"@google-cloud/storage": "^7.14.0",
|
|
23
24
|
"@google-cloud/vertexai": "^1.10.0",
|
|
24
25
|
"@google/generative-ai": "^0.24.1",
|
|
25
26
|
"@inquirer/prompts": "^8.1.0",
|
|
27
|
+
"@types/uuid": "^10.0.0",
|
|
26
28
|
"commander": "^12.1.0",
|
|
27
29
|
"firebase-admin": "^13.6.0",
|
|
28
30
|
"playwright": "^1.57.0",
|
|
29
31
|
"sharp": "^0.34.5",
|
|
32
|
+
"uuid": "^13.0.0",
|
|
30
33
|
"youtube-transcript": "^1.2.1",
|
|
31
34
|
"zod": "^4.3.5"
|
|
32
35
|
},
|