@simonfestl/husky-cli 1.7.0 → 1.9.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.
@@ -15,21 +15,23 @@ export class QdrantClient {
15
15
  }
16
16
  /**
17
17
  * Create client from Husky config
18
- * Priority: PROD_* env vars > env vars > local config
18
+ * Priority: HUSKY_QDRANT_URL (VM standard) > PROD_* env vars > config > localhost
19
19
  */
20
20
  static fromConfig() {
21
21
  const config = getConfig();
22
22
  const env = process.env.HUSKY_ENV || 'PROD';
23
23
  const qdrantConfig = {
24
- url: process.env[`${env}_QDRANT_URL`] || process.env.QDRANT_URL || config.qdrantUrl || 'http://localhost:6333',
24
+ // Priority: HUSKY_QDRANT_URL (VM standard) > PROD_QDRANT_URL > config > localhost
25
+ url: process.env.HUSKY_QDRANT_URL || process.env[`${env}_QDRANT_URL`] || process.env.QDRANT_URL || config.qdrantUrl || 'http://localhost:6333',
26
+ // API key optional - internal Qdrant VM is VPC secured
25
27
  apiKey: process.env[`${env}_QDRANT_API_KEY`] || process.env.QDRANT_API_KEY || config.qdrantApiKey,
26
28
  };
27
29
  if (!qdrantConfig.url || qdrantConfig.url === 'http://localhost:6333') {
28
- if (!process.env.QDRANT_URL && !process.env[`${env}_QDRANT_URL`]) {
30
+ if (!process.env.QDRANT_URL && !process.env[`${env}_QDRANT_URL`] && !process.env.HUSKY_QDRANT_URL) {
29
31
  throw new Error('Missing Qdrant URL. Configure with:\n' +
30
- ' husky config set qdrant-url <url>\n' +
31
- ' husky config set qdrant-api-key <key>\n' +
32
- 'Or set env vars: PROD_QDRANT_URL, PROD_QDRANT_API_KEY');
32
+ ' husky config set qdrant-url http://10.132.0.46:6333\n' +
33
+ 'Or set env var: HUSKY_QDRANT_URL\n\n' +
34
+ 'Note: Internal Qdrant VM - no API key needed (VPC secured)');
33
35
  }
34
36
  }
35
37
  return new QdrantClient(qdrantConfig);
@@ -153,6 +155,15 @@ export class QdrantClient {
153
155
  body: JSON.stringify({ points: ids }),
154
156
  });
155
157
  }
158
+ async setPayload(collectionName, pointId, payload) {
159
+ await this.request(`/collections/${collectionName}/points/payload?wait=true`, {
160
+ method: 'POST',
161
+ body: JSON.stringify({
162
+ points: [pointId],
163
+ payload,
164
+ }),
165
+ });
166
+ }
156
167
  async count(collectionName) {
157
168
  const info = await this.getCollection(collectionName);
158
169
  return info.pointsCount;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SOP (Standard Operating Procedure) Generator
3
+ *
4
+ * Generates SOPs from agent learnings using LLM.
5
+ * Leverages PII-free embeddings for safe processing.
6
+ */
7
+ import { AgentType } from './agent-brain.js';
8
+ export interface SOPGenerationOptions {
9
+ topic: string;
10
+ agentType?: AgentType;
11
+ minMemories?: number;
12
+ format?: 'markdown' | 'json';
13
+ }
14
+ export interface SOPSection {
15
+ title: string;
16
+ content: string;
17
+ examples?: string[];
18
+ }
19
+ export interface SOP {
20
+ title: string;
21
+ topic: string;
22
+ sections: SOPSection[];
23
+ sourceCount: number;
24
+ generatedAt: string;
25
+ }
26
+ /**
27
+ * Generate SOP from learnings
28
+ *
29
+ * TODO: In future, use Vertex AI/Gemini to:
30
+ * 1. Recall relevant memories by topic
31
+ * 2. Group and cluster similar learnings
32
+ * 3. Extract patterns and best practices
33
+ * 4. Generate structured SOP document
34
+ */
35
+ export declare function generateSOP(agentId: string, options: SOPGenerationOptions): Promise<SOP>;
36
+ /**
37
+ * Format SOP as Markdown
38
+ */
39
+ export declare function formatSOPAsMarkdown(sop: SOP): string;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * SOP (Standard Operating Procedure) Generator
3
+ *
4
+ * Generates SOPs from agent learnings using LLM.
5
+ * Leverages PII-free embeddings for safe processing.
6
+ */
7
+ import { AgentBrain } from './agent-brain.js';
8
+ /**
9
+ * Generate SOP from learnings
10
+ *
11
+ * TODO: In future, use Vertex AI/Gemini to:
12
+ * 1. Recall relevant memories by topic
13
+ * 2. Group and cluster similar learnings
14
+ * 3. Extract patterns and best practices
15
+ * 4. Generate structured SOP document
16
+ */
17
+ export async function generateSOP(agentId, options) {
18
+ const { topic, agentType, minMemories = 5, format = 'markdown' } = options;
19
+ // Initialize brain
20
+ const brain = new AgentBrain({ agentId, agentType });
21
+ // Recall memories related to topic
22
+ const results = await brain.recall(topic, 50, 0.3);
23
+ if (results.length < minMemories) {
24
+ throw new Error(`Not enough learnings found for topic "${topic}" (found ${results.length}, need ${minMemories})`);
25
+ }
26
+ // Group memories by tags
27
+ const grouped = groupMemoriesByTags(results.map(r => r.memory));
28
+ // Generate SOP sections (basic version - TODO: use LLM for better structure)
29
+ const sections = [];
30
+ for (const [tag, memories] of Object.entries(grouped)) {
31
+ sections.push({
32
+ title: formatTagAsTitle(tag),
33
+ content: summarizeMemories(memories),
34
+ examples: memories.slice(0, 3).map(m => m.content),
35
+ });
36
+ }
37
+ return {
38
+ title: `SOP: ${topic}`,
39
+ topic,
40
+ sections,
41
+ sourceCount: results.length,
42
+ generatedAt: new Date().toISOString(),
43
+ };
44
+ }
45
+ /**
46
+ * Group memories by tags
47
+ */
48
+ function groupMemoriesByTags(memories) {
49
+ const groups = {};
50
+ for (const memory of memories) {
51
+ for (const tag of memory.tags) {
52
+ // Skip task/project specific tags
53
+ if (tag.startsWith('task:') || tag.startsWith('project:')) {
54
+ continue;
55
+ }
56
+ if (!groups[tag]) {
57
+ groups[tag] = [];
58
+ }
59
+ groups[tag].push(memory);
60
+ }
61
+ }
62
+ // Sort by number of memories
63
+ return Object.fromEntries(Object.entries(groups).sort((a, b) => b[1].length - a[1].length));
64
+ }
65
+ /**
66
+ * Format tag as section title
67
+ */
68
+ function formatTagAsTitle(tag) {
69
+ // Remove prefixes
70
+ const cleaned = tag.replace(/^(type|source|category):/, '');
71
+ // Capitalize
72
+ return cleaned
73
+ .split(/[-_]/)
74
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
75
+ .join(' ');
76
+ }
77
+ /**
78
+ * Summarize memories into a paragraph
79
+ */
80
+ function summarizeMemories(memories) {
81
+ // Basic summarization (TODO: use LLM for better summary)
82
+ if (memories.length === 1) {
83
+ return memories[0].content;
84
+ }
85
+ const commonThemes = extractCommonThemes(memories);
86
+ return `Based on ${memories.length} learnings: ${commonThemes}`;
87
+ }
88
+ /**
89
+ * Extract common themes from memories
90
+ */
91
+ function extractCommonThemes(memories) {
92
+ // Simple word frequency analysis (TODO: use LLM for semantic analysis)
93
+ const words = {};
94
+ for (const memory of memories) {
95
+ const tokens = memory.content
96
+ .toLowerCase()
97
+ .split(/\W+/)
98
+ .filter(w => w.length > 4); // Only words > 4 chars
99
+ for (const word of tokens) {
100
+ words[word] = (words[word] || 0) + 1;
101
+ }
102
+ }
103
+ // Get top 5 words
104
+ const topWords = Object.entries(words)
105
+ .sort((a, b) => b[1] - a[1])
106
+ .slice(0, 5)
107
+ .map(([word]) => word);
108
+ return `Common themes include: ${topWords.join(', ')}`;
109
+ }
110
+ /**
111
+ * Format SOP as Markdown
112
+ */
113
+ export function formatSOPAsMarkdown(sop) {
114
+ let md = `# ${sop.title}\n\n`;
115
+ md += `**Topic:** ${sop.topic} \n`;
116
+ md += `**Generated:** ${new Date(sop.generatedAt).toLocaleString()} \n`;
117
+ md += `**Sources:** ${sop.sourceCount} learnings \n\n`;
118
+ md += `---\n\n`;
119
+ for (const section of sop.sections) {
120
+ md += `## ${section.title}\n\n`;
121
+ md += `${section.content}\n\n`;
122
+ if (section.examples && section.examples.length > 0) {
123
+ md += `### Examples\n\n`;
124
+ for (const example of section.examples) {
125
+ md += `- ${example}\n`;
126
+ }
127
+ md += `\n`;
128
+ }
129
+ }
130
+ return md;
131
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Error Hints System
3
+ *
4
+ * Provides helpful hints and references to `husky explain` when users encounter errors.
5
+ * This makes the CLI more user-friendly by guiding users to relevant documentation.
6
+ */
7
+ /**
8
+ * Map of error categories to their corresponding `husky explain` topics
9
+ */
10
+ export declare enum ExplainTopic {
11
+ TASK = "task",
12
+ CONFIG = "config",
13
+ ROADMAP = "roadmap",
14
+ CHANGELOG = "changelog",
15
+ AGENT = "agent"
16
+ }
17
+ /**
18
+ * Print an error message with an automatic hint based on content
19
+ */
20
+ export declare function errorWithAutoHint(message: string, exitCode?: number): never;
21
+ /**
22
+ * Print an error message with a specific hint topic
23
+ */
24
+ export declare function errorWithHint(message: string, topic: ExplainTopic, customHint?: string, exitCode?: number): never;
25
+ /**
26
+ * Print an error message with no hint (for errors where no help is available)
27
+ */
28
+ export declare function errorWithoutHint(message: string, exitCode?: number): never;
29
+ /**
30
+ * Predefined error helpers for common scenarios
31
+ */
32
+ export declare const ErrorHelpers: {
33
+ /**
34
+ * Error: Task ID not provided
35
+ */
36
+ missingTaskId: () => never;
37
+ /**
38
+ * Error: API URL not configured
39
+ */
40
+ missingApiUrl: () => never;
41
+ /**
42
+ * Error: API key not configured
43
+ */
44
+ missingApiKey: () => never;
45
+ /**
46
+ * Error: Both API URL and key not configured
47
+ */
48
+ missingConfig: () => never;
49
+ /**
50
+ * Error: Permission denied
51
+ */
52
+ permissionDenied: (operation: string) => never;
53
+ /**
54
+ * Error: Invalid task status
55
+ */
56
+ invalidTaskStatus: (status: string) => never;
57
+ /**
58
+ * Error: Task operation failed
59
+ */
60
+ taskOperationFailed: (operation: string, reason: string) => never;
61
+ /**
62
+ * Error: API request failed
63
+ */
64
+ apiRequestFailed: (status: number, message: string) => never;
65
+ };
66
+ /**
67
+ * Format a warning message with a hint (non-fatal)
68
+ */
69
+ export declare function warningWithHint(message: string, topic: ExplainTopic, customHint?: string): void;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Error Hints System
3
+ *
4
+ * Provides helpful hints and references to `husky explain` when users encounter errors.
5
+ * This makes the CLI more user-friendly by guiding users to relevant documentation.
6
+ */
7
+ /**
8
+ * Map of error categories to their corresponding `husky explain` topics
9
+ */
10
+ export var ExplainTopic;
11
+ (function (ExplainTopic) {
12
+ ExplainTopic["TASK"] = "task";
13
+ ExplainTopic["CONFIG"] = "config";
14
+ ExplainTopic["ROADMAP"] = "roadmap";
15
+ ExplainTopic["CHANGELOG"] = "changelog";
16
+ ExplainTopic["AGENT"] = "agent";
17
+ })(ExplainTopic || (ExplainTopic = {}));
18
+ const ERROR_PATTERNS = [
19
+ {
20
+ keywords: ["task id", "HUSKY_TASK_ID", "task status", "task complete", "task start"],
21
+ topic: ExplainTopic.TASK,
22
+ },
23
+ {
24
+ keywords: ["api url", "api key", "not configured", "config set", "authentication"],
25
+ topic: ExplainTopic.CONFIG,
26
+ },
27
+ {
28
+ keywords: ["roadmap", "phase", "feature"],
29
+ topic: ExplainTopic.ROADMAP,
30
+ },
31
+ {
32
+ keywords: ["changelog", "version", "commits"],
33
+ topic: ExplainTopic.CHANGELOG,
34
+ },
35
+ {
36
+ keywords: ["workflow", "agent", "session"],
37
+ topic: ExplainTopic.AGENT,
38
+ },
39
+ ];
40
+ /**
41
+ * Detect which explain topic is most relevant based on error message
42
+ */
43
+ function detectExplainTopic(message) {
44
+ const lowerMessage = message.toLowerCase();
45
+ for (const pattern of ERROR_PATTERNS) {
46
+ if (pattern.keywords.some(keyword => lowerMessage.includes(keyword))) {
47
+ return pattern.topic;
48
+ }
49
+ }
50
+ return null;
51
+ }
52
+ /**
53
+ * Format a hint message for a specific explain topic
54
+ */
55
+ function formatHint(topic, customHint) {
56
+ if (customHint) {
57
+ return `\nšŸ’” Hint: ${customHint}\n Run: husky explain ${topic}`;
58
+ }
59
+ const hints = {
60
+ [ExplainTopic.TASK]: "For task workflow help",
61
+ [ExplainTopic.CONFIG]: "For configuration help",
62
+ [ExplainTopic.ROADMAP]: "For roadmap commands help",
63
+ [ExplainTopic.CHANGELOG]: "For changelog commands help",
64
+ [ExplainTopic.AGENT]: "For agent workflow examples",
65
+ };
66
+ return `\nšŸ’” ${hints[topic]}: husky explain ${topic}`;
67
+ }
68
+ /**
69
+ * Print an error message with an automatic hint based on content
70
+ */
71
+ export function errorWithAutoHint(message, exitCode = 1) {
72
+ console.error(`Error: ${message}`);
73
+ const topic = detectExplainTopic(message);
74
+ if (topic) {
75
+ console.error(formatHint(topic));
76
+ }
77
+ process.exit(exitCode);
78
+ }
79
+ /**
80
+ * Print an error message with a specific hint topic
81
+ */
82
+ export function errorWithHint(message, topic, customHint, exitCode = 1) {
83
+ console.error(`Error: ${message}`);
84
+ console.error(formatHint(topic, customHint));
85
+ process.exit(exitCode);
86
+ }
87
+ /**
88
+ * Print an error message with no hint (for errors where no help is available)
89
+ */
90
+ export function errorWithoutHint(message, exitCode = 1) {
91
+ console.error(`Error: ${message}`);
92
+ process.exit(exitCode);
93
+ }
94
+ /**
95
+ * Predefined error helpers for common scenarios
96
+ */
97
+ export const ErrorHelpers = {
98
+ /**
99
+ * Error: Task ID not provided
100
+ */
101
+ missingTaskId: () => {
102
+ errorWithHint("Task ID required. Use --id or set HUSKY_TASK_ID environment variable.", ExplainTopic.TASK, "Learn about task ID usage and environment variables");
103
+ },
104
+ /**
105
+ * Error: API URL not configured
106
+ */
107
+ missingApiUrl: () => {
108
+ errorWithHint("API URL not configured. Run: husky config set api-url <url>", ExplainTopic.CONFIG, "Learn how to configure the CLI");
109
+ },
110
+ /**
111
+ * Error: API key not configured
112
+ */
113
+ missingApiKey: () => {
114
+ errorWithHint("API key not configured. Run: husky config set api-key <key>", ExplainTopic.CONFIG, "Learn how to configure authentication");
115
+ },
116
+ /**
117
+ * Error: Both API URL and key not configured
118
+ */
119
+ missingConfig: () => {
120
+ errorWithHint("API URL and key required. Run: husky config test", ExplainTopic.CONFIG, "Learn about CLI configuration");
121
+ },
122
+ /**
123
+ * Error: Permission denied
124
+ */
125
+ permissionDenied: (operation) => {
126
+ errorWithAutoHint(`Permission denied: ${operation}`);
127
+ },
128
+ /**
129
+ * Error: Invalid task status
130
+ */
131
+ invalidTaskStatus: (status) => {
132
+ errorWithHint(`Invalid status: ${status}. Valid statuses: backlog, in_progress, review, done`, ExplainTopic.TASK, "See all available task statuses");
133
+ },
134
+ /**
135
+ * Error: Task operation failed
136
+ */
137
+ taskOperationFailed: (operation, reason) => {
138
+ errorWithHint(`Failed to ${operation}: ${reason}`, ExplainTopic.TASK, "Learn about task management");
139
+ },
140
+ /**
141
+ * Error: API request failed
142
+ */
143
+ apiRequestFailed: (status, message) => {
144
+ if (status === 401) {
145
+ errorWithHint("Authentication failed. Check your API key.", ExplainTopic.CONFIG, "Learn how to configure authentication");
146
+ }
147
+ else if (status === 403) {
148
+ errorWithAutoHint(`Permission denied: ${message}`);
149
+ }
150
+ else if (status === 404) {
151
+ errorWithoutHint(`Resource not found: ${message}`);
152
+ }
153
+ else {
154
+ errorWithAutoHint(`API error (${status}): ${message}`);
155
+ }
156
+ },
157
+ };
158
+ /**
159
+ * Format a warning message with a hint (non-fatal)
160
+ */
161
+ export function warningWithHint(message, topic, customHint) {
162
+ console.warn(`⚠ Warning: ${message}`);
163
+ console.warn(formatHint(topic, customHint).replace("šŸ’”", "ā„¹ļø"));
164
+ }
@@ -5,6 +5,7 @@
5
5
  * Permissions are fetched from the API and cached locally.
6
6
  */
7
7
  import { getConfig, hasPermission, getRole, fetchAndCacheRole, clearRoleCache } from "../commands/config.js";
8
+ import { ExplainTopic } from "./error-hints.js";
8
9
  /**
9
10
  * Check if current user has a specific permission.
10
11
  * Uses cached permissions from config.
@@ -27,6 +28,7 @@ export function requirePermission(permission) {
27
28
  else {
28
29
  console.error("Run 'husky config test' to refresh your role and permissions.");
29
30
  }
31
+ console.error(`\nšŸ’” For configuration help: husky explain ${ExplainTopic.CONFIG}`);
30
32
  process.exit(1);
31
33
  }
32
34
  }
@@ -43,6 +45,7 @@ export function requireAnyPermission(permissions) {
43
45
  if (config.role) {
44
46
  console.error(`Your role (${config.role}) does not have these permissions.`);
45
47
  }
48
+ console.error(`\nšŸ’” For configuration help: husky explain ${ExplainTopic.CONFIG}`);
46
49
  process.exit(1);
47
50
  }
48
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "1.7.0",
3
+ "version": "1.9.1",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,9 +20,14 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@anthropic-ai/claude-code": "^1.0.0",
23
+ "@google-cloud/vertexai": "^1.10.0",
24
+ "@google/generative-ai": "^0.24.1",
23
25
  "@inquirer/prompts": "^8.1.0",
24
26
  "commander": "^12.1.0",
25
- "firebase-admin": "^13.6.0"
27
+ "firebase-admin": "^13.6.0",
28
+ "sharp": "^0.34.5",
29
+ "youtube-transcript": "^1.2.1",
30
+ "zod": "^4.3.5"
26
31
  },
27
32
  "devDependencies": {
28
33
  "@types/node": "^22",