@simonfestl/husky-cli 1.31.1 → 1.33.0
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/diagrams.d.ts +8 -0
- package/dist/commands/diagrams.js +374 -0
- package/dist/commands/project.js +177 -1
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Husky Diagrams Command
|
|
3
|
+
*
|
|
4
|
+
* Generate and manage AI-powered code visualization diagrams (Mermaid)
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { getApiClient } from "../lib/api-client.js";
|
|
9
|
+
// Mermaid diagram type patterns
|
|
10
|
+
const DIAGRAM_PATTERNS = [
|
|
11
|
+
{ name: "flowchart", pattern: /^(flowchart|graph)\s+(TB|BT|LR|RL|TD)/i },
|
|
12
|
+
{ name: "sequenceDiagram", pattern: /^sequenceDiagram/i },
|
|
13
|
+
{ name: "classDiagram", pattern: /^classDiagram/i },
|
|
14
|
+
{ name: "stateDiagram", pattern: /^stateDiagram(-v2)?/i },
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Validate Mermaid diagram syntax
|
|
18
|
+
*/
|
|
19
|
+
function validateMermaid(code) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
let diagramType = null;
|
|
23
|
+
const lines = code.trim().split("\n").map((l) => l.trim());
|
|
24
|
+
const cleanCode = lines.join("\n");
|
|
25
|
+
if (!cleanCode) {
|
|
26
|
+
return { valid: false, diagramType: null, errors: ["Empty diagram"], warnings: [] };
|
|
27
|
+
}
|
|
28
|
+
for (const type of DIAGRAM_PATTERNS) {
|
|
29
|
+
if (type.pattern.test(cleanCode)) {
|
|
30
|
+
diagramType = type.name;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!diagramType) {
|
|
35
|
+
errors.push("Unknown diagram type");
|
|
36
|
+
}
|
|
37
|
+
// Check bracket balance
|
|
38
|
+
const brackets = { "[": 0, "{": 0, "(": 0 };
|
|
39
|
+
for (const char of cleanCode) {
|
|
40
|
+
if (char === "[")
|
|
41
|
+
brackets["["]++;
|
|
42
|
+
else if (char === "]")
|
|
43
|
+
brackets["["]--;
|
|
44
|
+
else if (char === "{")
|
|
45
|
+
brackets["{"]++;
|
|
46
|
+
else if (char === "}")
|
|
47
|
+
brackets["{"]--;
|
|
48
|
+
else if (char === "(")
|
|
49
|
+
brackets["("]++;
|
|
50
|
+
else if (char === ")")
|
|
51
|
+
brackets["("]--;
|
|
52
|
+
}
|
|
53
|
+
for (const [bracket, count] of Object.entries(brackets)) {
|
|
54
|
+
if (count !== 0) {
|
|
55
|
+
errors.push(`Unbalanced brackets: ${bracket}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Check subgraph balance
|
|
59
|
+
const subgraphCount = (cleanCode.match(/\bsubgraph\b/gi) || []).length;
|
|
60
|
+
const endCount = (cleanCode.match(/^\s*end\s*$/gim) || []).length;
|
|
61
|
+
if (subgraphCount > endCount) {
|
|
62
|
+
errors.push(`Missing 'end' for subgraph`);
|
|
63
|
+
}
|
|
64
|
+
return { valid: errors.length === 0, diagramType, errors, warnings };
|
|
65
|
+
}
|
|
66
|
+
// AI Prompts for diagram generation
|
|
67
|
+
const DIAGRAM_PROMPTS = {
|
|
68
|
+
flowchart: `Analyze this codebase and create a Mermaid flowchart showing:
|
|
69
|
+
- Main entry points and their execution flow
|
|
70
|
+
- Key decision points and branches
|
|
71
|
+
- Integration with external services
|
|
72
|
+
- Error handling paths
|
|
73
|
+
|
|
74
|
+
Focus on the high-level architecture, not implementation details.
|
|
75
|
+
Output ONLY valid Mermaid flowchart code starting with "flowchart TD".
|
|
76
|
+
Do not include any explanation or markdown code blocks.`,
|
|
77
|
+
sequence: `Analyze this codebase and create a Mermaid sequence diagram showing:
|
|
78
|
+
- Key user interactions and their flow through the system
|
|
79
|
+
- API request/response patterns
|
|
80
|
+
- Service-to-service communication
|
|
81
|
+
- Database interactions
|
|
82
|
+
|
|
83
|
+
Include the most common/important flows only.
|
|
84
|
+
Output ONLY valid Mermaid sequence diagram code starting with "sequenceDiagram".
|
|
85
|
+
Do not include any explanation or markdown code blocks.`,
|
|
86
|
+
class: `Analyze this codebase and create a Mermaid class diagram showing:
|
|
87
|
+
- Main entities/models and their relationships
|
|
88
|
+
- Key interfaces and their implementations
|
|
89
|
+
- Important inheritance hierarchies
|
|
90
|
+
- Composition relationships
|
|
91
|
+
|
|
92
|
+
Focus on domain models and core abstractions.
|
|
93
|
+
Output ONLY valid Mermaid class diagram code starting with "classDiagram".
|
|
94
|
+
Do not include any explanation or markdown code blocks.`,
|
|
95
|
+
architecture: `Analyze this codebase and create a Mermaid architecture diagram showing:
|
|
96
|
+
- System components and their boundaries
|
|
97
|
+
- External dependencies (databases, APIs, services)
|
|
98
|
+
- Data flow between components
|
|
99
|
+
- Deployment topology
|
|
100
|
+
|
|
101
|
+
Use subgraphs to group related components.
|
|
102
|
+
Output ONLY valid Mermaid flowchart code with subgraphs starting with "flowchart TB".
|
|
103
|
+
Do not include any explanation or markdown code blocks.`,
|
|
104
|
+
};
|
|
105
|
+
export const diagramsCommand = new Command("diagrams").description("Generate and manage AI-powered code visualization diagrams");
|
|
106
|
+
// husky diagrams list
|
|
107
|
+
diagramsCommand
|
|
108
|
+
.command("list")
|
|
109
|
+
.description("List diagrams")
|
|
110
|
+
.option("--repo <url>", "Filter by repository URL")
|
|
111
|
+
.option("--type <type>", "Filter by type (flowchart, sequence, class, architecture)")
|
|
112
|
+
.option("--json", "Output as JSON")
|
|
113
|
+
.action(async (options) => {
|
|
114
|
+
try {
|
|
115
|
+
const api = getApiClient();
|
|
116
|
+
const params = new URLSearchParams();
|
|
117
|
+
if (options.repo)
|
|
118
|
+
params.set("repoUrl", options.repo);
|
|
119
|
+
if (options.type)
|
|
120
|
+
params.set("type", options.type);
|
|
121
|
+
const response = await api.get(`/api/diagrams?${params.toString()}`);
|
|
122
|
+
if (options.json) {
|
|
123
|
+
console.log(JSON.stringify(response.diagrams, null, 2));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (response.diagrams.length === 0) {
|
|
127
|
+
console.log(chalk.yellow("No diagrams found"));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
console.log(chalk.bold(`\nDiagrams (${response.diagrams.length}):\n`));
|
|
131
|
+
for (const diagram of response.diagrams) {
|
|
132
|
+
const typeColor = diagram.type === "flowchart"
|
|
133
|
+
? chalk.blue
|
|
134
|
+
: diagram.type === "sequence"
|
|
135
|
+
? chalk.magenta
|
|
136
|
+
: diagram.type === "class"
|
|
137
|
+
? chalk.green
|
|
138
|
+
: chalk.yellow;
|
|
139
|
+
console.log(` ${chalk.bold(diagram.title)}`);
|
|
140
|
+
console.log(` ID: ${diagram.id}`);
|
|
141
|
+
console.log(` Type: ${typeColor(diagram.type)}`);
|
|
142
|
+
console.log(` Repo: ${diagram.repoUrl}`);
|
|
143
|
+
console.log(` Updated: ${new Date(diagram.updatedAt).toLocaleString()}`);
|
|
144
|
+
console.log();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// husky diagrams get <id>
|
|
153
|
+
diagramsCommand
|
|
154
|
+
.command("get <id>")
|
|
155
|
+
.description("Get a specific diagram")
|
|
156
|
+
.option("--json", "Output as JSON")
|
|
157
|
+
.option("--code", "Output only the Mermaid code")
|
|
158
|
+
.action(async (id, options) => {
|
|
159
|
+
try {
|
|
160
|
+
const api = getApiClient();
|
|
161
|
+
const diagram = await api.get(`/api/diagrams/${id}`);
|
|
162
|
+
if (options.code) {
|
|
163
|
+
console.log(diagram.mermaidCode);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (options.json) {
|
|
167
|
+
console.log(JSON.stringify(diagram, null, 2));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk.bold(`\n${diagram.title}\n`));
|
|
171
|
+
console.log(`Type: ${diagram.type}`);
|
|
172
|
+
console.log(`Repo: ${diagram.repoUrl}`);
|
|
173
|
+
if (diagram.description)
|
|
174
|
+
console.log(`Description: ${diagram.description}`);
|
|
175
|
+
if (diagram.gitCommitSha)
|
|
176
|
+
console.log(`Commit: ${diagram.gitCommitSha}`);
|
|
177
|
+
console.log(`Generated by: ${diagram.generatedBy}`);
|
|
178
|
+
console.log();
|
|
179
|
+
console.log(chalk.dim("Mermaid Code:"));
|
|
180
|
+
console.log(chalk.cyan(diagram.mermaidCode));
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// husky diagrams validate <file-or-code>
|
|
188
|
+
diagramsCommand
|
|
189
|
+
.command("validate <input>")
|
|
190
|
+
.description("Validate Mermaid diagram syntax")
|
|
191
|
+
.option("--json", "Output as JSON")
|
|
192
|
+
.action(async (input, options) => {
|
|
193
|
+
try {
|
|
194
|
+
const { readFileSync, existsSync } = await import("fs");
|
|
195
|
+
let code;
|
|
196
|
+
if (existsSync(input)) {
|
|
197
|
+
code = readFileSync(input, "utf-8");
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
code = input;
|
|
201
|
+
}
|
|
202
|
+
const result = validateMermaid(code);
|
|
203
|
+
if (options.json) {
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
process.exit(result.valid ? 0 : 1);
|
|
206
|
+
}
|
|
207
|
+
if (result.diagramType) {
|
|
208
|
+
console.log(`Diagram type: ${result.diagramType}`);
|
|
209
|
+
}
|
|
210
|
+
if (result.errors.length > 0) {
|
|
211
|
+
console.log(chalk.red("\nErrors:"));
|
|
212
|
+
for (const err of result.errors) {
|
|
213
|
+
console.log(` - ${err}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (result.warnings.length > 0) {
|
|
217
|
+
console.log(chalk.yellow("\nWarnings:"));
|
|
218
|
+
for (const warn of result.warnings) {
|
|
219
|
+
console.log(` - ${warn}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (result.valid) {
|
|
223
|
+
console.log(chalk.green("\nDiagram syntax is valid"));
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
console.log(chalk.red("\nDiagram has syntax errors"));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// husky diagrams generate
|
|
236
|
+
diagramsCommand
|
|
237
|
+
.command("generate")
|
|
238
|
+
.description("Generate diagrams for a codebase using AI")
|
|
239
|
+
.option("--repo <url>", "Repository URL (required for upload)")
|
|
240
|
+
.option("--path <path>", "Local path to analyze", ".")
|
|
241
|
+
.option("--types <types>", "Diagram types (comma-separated)", "flowchart,architecture")
|
|
242
|
+
.option("--commit <sha>", "Git commit SHA")
|
|
243
|
+
.option("--upload", "Upload to Husky API")
|
|
244
|
+
.option("--json", "Output as JSON")
|
|
245
|
+
.action(async (options) => {
|
|
246
|
+
try {
|
|
247
|
+
const { readdirSync, readFileSync } = await import("fs");
|
|
248
|
+
const { join, relative } = await import("path");
|
|
249
|
+
const types = options.types.split(",").map((t) => t.trim());
|
|
250
|
+
// Collect codebase summary
|
|
251
|
+
console.log(chalk.blue("Analyzing codebase..."));
|
|
252
|
+
const collectFiles = (dir, files = []) => {
|
|
253
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
254
|
+
for (const entry of entries) {
|
|
255
|
+
const fullPath = join(dir, entry.name);
|
|
256
|
+
if (entry.isDirectory()) {
|
|
257
|
+
if (!["node_modules", ".git", "dist", ".next", "coverage"].includes(entry.name)) {
|
|
258
|
+
collectFiles(fullPath, files);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (entry.isFile() && /\.(ts|tsx|js|jsx|py|go|rs)$/.test(entry.name)) {
|
|
262
|
+
files.push(fullPath);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return files;
|
|
266
|
+
};
|
|
267
|
+
const files = collectFiles(options.path);
|
|
268
|
+
console.log(`Found ${files.length} source files`);
|
|
269
|
+
// Generate diagrams
|
|
270
|
+
const results = [];
|
|
271
|
+
for (const type of types) {
|
|
272
|
+
if (!DIAGRAM_PROMPTS[type]) {
|
|
273
|
+
console.log(chalk.yellow(`Unknown diagram type: ${type}, skipping`));
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
console.log(chalk.blue(`Generating ${type} diagram...`));
|
|
277
|
+
// For now, create a placeholder diagram
|
|
278
|
+
// In production, this would call the Anthropic API
|
|
279
|
+
const mermaidCode = generatePlaceholderDiagram(type);
|
|
280
|
+
const validation = validateMermaid(mermaidCode);
|
|
281
|
+
results.push({
|
|
282
|
+
type,
|
|
283
|
+
title: `${type.charAt(0).toUpperCase() + type.slice(1)} Diagram`,
|
|
284
|
+
mermaidCode,
|
|
285
|
+
valid: validation.valid,
|
|
286
|
+
});
|
|
287
|
+
if (validation.valid) {
|
|
288
|
+
console.log(chalk.green(` ${type}: Valid`));
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
console.log(chalk.yellow(` ${type}: ${validation.errors.join(", ")}`));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// Upload if requested
|
|
295
|
+
if (options.upload && options.repo) {
|
|
296
|
+
console.log(chalk.blue("\nUploading to Husky API..."));
|
|
297
|
+
const api = getApiClient();
|
|
298
|
+
const diagrams = results
|
|
299
|
+
.filter((r) => r.valid)
|
|
300
|
+
.map((r) => ({
|
|
301
|
+
repoUrl: options.repo,
|
|
302
|
+
type: r.type,
|
|
303
|
+
title: r.title,
|
|
304
|
+
mermaidCode: r.mermaidCode,
|
|
305
|
+
gitCommitSha: options.commit,
|
|
306
|
+
generatedBy: "cli",
|
|
307
|
+
}));
|
|
308
|
+
if (diagrams.length > 0) {
|
|
309
|
+
const response = await api.post(`/api/diagrams/batch`, { diagrams });
|
|
310
|
+
console.log(chalk.green(`Uploaded ${response.created} diagrams`));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (options.json) {
|
|
314
|
+
console.log(JSON.stringify(results, null, 2));
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
console.log(chalk.bold(`\nGenerated ${results.length} diagrams`));
|
|
318
|
+
for (const result of results) {
|
|
319
|
+
console.log(` ${result.valid ? chalk.green("✓") : chalk.red("✗")} ${result.type}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : error);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
// Helper function to generate placeholder diagrams
|
|
329
|
+
function generatePlaceholderDiagram(type) {
|
|
330
|
+
switch (type) {
|
|
331
|
+
case "flowchart":
|
|
332
|
+
return `flowchart TD
|
|
333
|
+
A[Start] --> B{Check Input}
|
|
334
|
+
B -->|Valid| C[Process]
|
|
335
|
+
B -->|Invalid| D[Error Handler]
|
|
336
|
+
C --> E[Output]
|
|
337
|
+
D --> E
|
|
338
|
+
E --> F[End]`;
|
|
339
|
+
case "sequence":
|
|
340
|
+
return `sequenceDiagram
|
|
341
|
+
participant User
|
|
342
|
+
participant API
|
|
343
|
+
participant DB
|
|
344
|
+
User->>API: Request
|
|
345
|
+
API->>DB: Query
|
|
346
|
+
DB-->>API: Result
|
|
347
|
+
API-->>User: Response`;
|
|
348
|
+
case "class":
|
|
349
|
+
return `classDiagram
|
|
350
|
+
class Application {
|
|
351
|
+
+start()
|
|
352
|
+
+stop()
|
|
353
|
+
}
|
|
354
|
+
class Service {
|
|
355
|
+
+process()
|
|
356
|
+
}
|
|
357
|
+
Application --> Service`;
|
|
358
|
+
case "architecture":
|
|
359
|
+
return `flowchart TB
|
|
360
|
+
subgraph Frontend
|
|
361
|
+
A[Web App]
|
|
362
|
+
end
|
|
363
|
+
subgraph Backend
|
|
364
|
+
B[API Server]
|
|
365
|
+
C[Database]
|
|
366
|
+
end
|
|
367
|
+
A --> B
|
|
368
|
+
B --> C`;
|
|
369
|
+
default:
|
|
370
|
+
return `flowchart TD
|
|
371
|
+
A[Start] --> B[End]`;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
export default diagramsCommand;
|
package/dist/commands/project.js
CHANGED
|
@@ -261,6 +261,154 @@ projectCommand
|
|
|
261
261
|
process.exit(1);
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
|
+
projectCommand
|
|
265
|
+
.command("list-configs")
|
|
266
|
+
.description("List all project configurations")
|
|
267
|
+
.option("--json", "Output as JSON")
|
|
268
|
+
.action(async (options) => {
|
|
269
|
+
const config = ensureConfig();
|
|
270
|
+
try {
|
|
271
|
+
const res = await fetch(`${config.apiUrl}/api/project-configs`, {
|
|
272
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
273
|
+
});
|
|
274
|
+
if (!res.ok) {
|
|
275
|
+
throw new Error(`API error: ${res.status}`);
|
|
276
|
+
}
|
|
277
|
+
const configs = await res.json();
|
|
278
|
+
if (options.json) {
|
|
279
|
+
console.log(JSON.stringify(configs, null, 2));
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
printProjectConfigs(configs);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
console.error("Error fetching project configs:", error);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
projectCommand
|
|
291
|
+
.command("show-config <repo>")
|
|
292
|
+
.description("Show configuration for a repository (owner/repo)")
|
|
293
|
+
.option("--json", "Output as JSON")
|
|
294
|
+
.action(async (repo, options) => {
|
|
295
|
+
const config = ensureConfig();
|
|
296
|
+
try {
|
|
297
|
+
const url = new URL("/api/project-configs", config.apiUrl);
|
|
298
|
+
url.searchParams.set("repo", repo);
|
|
299
|
+
const res = await fetch(url.toString(), {
|
|
300
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
301
|
+
});
|
|
302
|
+
if (!res.ok) {
|
|
303
|
+
if (res.status === 404) {
|
|
304
|
+
console.error(`Error: No configuration found for repository ${repo}`);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.error(`Error: API returned ${res.status}`);
|
|
308
|
+
}
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
const projectConfig = await res.json();
|
|
312
|
+
if (options.json) {
|
|
313
|
+
console.log(JSON.stringify(projectConfig, null, 2));
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
printProjectConfigDetail(projectConfig);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error("Error fetching project config:", error);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
projectCommand
|
|
325
|
+
.command("enable-pr-agent <repo>")
|
|
326
|
+
.description("Enable PR Agent for a repository (owner/repo)")
|
|
327
|
+
.option("--json", "Output as JSON")
|
|
328
|
+
.action(async (repo, options) => {
|
|
329
|
+
const config = ensureConfig();
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetch(`${config.apiUrl}/api/project-configs`, {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: {
|
|
334
|
+
"Content-Type": "application/json",
|
|
335
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
repo,
|
|
339
|
+
prAgentEnabled: true,
|
|
340
|
+
}),
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
const errorData = await res.json().catch(() => ({}));
|
|
344
|
+
throw new Error(errorData.error || `API error: ${res.status}`);
|
|
345
|
+
}
|
|
346
|
+
const projectConfig = await res.json();
|
|
347
|
+
if (options.json) {
|
|
348
|
+
console.log(JSON.stringify(projectConfig, null, 2));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
console.log(`✓ PR Agent enabled for ${repo}`);
|
|
352
|
+
console.log(` Repository: ${projectConfig.repo}`);
|
|
353
|
+
console.log(` PR Agent: ${projectConfig.prAgentEnabled ? "Enabled" : "Disabled"}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error("Error enabling PR Agent:", error);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
projectCommand
|
|
362
|
+
.command("disable-pr-agent <repo>")
|
|
363
|
+
.description("Disable PR Agent for a repository (owner/repo)")
|
|
364
|
+
.option("--json", "Output as JSON")
|
|
365
|
+
.action(async (repo, options) => {
|
|
366
|
+
const config = ensureConfig();
|
|
367
|
+
try {
|
|
368
|
+
const url = new URL("/api/project-configs", config.apiUrl);
|
|
369
|
+
url.searchParams.set("repo", repo);
|
|
370
|
+
const getRes = await fetch(url.toString(), {
|
|
371
|
+
headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
|
|
372
|
+
});
|
|
373
|
+
if (!getRes.ok) {
|
|
374
|
+
if (getRes.status === 404) {
|
|
375
|
+
console.error(`Error: No configuration found for repository ${repo}`);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
console.error(`Error: API returned ${getRes.status}`);
|
|
379
|
+
}
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
const projectConfig = await getRes.json();
|
|
383
|
+
const updateRes = await fetch(`${config.apiUrl}/api/project-configs/${projectConfig.id}`, {
|
|
384
|
+
method: "PUT",
|
|
385
|
+
headers: {
|
|
386
|
+
"Content-Type": "application/json",
|
|
387
|
+
...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
|
|
388
|
+
},
|
|
389
|
+
body: JSON.stringify({
|
|
390
|
+
prAgentEnabled: false,
|
|
391
|
+
}),
|
|
392
|
+
});
|
|
393
|
+
if (!updateRes.ok) {
|
|
394
|
+
const errorData = await updateRes.json().catch(() => ({}));
|
|
395
|
+
throw new Error(errorData.error || `API error: ${updateRes.status}`);
|
|
396
|
+
}
|
|
397
|
+
const updatedConfig = await updateRes.json();
|
|
398
|
+
if (options.json) {
|
|
399
|
+
console.log(JSON.stringify(updatedConfig, null, 2));
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
console.log(`✓ PR Agent disabled for ${repo}`);
|
|
403
|
+
console.log(` Repository: ${updatedConfig.repo}`);
|
|
404
|
+
console.log(` PR Agent: ${updatedConfig.prAgentEnabled ? "Enabled" : "Disabled"}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
console.error("Error disabling PR Agent:", error);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
264
412
|
// ============================================
|
|
265
413
|
// KNOWLEDGE MANAGEMENT COMMANDS
|
|
266
414
|
// ============================================
|
|
@@ -459,7 +607,6 @@ function printKnowledgeList(projectId, knowledge) {
|
|
|
459
607
|
const truncatedTitle = entry.title.length > 33 ? entry.title.substring(0, 30) + "..." : entry.title;
|
|
460
608
|
console.log(` ${entry.id.padEnd(24)} [${categoryConfig.icon}] ${categoryConfig.label.padEnd(10)} ${truncatedTitle}`);
|
|
461
609
|
}
|
|
462
|
-
// Summary by category
|
|
463
610
|
const byCategory = {};
|
|
464
611
|
for (const entry of knowledge) {
|
|
465
612
|
byCategory[entry.category] = (byCategory[entry.category] || 0) + 1;
|
|
@@ -471,3 +618,32 @@ function printKnowledgeList(projectId, knowledge) {
|
|
|
471
618
|
.join(", ");
|
|
472
619
|
console.log(` By Category: ${categoryStr}\n`);
|
|
473
620
|
}
|
|
621
|
+
function printProjectConfigs(configs) {
|
|
622
|
+
if (configs.length === 0) {
|
|
623
|
+
console.log("\n No project configurations found.");
|
|
624
|
+
console.log(" Enable PR Agent with: husky project enable-pr-agent <owner/repo>\n");
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
console.log("\n PROJECT CONFIGURATIONS");
|
|
628
|
+
console.log(" " + "-".repeat(80));
|
|
629
|
+
console.log(` ${"REPOSITORY".padEnd(40)} ${"PR AGENT".padEnd(15)} ${"UPDATED".padEnd(20)}`);
|
|
630
|
+
console.log(" " + "-".repeat(80));
|
|
631
|
+
for (const config of configs) {
|
|
632
|
+
const status = config.prAgentEnabled ? "✓ Enabled" : "✗ Disabled";
|
|
633
|
+
const updated = new Date(config.updatedAt).toLocaleDateString();
|
|
634
|
+
const truncatedRepo = config.repo.length > 38 ? config.repo.substring(0, 35) + "..." : config.repo;
|
|
635
|
+
console.log(` ${truncatedRepo.padEnd(40)} ${status.padEnd(15)} ${updated}`);
|
|
636
|
+
}
|
|
637
|
+
console.log(" " + "-".repeat(80));
|
|
638
|
+
console.log(` Total: ${configs.length} configuration(s)\n`);
|
|
639
|
+
}
|
|
640
|
+
function printProjectConfigDetail(config) {
|
|
641
|
+
console.log(`\n Project Configuration: ${config.repo}`);
|
|
642
|
+
console.log(" " + "=".repeat(60));
|
|
643
|
+
console.log(` ID: ${config.id}`);
|
|
644
|
+
console.log(` Repository: ${config.repo}`);
|
|
645
|
+
console.log(` PR Agent: ${config.prAgentEnabled ? "✓ Enabled" : "✗ Disabled"}`);
|
|
646
|
+
console.log(` Created: ${new Date(config.createdAt).toLocaleString()}`);
|
|
647
|
+
console.log(` Updated: ${new Date(config.updatedAt).toLocaleString()}`);
|
|
648
|
+
console.log("");
|
|
649
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ import { imageCommand } from "./commands/image.js";
|
|
|
37
37
|
import { authCommand } from "./commands/auth.js";
|
|
38
38
|
import { businessCommand } from "./commands/business.js";
|
|
39
39
|
import { planCommand } from "./commands/plan.js";
|
|
40
|
+
import { diagramsCommand } from "./commands/diagrams.js";
|
|
40
41
|
// Read version from package.json
|
|
41
42
|
const require = createRequire(import.meta.url);
|
|
42
43
|
const packageJson = require("../package.json");
|
|
@@ -81,6 +82,7 @@ program.addCommand(imageCommand);
|
|
|
81
82
|
program.addCommand(authCommand);
|
|
82
83
|
program.addCommand(businessCommand);
|
|
83
84
|
program.addCommand(planCommand);
|
|
85
|
+
program.addCommand(diagramsCommand);
|
|
84
86
|
// Handle --llm flag specially
|
|
85
87
|
if (process.argv.includes("--llm")) {
|
|
86
88
|
printLLMContext();
|