@simonfestl/husky-cli 0.9.1 → 0.9.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/interactive/business.d.ts +6 -0
- package/dist/commands/interactive/business.js +289 -0
- package/dist/commands/interactive.js +5 -0
- package/dist/commands/task.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/biz/billbee.d.ts +1 -0
- package/dist/lib/biz/billbee.js +8 -5
- package/dist/lib/biz/qdrant.d.ts +1 -0
- package/dist/lib/biz/qdrant.js +7 -4
- package/dist/lib/biz/seatable.d.ts +1 -0
- package/dist/lib/biz/seatable.js +6 -3
- package/dist/lib/biz/zendesk.d.ts +1 -0
- package/dist/lib/biz/zendesk.js +7 -4
- package/package.json +1 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business Operations Interactive Menu
|
|
3
|
+
* Provides access to Billbee, Zendesk, SeaTable, and Qdrant
|
|
4
|
+
*/
|
|
5
|
+
import { select, input } from "@inquirer/prompts";
|
|
6
|
+
import { pressEnterToContinue } from "./utils.js";
|
|
7
|
+
import { ZendeskClient, BillbeeClient, SeaTableClient, QdrantClient, EmbeddingService } from "../../lib/biz/index.js";
|
|
8
|
+
// ============================================
|
|
9
|
+
// MAIN BUSINESS MENU
|
|
10
|
+
// ============================================
|
|
11
|
+
export async function businessMenu() {
|
|
12
|
+
const menuItems = [
|
|
13
|
+
{ name: "🎫 Tickets (Zendesk)", value: "tickets" },
|
|
14
|
+
{ name: "👤 Customers (Billbee)", value: "customers" },
|
|
15
|
+
{ name: "📋 SeaTable (Supply Chain)", value: "seatable" },
|
|
16
|
+
{ name: "🔍 Qdrant (Vector DB)", value: "qdrant" },
|
|
17
|
+
{ name: "Back to main menu", value: "back" },
|
|
18
|
+
];
|
|
19
|
+
const choice = await select({
|
|
20
|
+
message: "Business Operations:",
|
|
21
|
+
choices: menuItems,
|
|
22
|
+
});
|
|
23
|
+
switch (choice) {
|
|
24
|
+
case "tickets":
|
|
25
|
+
await ticketsSubMenu();
|
|
26
|
+
break;
|
|
27
|
+
case "customers":
|
|
28
|
+
await customersSubMenu();
|
|
29
|
+
break;
|
|
30
|
+
case "seatable":
|
|
31
|
+
await seatableSubMenu();
|
|
32
|
+
break;
|
|
33
|
+
case "qdrant":
|
|
34
|
+
await qdrantSubMenu();
|
|
35
|
+
break;
|
|
36
|
+
case "back":
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// ============================================
|
|
41
|
+
// TICKETS SUBMENU (Zendesk)
|
|
42
|
+
// ============================================
|
|
43
|
+
async function ticketsSubMenu() {
|
|
44
|
+
const menuItems = [
|
|
45
|
+
{ name: "List recent tickets", value: "list" },
|
|
46
|
+
{ name: "Get ticket details", value: "get" },
|
|
47
|
+
{ name: "Search tickets", value: "search" },
|
|
48
|
+
{ name: "---", value: "sep1" },
|
|
49
|
+
{ name: "[PREBUILD] Find similar tickets", value: "similar" },
|
|
50
|
+
{ name: "[PREBUILD] Knowledge search", value: "knowledge" },
|
|
51
|
+
{ name: "Back", value: "back" },
|
|
52
|
+
];
|
|
53
|
+
const choice = await select({
|
|
54
|
+
message: "Tickets:",
|
|
55
|
+
choices: menuItems.filter(m => !m.value.startsWith("sep")),
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const zendesk = ZendeskClient.fromConfig();
|
|
59
|
+
switch (choice) {
|
|
60
|
+
case "list":
|
|
61
|
+
console.log("\n Fetching tickets...\n");
|
|
62
|
+
const tickets = await zendesk.listTickets({ per_page: 10 });
|
|
63
|
+
console.log(` 🎫 Recent Tickets (${tickets.length})\n`);
|
|
64
|
+
for (const t of tickets) {
|
|
65
|
+
console.log(` #${String(t.id).padEnd(8)} │ ${t.status.padEnd(8)} │ ${t.subject.slice(0, 40)}`);
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
case "get":
|
|
69
|
+
const ticketId = await input({ message: "Ticket ID:" });
|
|
70
|
+
const ticket = await zendesk.getTicket(parseInt(ticketId, 10));
|
|
71
|
+
console.log(`\n Ticket #${ticket.id}: ${ticket.subject}`);
|
|
72
|
+
console.log(` Status: ${ticket.status} | Priority: ${ticket.priority || "normal"}`);
|
|
73
|
+
console.log(` Created: ${new Date(ticket.created_at).toLocaleString("de-DE")}`);
|
|
74
|
+
break;
|
|
75
|
+
case "search":
|
|
76
|
+
const query = await input({ message: "Search query:" });
|
|
77
|
+
console.log("\n Searching...\n");
|
|
78
|
+
const results = await zendesk.searchTickets(query);
|
|
79
|
+
console.log(` Found ${results.length} tickets\n`);
|
|
80
|
+
for (const t of results.slice(0, 10)) {
|
|
81
|
+
console.log(` #${String(t.id).padEnd(8)} │ ${t.subject.slice(0, 45)}`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "similar":
|
|
85
|
+
const embeddings = EmbeddingService.fromConfig();
|
|
86
|
+
const qdrant = QdrantClient.fromConfig();
|
|
87
|
+
const simQuery = await input({ message: "Describe the issue:" });
|
|
88
|
+
console.log("\n Generating embedding...");
|
|
89
|
+
const vector = await embeddings.embed(simQuery);
|
|
90
|
+
console.log(" Searching similar tickets...\n");
|
|
91
|
+
const simResults = await qdrant.search("zendesk_tickets_01", vector, 5);
|
|
92
|
+
for (const r of simResults) {
|
|
93
|
+
const p = r.payload;
|
|
94
|
+
console.log(` [${(r.score * 100).toFixed(1)}%] ${p?.subject || r.id}`);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
case "knowledge":
|
|
98
|
+
const kEmbeddings = EmbeddingService.fromConfig();
|
|
99
|
+
const kQdrant = QdrantClient.fromConfig();
|
|
100
|
+
const kQuery = await input({ message: "Search resolved tickets:" });
|
|
101
|
+
console.log("\n Searching knowledge base...\n");
|
|
102
|
+
const kVector = await kEmbeddings.embed(kQuery);
|
|
103
|
+
const kResults = await kQdrant.search("tickets_learned", kVector, 3);
|
|
104
|
+
for (const r of kResults) {
|
|
105
|
+
const p = r.payload;
|
|
106
|
+
console.log(` [${(r.score * 100).toFixed(1)}%] ${p?.subject || r.id}`);
|
|
107
|
+
if (p?.resolution)
|
|
108
|
+
console.log(` → ${String(p.resolution).slice(0, 60)}...`);
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "back":
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
console.error("\n Error:", error.message);
|
|
117
|
+
}
|
|
118
|
+
console.log("");
|
|
119
|
+
await pressEnterToContinue();
|
|
120
|
+
}
|
|
121
|
+
// ============================================
|
|
122
|
+
// CUSTOMERS SUBMENU (Billbee)
|
|
123
|
+
// ============================================
|
|
124
|
+
async function customersSubMenu() {
|
|
125
|
+
const menuItems = [
|
|
126
|
+
{ name: "Search by email", value: "search" },
|
|
127
|
+
{ name: "Order history", value: "history" },
|
|
128
|
+
{ name: "[PREBUILD] Customer 360", value: "360" },
|
|
129
|
+
{ name: "Back", value: "back" },
|
|
130
|
+
];
|
|
131
|
+
const choice = await select({
|
|
132
|
+
message: "Customers:",
|
|
133
|
+
choices: menuItems,
|
|
134
|
+
});
|
|
135
|
+
try {
|
|
136
|
+
switch (choice) {
|
|
137
|
+
case "search":
|
|
138
|
+
case "history":
|
|
139
|
+
const billbee = BillbeeClient.fromConfig();
|
|
140
|
+
const email = await input({ message: "Customer email:" });
|
|
141
|
+
console.log("\n Searching...\n");
|
|
142
|
+
const result = await billbee.findCustomerByEmail(email);
|
|
143
|
+
if (result) {
|
|
144
|
+
const addr = result.address;
|
|
145
|
+
console.log(` 👤 ${addr?.FirstName || ""} ${addr?.LastName || ""}`);
|
|
146
|
+
console.log(` Orders: ${result.orders.length}`);
|
|
147
|
+
for (const o of result.orders.slice(0, 5)) {
|
|
148
|
+
console.log(` #${o.OrderNumber} │ €${o.TotalCost?.toFixed(2) || "0"}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(" Customer not found.");
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
case "360":
|
|
156
|
+
const b = BillbeeClient.fromConfig();
|
|
157
|
+
const z = ZendeskClient.fromConfig();
|
|
158
|
+
const e360 = await input({ message: "Customer email:" });
|
|
159
|
+
console.log("\n Fetching from Billbee + Zendesk...\n");
|
|
160
|
+
const [customer, tickets] = await Promise.all([
|
|
161
|
+
b.findCustomerByEmail(e360).catch(() => null),
|
|
162
|
+
z.searchTickets(`requester:${e360}`).catch(() => []),
|
|
163
|
+
]);
|
|
164
|
+
console.log(` 👤 Customer 360: ${e360}`);
|
|
165
|
+
console.log(" " + "═".repeat(50));
|
|
166
|
+
if (customer) {
|
|
167
|
+
console.log(` 📦 Orders: ${customer.orders.length}`);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.log(" 📦 No Billbee data");
|
|
171
|
+
}
|
|
172
|
+
console.log(` 🎫 Tickets: ${tickets.length}`);
|
|
173
|
+
break;
|
|
174
|
+
case "back":
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error("\n Error:", error.message);
|
|
180
|
+
}
|
|
181
|
+
console.log("");
|
|
182
|
+
await pressEnterToContinue();
|
|
183
|
+
}
|
|
184
|
+
// ============================================
|
|
185
|
+
// SEATABLE SUBMENU
|
|
186
|
+
// ============================================
|
|
187
|
+
async function seatableSubMenu() {
|
|
188
|
+
const menuItems = [
|
|
189
|
+
{ name: "List tables", value: "tables" },
|
|
190
|
+
{ name: "List rows", value: "rows" },
|
|
191
|
+
{ name: "---", value: "sep" },
|
|
192
|
+
{ name: "[PREBUILD] Find supplier", value: "supplier" },
|
|
193
|
+
{ name: "[PREBUILD] Stock check", value: "stock" },
|
|
194
|
+
{ name: "Back", value: "back" },
|
|
195
|
+
];
|
|
196
|
+
const choice = await select({
|
|
197
|
+
message: "SeaTable:",
|
|
198
|
+
choices: menuItems.filter(m => !m.value.startsWith("sep")),
|
|
199
|
+
});
|
|
200
|
+
try {
|
|
201
|
+
const client = SeaTableClient.fromConfig();
|
|
202
|
+
switch (choice) {
|
|
203
|
+
case "tables":
|
|
204
|
+
console.log("\n Fetching tables...\n");
|
|
205
|
+
const meta = await client.getMetadata();
|
|
206
|
+
for (const t of meta.tables) {
|
|
207
|
+
console.log(` 📋 ${t.name}`);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
case "rows":
|
|
211
|
+
const tableName = await input({ message: "Table name:" });
|
|
212
|
+
console.log("\n Fetching rows...\n");
|
|
213
|
+
const rows = await client.listRows({ table_name: tableName, limit: 10 });
|
|
214
|
+
console.log(` Found ${rows.length} rows`);
|
|
215
|
+
for (const r of rows) {
|
|
216
|
+
console.log(` [${r._id.slice(0, 8)}] ${JSON.stringify(r).slice(0, 60)}...`);
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
case "supplier":
|
|
220
|
+
const query = await input({ message: "Search suppliers:" });
|
|
221
|
+
console.log("\n Searching...\n");
|
|
222
|
+
const allRows = await client.listRows({ table_name: "Suppliers", limit: 100 });
|
|
223
|
+
const lq = query.toLowerCase();
|
|
224
|
+
const filtered = allRows.filter(r => String(r.Name || "").toLowerCase().includes(lq) ||
|
|
225
|
+
String(r.TAG || "").toLowerCase().includes(lq));
|
|
226
|
+
for (const r of filtered.slice(0, 10)) {
|
|
227
|
+
console.log(` ${r.Name || r._id} │ ${r.TAG || ""}`);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
case "stock":
|
|
231
|
+
const sku = await input({ message: "SKU:" });
|
|
232
|
+
console.log("\n Checking stock...\n");
|
|
233
|
+
const sources = await client.listRows({ table_name: "Supplier_Sources", limit: 100 });
|
|
234
|
+
const matches = sources.filter(s => String(s.SKU || s.sku || "").includes(sku));
|
|
235
|
+
for (const s of matches) {
|
|
236
|
+
console.log(` ${s.Supplier || "?"} │ Stock: ${s.Stock || "?"} │ Price: ${s.Price || "?"}`);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
case "back":
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
console.error("\n Error:", error.message);
|
|
245
|
+
}
|
|
246
|
+
console.log("");
|
|
247
|
+
await pressEnterToContinue();
|
|
248
|
+
}
|
|
249
|
+
// ============================================
|
|
250
|
+
// QDRANT SUBMENU
|
|
251
|
+
// ============================================
|
|
252
|
+
async function qdrantSubMenu() {
|
|
253
|
+
const menuItems = [
|
|
254
|
+
{ name: "List collections", value: "list" },
|
|
255
|
+
{ name: "Collection info", value: "info" },
|
|
256
|
+
{ name: "Back", value: "back" },
|
|
257
|
+
];
|
|
258
|
+
const choice = await select({
|
|
259
|
+
message: "Qdrant:",
|
|
260
|
+
choices: menuItems,
|
|
261
|
+
});
|
|
262
|
+
try {
|
|
263
|
+
const client = QdrantClient.fromConfig();
|
|
264
|
+
switch (choice) {
|
|
265
|
+
case "list":
|
|
266
|
+
console.log("\n Fetching collections...\n");
|
|
267
|
+
const collections = await client.listCollections();
|
|
268
|
+
for (const name of collections) {
|
|
269
|
+
const info = await client.getCollection(name).catch(() => null);
|
|
270
|
+
console.log(` 📁 ${name.padEnd(35)} │ ${info?.pointsCount || "?"} points`);
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
case "info":
|
|
274
|
+
const collName = await input({ message: "Collection name:" });
|
|
275
|
+
const info = await client.getCollection(collName);
|
|
276
|
+
console.log(`\n 📁 ${info.name}`);
|
|
277
|
+
console.log(` Points: ${info.pointsCount}`);
|
|
278
|
+
break;
|
|
279
|
+
case "back":
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
console.error("\n Error:", error.message);
|
|
285
|
+
}
|
|
286
|
+
console.log("");
|
|
287
|
+
await pressEnterToContinue();
|
|
288
|
+
}
|
|
289
|
+
export default businessMenu;
|
|
@@ -13,6 +13,7 @@ import { roadmapsMenu } from "./interactive/roadmaps.js";
|
|
|
13
13
|
import { strategyMenu } from "./interactive/strategy.js";
|
|
14
14
|
import { changelogMenu } from "./interactive/changelog.js";
|
|
15
15
|
import { worktreesMenu } from "./interactive/worktrees.js";
|
|
16
|
+
import { businessMenu } from "./interactive/business.js";
|
|
16
17
|
import { clearScreen, printHeader, pressEnterToContinue } from "./interactive/utils.js";
|
|
17
18
|
// ============================================
|
|
18
19
|
// MAIN MENU
|
|
@@ -36,6 +37,7 @@ export async function runInteractiveMode() {
|
|
|
36
37
|
{ name: "Jules Sessions", value: "jules", description: "Manage Jules AI sessions" },
|
|
37
38
|
{ name: "Worktrees", value: "worktrees", description: "Manage Git worktrees for agent isolation" },
|
|
38
39
|
{ name: "---", value: "separator3", description: "" },
|
|
40
|
+
{ name: "Business Operations", value: "business", description: "Billbee, Zendesk, SeaTable, Qdrant" },
|
|
39
41
|
{ name: "Business Strategy", value: "strategy", description: "Manage business strategy" },
|
|
40
42
|
{ name: "Changelog", value: "changelog", description: "Generate and manage changelogs" },
|
|
41
43
|
{ name: "Dashboard Settings", value: "settings", description: "Manage dashboard settings" },
|
|
@@ -82,6 +84,9 @@ export async function runInteractiveMode() {
|
|
|
82
84
|
case "strategy":
|
|
83
85
|
await strategyMenu();
|
|
84
86
|
break;
|
|
87
|
+
case "business":
|
|
88
|
+
await businessMenu();
|
|
89
|
+
break;
|
|
85
90
|
case "changelog":
|
|
86
91
|
await changelogMenu();
|
|
87
92
|
break;
|
package/dist/commands/task.js
CHANGED
|
@@ -334,7 +334,7 @@ taskCommand
|
|
|
334
334
|
.description("Update task properties")
|
|
335
335
|
.option("-t, --title <title>", "New title")
|
|
336
336
|
.option("-d, --description <desc>", "New description")
|
|
337
|
-
.option("--status <status>", "New status (backlog, in_progress, review, done)")
|
|
337
|
+
.option("--status <status>", "New status (e.g., backlog, in_progress, review, done, or custom status)")
|
|
338
338
|
.option("--priority <priority>", "New priority (low, medium, high, urgent)")
|
|
339
339
|
.option("--assignee <assignee>", "New assignee (human, llm, unassigned)")
|
|
340
340
|
.option("--project <projectId>", "Link to project")
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ const program = new Command();
|
|
|
25
25
|
program
|
|
26
26
|
.name("husky")
|
|
27
27
|
.description("CLI for Huskyv0 Task Orchestration with Claude Agent")
|
|
28
|
-
.version("0.9.
|
|
28
|
+
.version("0.9.2");
|
|
29
29
|
program.addCommand(taskCommand);
|
|
30
30
|
program.addCommand(configCommand);
|
|
31
31
|
program.addCommand(agentCommand);
|
package/dist/lib/biz/billbee.js
CHANGED
|
@@ -10,20 +10,23 @@ export class BillbeeClient {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Create client from Husky config
|
|
13
|
+
* Priority: PROD_* env vars > env vars > local config
|
|
13
14
|
*/
|
|
14
15
|
static fromConfig() {
|
|
15
16
|
const config = getConfig();
|
|
17
|
+
const env = process.env.HUSKY_ENV || 'PROD';
|
|
16
18
|
const billbeeConfig = {
|
|
17
|
-
API_KEY:
|
|
18
|
-
USERNAME:
|
|
19
|
-
PASSWORD:
|
|
20
|
-
BASE_URL:
|
|
19
|
+
API_KEY: process.env[`${env}_BILLBEE_API_KEY`] || process.env.BILLBEE_API_KEY || config.billbeeApiKey || '',
|
|
20
|
+
USERNAME: process.env[`${env}_BILLBEE_USERNAME`] || process.env.BILLBEE_USERNAME || config.billbeeUsername || '',
|
|
21
|
+
PASSWORD: process.env[`${env}_BILLBEE_PASSWORD`] || process.env.BILLBEE_PASSWORD || config.billbeePassword || '',
|
|
22
|
+
BASE_URL: process.env[`${env}_BILLBEE_BASE_URL`] || process.env.BILLBEE_BASE_URL || config.billbeeBaseUrl || 'https://app.billbee.io/api/v1',
|
|
21
23
|
};
|
|
22
24
|
if (!billbeeConfig.API_KEY || !billbeeConfig.USERNAME || !billbeeConfig.PASSWORD) {
|
|
23
25
|
throw new Error('Missing Billbee credentials. Configure with:\n' +
|
|
24
26
|
' husky config set billbee-api-key <key>\n' +
|
|
25
27
|
' husky config set billbee-username <email>\n' +
|
|
26
|
-
' husky config set billbee-password <password
|
|
28
|
+
' husky config set billbee-password <password>\n' +
|
|
29
|
+
'Or set env vars: PROD_BILLBEE_API_KEY, PROD_BILLBEE_USERNAME, PROD_BILLBEE_PASSWORD');
|
|
27
30
|
}
|
|
28
31
|
return new BillbeeClient(billbeeConfig);
|
|
29
32
|
}
|
package/dist/lib/biz/qdrant.d.ts
CHANGED
package/dist/lib/biz/qdrant.js
CHANGED
|
@@ -15,18 +15,21 @@ export class QdrantClient {
|
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Create client from Husky config
|
|
18
|
+
* Priority: PROD_* env vars > env vars > local config
|
|
18
19
|
*/
|
|
19
20
|
static fromConfig() {
|
|
20
21
|
const config = getConfig();
|
|
22
|
+
const env = process.env.HUSKY_ENV || 'PROD';
|
|
21
23
|
const qdrantConfig = {
|
|
22
|
-
url:
|
|
23
|
-
apiKey:
|
|
24
|
+
url: process.env[`${env}_QDRANT_URL`] || process.env.QDRANT_URL || config.qdrantUrl || 'http://localhost:6333',
|
|
25
|
+
apiKey: process.env[`${env}_QDRANT_API_KEY`] || process.env.QDRANT_API_KEY || config.qdrantApiKey,
|
|
24
26
|
};
|
|
25
27
|
if (!qdrantConfig.url || qdrantConfig.url === 'http://localhost:6333') {
|
|
26
|
-
if (!process.env.QDRANT_URL) {
|
|
28
|
+
if (!process.env.QDRANT_URL && !process.env[`${env}_QDRANT_URL`]) {
|
|
27
29
|
throw new Error('Missing Qdrant URL. Configure with:\n' +
|
|
28
30
|
' husky config set qdrant-url <url>\n' +
|
|
29
|
-
' husky config set qdrant-api-key <key
|
|
31
|
+
' husky config set qdrant-api-key <key>\n' +
|
|
32
|
+
'Or set env vars: PROD_QDRANT_URL, PROD_QDRANT_API_KEY');
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
return new QdrantClient(qdrantConfig);
|
package/dist/lib/biz/seatable.js
CHANGED
|
@@ -20,16 +20,19 @@ export class SeaTableClient {
|
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Create client from Husky config
|
|
23
|
+
* Priority: PROD_* env vars > env vars > local config
|
|
23
24
|
*/
|
|
24
25
|
static fromConfig() {
|
|
25
26
|
const config = getConfig();
|
|
27
|
+
const env = process.env.HUSKY_ENV || 'PROD';
|
|
26
28
|
const seaTableConfig = {
|
|
27
|
-
apiToken:
|
|
28
|
-
serverUrl:
|
|
29
|
+
apiToken: process.env[`${env}_SEATABLE_API_TOKEN`] || process.env.SEATABLE_API_TOKEN || config.seatableApiToken || '',
|
|
30
|
+
serverUrl: process.env[`${env}_SEATABLE_SERVER_URL`] || process.env.SEATABLE_SERVER_URL || config.seatableServerUrl || 'https://cloud.seatable.io',
|
|
29
31
|
};
|
|
30
32
|
if (!seaTableConfig.apiToken) {
|
|
31
33
|
throw new Error('Missing SeaTable API Token. Configure with:\n' +
|
|
32
|
-
' husky config set seatable-api-token <token>\n
|
|
34
|
+
' husky config set seatable-api-token <token>\n' +
|
|
35
|
+
'Or set env var: PROD_SEATABLE_API_TOKEN\n\n' +
|
|
33
36
|
'Get your token from SeaTable: Base → Advanced → API Token');
|
|
34
37
|
}
|
|
35
38
|
return new SeaTableClient(seaTableConfig);
|
package/dist/lib/biz/zendesk.js
CHANGED
|
@@ -12,19 +12,22 @@ export class ZendeskClient {
|
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
14
|
* Create client from Husky config
|
|
15
|
+
* Priority: PROD_* env vars > env vars > local config
|
|
15
16
|
*/
|
|
16
17
|
static fromConfig() {
|
|
17
18
|
const config = getConfig();
|
|
19
|
+
const env = process.env.HUSKY_ENV || 'PROD';
|
|
18
20
|
const zendeskConfig = {
|
|
19
|
-
SUBDOMAIN:
|
|
20
|
-
EMAIL:
|
|
21
|
-
API_TOKEN:
|
|
21
|
+
SUBDOMAIN: process.env[`${env}_ZENDESK_SUBDOMAIN`] || process.env.ZENDESK_SUBDOMAIN || config.zendeskSubdomain || '',
|
|
22
|
+
EMAIL: process.env[`${env}_ZENDESK_EMAIL`] || process.env.ZENDESK_EMAIL || config.zendeskEmail || '',
|
|
23
|
+
API_TOKEN: process.env[`${env}_ZENDESK_API_TOKEN`] || process.env.ZENDESK_API_TOKEN || config.zendeskApiToken || '',
|
|
22
24
|
};
|
|
23
25
|
if (!zendeskConfig.SUBDOMAIN || !zendeskConfig.EMAIL || !zendeskConfig.API_TOKEN) {
|
|
24
26
|
throw new Error('Missing Zendesk credentials. Configure with:\n' +
|
|
25
27
|
' husky config set zendesk-subdomain <subdomain>\n' +
|
|
26
28
|
' husky config set zendesk-email <email>\n' +
|
|
27
|
-
' husky config set zendesk-api-token <token
|
|
29
|
+
' husky config set zendesk-api-token <token>\n' +
|
|
30
|
+
'Or set env vars: PROD_ZENDESK_SUBDOMAIN, PROD_ZENDESK_EMAIL, PROD_ZENDESK_API_TOKEN');
|
|
28
31
|
}
|
|
29
32
|
return new ZendeskClient(zendeskConfig);
|
|
30
33
|
}
|