@simonfestl/husky-cli 0.9.1 → 0.9.3

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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Business Operations Interactive Menu
3
+ * Provides access to Billbee, Zendesk, SeaTable, and Qdrant
4
+ */
5
+ export declare function businessMenu(): Promise<void>;
6
+ export default businessMenu;
@@ -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;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * LLM Context Generator
3
+ * Outputs markdown reference for LLM agents
4
+ */
5
+ export declare function generateLLMContext(): string;
6
+ export declare function printLLMContext(): void;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * LLM Context Generator
3
+ * Outputs markdown reference for LLM agents
4
+ */
5
+ const VERSION = "0.9.3";
6
+ export function generateLLMContext() {
7
+ return `# Husky CLI Reference (v${VERSION})
8
+
9
+ > [!CAUTION]
10
+ > ## MANDATORY: You MUST Use Husky CLI
11
+ >
12
+ > As an AI agent working in this environment, you are **REQUIRED** to use the Husky CLI for all business operations and task management.
13
+ >
14
+ > **DO NOT:**
15
+ > - Make direct API calls to Billbee, Zendesk, or other services
16
+ > - Bypass Husky CLI for task management
17
+ > - Create custom integrations when Husky commands exist
18
+ >
19
+ > **ALWAYS:**
20
+ > - Use \`husky biz\` commands for business operations
21
+ > - Use \`husky task\` commands for task lifecycle
22
+ > - Use \`husky worktree\` for Git isolation
23
+ > - Check \`husky config test\` before operations
24
+
25
+ ---
26
+
27
+ ## What is Husky?
28
+
29
+ Husky is the **central orchestration layer** for AI agents. It provides:
30
+ - **Dashboard API** - Task management, projects, roadmaps
31
+ - **Billbee** - E-commerce orders and products
32
+ - **Zendesk** - Customer support tickets
33
+ - **SeaTable** - Supply chain and inventory
34
+ - **Qdrant** - Vector database for semantic search
35
+ - **Vertex AI** - Embeddings for similarity search
36
+
37
+ ---
38
+
39
+ ## Configuration
40
+
41
+ \`\`\`bash
42
+ husky config set api-url <dashboard-url>
43
+ husky config set api-key <key>
44
+ husky config list
45
+ husky config test
46
+ \`\`\`
47
+
48
+ ---
49
+
50
+ ## Core Commands
51
+
52
+ ### Tasks
53
+ \`\`\`bash
54
+ husky task list [--status <status>] # List tasks
55
+ husky task get <id> # Get task details
56
+ husky task create # Create new task
57
+ husky task start <id> # Start working on task
58
+ husky task done <id> [--pr <url>] # Mark task as done
59
+ husky task update <id> # Update task fields
60
+ \`\`\`
61
+
62
+ ### Projects
63
+ \`\`\`bash
64
+ husky project list # List projects
65
+ husky project get <id> # Get project details
66
+ husky project create # Create new project
67
+ \`\`\`
68
+
69
+ ### Worktrees (Git)
70
+ \`\`\`bash
71
+ husky worktree list # List worktrees
72
+ husky worktree create <name> # Create isolated worktree
73
+ husky worktree delete <name> # Delete worktree
74
+ \`\`\`
75
+
76
+ ---
77
+
78
+ ## Business Operations (husky biz)
79
+
80
+ ### Tickets (Zendesk)
81
+ \`\`\`bash
82
+ husky biz tickets list # List recent tickets
83
+ husky biz tickets get <id> # Get ticket details
84
+ husky biz tickets search "<query>" # Search tickets
85
+ husky biz tickets reply <id> "<msg>" # Reply to ticket
86
+ husky biz tickets note <id> "<note>" # Add internal note
87
+ husky biz tickets close <id> # Close ticket
88
+
89
+ # Prebuild (Semantic Search)
90
+ husky biz tickets similar <id> # Find similar tickets
91
+ husky biz tickets find-similar "<q>" # Semantic ticket search
92
+ husky biz tickets knowledge "<query>" # Search resolved tickets
93
+ \`\`\`
94
+
95
+ ### Orders (Billbee)
96
+ \`\`\`bash
97
+ husky biz orders list # List orders
98
+ husky biz orders get <id> # Get order details
99
+ husky biz orders search "<query>" # Search orders
100
+ \`\`\`
101
+
102
+ ### Products (Billbee)
103
+ \`\`\`bash
104
+ husky biz products list # List products
105
+ husky biz products get <id> # Get product details
106
+ husky biz products sku <sku> # Get by SKU
107
+ \`\`\`
108
+
109
+ ### Customers
110
+ \`\`\`bash
111
+ husky biz customers search <email> # Search by email
112
+ husky biz customers orders <email> # Get order history
113
+ husky biz customers 360 <email> # Full customer view (Billbee + Zendesk)
114
+ \`\`\`
115
+
116
+ ### SeaTable (Supply Chain)
117
+ \`\`\`bash
118
+ husky biz seatable tables # List tables
119
+ husky biz seatable rows <table> # List rows
120
+ husky biz seatable find-supplier "<q>" # Search suppliers
121
+ husky biz seatable order-status <no> # Get order status
122
+ husky biz seatable stock-check <sku> # Check stock levels
123
+ \`\`\`
124
+
125
+ ### Qdrant (Vector DB)
126
+ \`\`\`bash
127
+ husky biz qdrant collections # List collections
128
+ husky biz qdrant info <name> # Collection info
129
+ husky biz qdrant count <name> # Count points
130
+ husky biz qdrant search <coll> "<q>" # Semantic search
131
+ \`\`\`
132
+
133
+ ---
134
+
135
+ ## Environment Variables
136
+
137
+ | Variable | Description |
138
+ |----------|-------------|
139
+ | \`HUSKY_ENV\` | Environment prefix (PROD/SANDBOX) |
140
+ | \`PROD_BILLBEE_API_KEY\` | Billbee API key |
141
+ | \`PROD_ZENDESK_API_TOKEN\` | Zendesk token |
142
+ | \`PROD_SEATABLE_API_TOKEN\` | SeaTable token |
143
+ | \`PROD_QDRANT_URL\` | Qdrant URL |
144
+ | \`PROD_QDRANT_API_KEY\` | Qdrant API key |
145
+
146
+ ---
147
+
148
+ ## Usage Tips for LLM Agents
149
+
150
+ 1. **Always use \`--json\` flag** when available for structured output
151
+ 2. **Check config first**: \`husky config test\` before API calls
152
+ 3. **Use prebuild commands** for complex workflows (e.g., \`tickets similar\`)
153
+ 4. **Customer 360** combines Billbee + Zendesk data in one call
154
+ 5. **Semantic search** requires Qdrant and Vertex AI configured
155
+ `;
156
+ }
157
+ export function printLLMContext() {
158
+ console.log(generateLLMContext());
159
+ }
@@ -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
@@ -20,12 +20,14 @@ import { completionCommand } from "./commands/completion.js";
20
20
  import { worktreeCommand } from "./commands/worktree.js";
21
21
  import { workerCommand } from "./commands/worker.js";
22
22
  import { bizCommand } from "./commands/biz.js";
23
+ import { printLLMContext } from "./commands/llm-context.js";
23
24
  import { runInteractiveMode } from "./commands/interactive.js";
24
25
  const program = new Command();
25
26
  program
26
27
  .name("husky")
27
28
  .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
28
- .version("0.9.1");
29
+ .version("0.9.3")
30
+ .option("--llm", "Output LLM reference documentation (markdown)");
29
31
  program.addCommand(taskCommand);
30
32
  program.addCommand(configCommand);
31
33
  program.addCommand(agentCommand);
@@ -46,6 +48,11 @@ program.addCommand(completionCommand);
46
48
  program.addCommand(worktreeCommand);
47
49
  program.addCommand(workerCommand);
48
50
  program.addCommand(bizCommand);
51
+ // Handle --llm flag specially
52
+ if (process.argv.includes("--llm")) {
53
+ printLLMContext();
54
+ process.exit(0);
55
+ }
49
56
  // Check if no command was provided - run interactive mode
50
57
  if (process.argv.length <= 2) {
51
58
  runInteractiveMode();
@@ -8,6 +8,7 @@ export declare class BillbeeClient {
8
8
  constructor(config: BillbeeConfig);
9
9
  /**
10
10
  * Create client from Husky config
11
+ * Priority: PROD_* env vars > env vars > local config
11
12
  */
12
13
  static fromConfig(): BillbeeClient;
13
14
  /**
@@ -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: 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',
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
  }
@@ -33,6 +33,7 @@ export declare class QdrantClient {
33
33
  constructor(config: QdrantConfig);
34
34
  /**
35
35
  * Create client from Husky config
36
+ * Priority: PROD_* env vars > env vars > local config
36
37
  */
37
38
  static fromConfig(): QdrantClient;
38
39
  private request;
@@ -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: config.qdrantUrl || process.env.QDRANT_URL || 'http://localhost:6333',
23
- apiKey: config.qdrantApiKey || process.env.QDRANT_API_KEY,
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);
@@ -17,6 +17,7 @@ export declare class SeaTableClient {
17
17
  constructor(config: SeaTableConfig);
18
18
  /**
19
19
  * Create client from Husky config
20
+ * Priority: PROD_* env vars > env vars > local config
20
21
  */
21
22
  static fromConfig(): SeaTableClient;
22
23
  private isApiGateway;
@@ -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: config.seatableApiToken || process.env.SEATABLE_API_TOKEN || '',
28
- serverUrl: config.seatableServerUrl || process.env.SEATABLE_SERVER_URL || 'https://cloud.seatable.io',
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\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);
@@ -9,6 +9,7 @@ export declare class ZendeskClient {
9
9
  constructor(config: ZendeskConfig);
10
10
  /**
11
11
  * Create client from Husky config
12
+ * Priority: PROD_* env vars > env vars > local config
12
13
  */
13
14
  static fromConfig(): ZendeskClient;
14
15
  /**
@@ -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: config.zendeskSubdomain || process.env.ZENDESK_SUBDOMAIN || '',
20
- EMAIL: config.zendeskEmail || process.env.ZENDESK_EMAIL || '',
21
- API_TOKEN: config.zendeskApiToken || process.env.ZENDESK_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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {