@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.
- 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/llm-context.d.ts +6 -0
- package/dist/commands/llm-context.js +159 -0
- package/dist/commands/task.js +1 -1
- package/dist/index.js +8 -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;
|
|
@@ -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
|
+
}
|
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
|
@@ -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.
|
|
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();
|
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
|
}
|