@simonfestl/husky-cli 0.3.0 → 0.5.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.
- package/README.md +228 -58
- package/dist/commands/changelog.d.ts +2 -0
- package/dist/commands/changelog.js +401 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +400 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +101 -1
- package/dist/commands/department.d.ts +2 -0
- package/dist/commands/department.js +240 -0
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +411 -0
- package/dist/commands/idea.d.ts +2 -0
- package/dist/commands/idea.js +340 -0
- package/dist/commands/interactive.d.ts +1 -0
- package/dist/commands/interactive.js +1397 -0
- package/dist/commands/jules.d.ts +2 -0
- package/dist/commands/jules.js +593 -0
- package/dist/commands/process.d.ts +2 -0
- package/dist/commands/process.js +289 -0
- package/dist/commands/project.d.ts +2 -0
- package/dist/commands/project.js +473 -0
- package/dist/commands/roadmap.js +318 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +153 -0
- package/dist/commands/strategy.d.ts +2 -0
- package/dist/commands/strategy.js +706 -0
- package/dist/commands/task.js +244 -1
- package/dist/commands/vm-config.d.ts +2 -0
- package/dist/commands/vm-config.js +318 -0
- package/dist/commands/vm.d.ts +2 -0
- package/dist/commands/vm.js +621 -0
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +545 -0
- package/dist/index.js +35 -2
- package/package.json +8 -2
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export const completionCommand = new Command("completion")
|
|
3
|
+
.description("Generate shell completion scripts");
|
|
4
|
+
// Command definitions for completion
|
|
5
|
+
const COMMANDS = {
|
|
6
|
+
task: [
|
|
7
|
+
"list", "create", "get", "update", "delete", "start", "done", "status", "plan",
|
|
8
|
+
"wait-approval", "complete", "qa-start", "qa-status", "qa-approve", "qa-reject",
|
|
9
|
+
"qa-iteration", "qa-escalate", "merge-conflict"
|
|
10
|
+
],
|
|
11
|
+
project: ["list", "create", "get", "update", "delete", "add-knowledge", "list-knowledge", "delete-knowledge"],
|
|
12
|
+
roadmap: [
|
|
13
|
+
"list", "create", "get", "update", "delete", "add-phase", "add-feature", "generate",
|
|
14
|
+
"list-features", "update-feature", "delete-feature", "convert-feature"
|
|
15
|
+
],
|
|
16
|
+
workflow: [
|
|
17
|
+
"list", "create", "get", "update", "delete", "add-step", "update-step", "delete-step",
|
|
18
|
+
"generate-steps", "list-steps"
|
|
19
|
+
],
|
|
20
|
+
idea: ["list", "create", "get", "update", "delete", "convert"],
|
|
21
|
+
department: ["list", "create", "get", "update", "delete"],
|
|
22
|
+
vm: ["list", "create", "get", "update", "delete", "start", "stop", "logs", "approve", "reject"],
|
|
23
|
+
jules: ["list", "create", "get", "update", "delete", "message", "approve", "activities", "sources"],
|
|
24
|
+
process: ["list", "create", "get", "update", "delete"],
|
|
25
|
+
strategy: [
|
|
26
|
+
"show", "set-vision", "set-mission", "add-value", "update-value", "delete-value",
|
|
27
|
+
"add-goal", "update-goal", "delete-goal", "add-persona", "update-persona", "delete-persona"
|
|
28
|
+
],
|
|
29
|
+
settings: ["get", "set"],
|
|
30
|
+
"vm-config": ["list", "create", "get", "update", "delete"],
|
|
31
|
+
config: ["set", "get", "list", "test"],
|
|
32
|
+
changelog: ["generate", "list", "show", "publish", "delete"],
|
|
33
|
+
explain: [],
|
|
34
|
+
completion: ["bash", "zsh", "fish", "install"],
|
|
35
|
+
agent: [],
|
|
36
|
+
};
|
|
37
|
+
const MAIN_COMMANDS = Object.keys(COMMANDS).join(" ");
|
|
38
|
+
// Common options across commands
|
|
39
|
+
const COMMON_OPTIONS = "--json --force --status";
|
|
40
|
+
// husky completion bash
|
|
41
|
+
completionCommand
|
|
42
|
+
.command("bash")
|
|
43
|
+
.description("Output bash completion script")
|
|
44
|
+
.action(() => {
|
|
45
|
+
console.log(generateBashCompletion());
|
|
46
|
+
});
|
|
47
|
+
// husky completion zsh
|
|
48
|
+
completionCommand
|
|
49
|
+
.command("zsh")
|
|
50
|
+
.description("Output zsh completion script")
|
|
51
|
+
.action(() => {
|
|
52
|
+
console.log(generateZshCompletion());
|
|
53
|
+
});
|
|
54
|
+
// husky completion fish
|
|
55
|
+
completionCommand
|
|
56
|
+
.command("fish")
|
|
57
|
+
.description("Output fish completion script")
|
|
58
|
+
.action(() => {
|
|
59
|
+
console.log(generateFishCompletion());
|
|
60
|
+
});
|
|
61
|
+
// husky completion install
|
|
62
|
+
completionCommand
|
|
63
|
+
.command("install")
|
|
64
|
+
.description("Print installation instructions for shell completions")
|
|
65
|
+
.action(() => {
|
|
66
|
+
console.log(`
|
|
67
|
+
SHELL COMPLETION INSTALLATION
|
|
68
|
+
|
|
69
|
+
Bash:
|
|
70
|
+
Add to your ~/.bashrc:
|
|
71
|
+
eval "$(husky completion bash)"
|
|
72
|
+
|
|
73
|
+
Or append to file:
|
|
74
|
+
husky completion bash >> ~/.bashrc
|
|
75
|
+
source ~/.bashrc
|
|
76
|
+
|
|
77
|
+
Zsh:
|
|
78
|
+
Add to your ~/.zshrc:
|
|
79
|
+
eval "$(husky completion zsh)"
|
|
80
|
+
|
|
81
|
+
Or append to file:
|
|
82
|
+
husky completion zsh >> ~/.zshrc
|
|
83
|
+
source ~/.zshrc
|
|
84
|
+
|
|
85
|
+
Fish:
|
|
86
|
+
Save to fish completions directory:
|
|
87
|
+
husky completion fish > ~/.config/fish/completions/husky.fish
|
|
88
|
+
|
|
89
|
+
Or create if directory doesn't exist:
|
|
90
|
+
mkdir -p ~/.config/fish/completions
|
|
91
|
+
husky completion fish > ~/.config/fish/completions/husky.fish
|
|
92
|
+
|
|
93
|
+
After installation, restart your shell or source the config file.
|
|
94
|
+
`);
|
|
95
|
+
});
|
|
96
|
+
function generateBashCompletion() {
|
|
97
|
+
const subcommandCases = Object.entries(COMMANDS)
|
|
98
|
+
.filter(([, subs]) => subs.length > 0)
|
|
99
|
+
.map(([cmd, subs]) => ` ${cmd})
|
|
100
|
+
COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- \${cur}) )
|
|
101
|
+
return 0
|
|
102
|
+
;;`)
|
|
103
|
+
.join("\n");
|
|
104
|
+
return `# Bash completion for husky CLI
|
|
105
|
+
# Generated by husky completion bash
|
|
106
|
+
|
|
107
|
+
_husky_completions() {
|
|
108
|
+
local cur prev words cword
|
|
109
|
+
COMPREPLY=()
|
|
110
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
111
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
112
|
+
|
|
113
|
+
# Main commands
|
|
114
|
+
local commands="${MAIN_COMMANDS}"
|
|
115
|
+
|
|
116
|
+
# Common options
|
|
117
|
+
local common_opts="${COMMON_OPTIONS}"
|
|
118
|
+
|
|
119
|
+
# Handle completion based on previous word
|
|
120
|
+
case "\${prev}" in
|
|
121
|
+
husky)
|
|
122
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
|
|
123
|
+
return 0
|
|
124
|
+
;;
|
|
125
|
+
${subcommandCases}
|
|
126
|
+
--status)
|
|
127
|
+
COMPREPLY=( $(compgen -W "backlog in_progress review done pending running completed failed" -- \${cur}) )
|
|
128
|
+
return 0
|
|
129
|
+
;;
|
|
130
|
+
--priority)
|
|
131
|
+
COMPREPLY=( $(compgen -W "low medium high urgent must should could wont" -- \${cur}) )
|
|
132
|
+
return 0
|
|
133
|
+
;;
|
|
134
|
+
--assignee)
|
|
135
|
+
COMPREPLY=( $(compgen -W "human llm unassigned" -- \${cur}) )
|
|
136
|
+
return 0
|
|
137
|
+
;;
|
|
138
|
+
--agent)
|
|
139
|
+
COMPREPLY=( $(compgen -W "claude-code gemini-cli aider custom" -- \${cur}) )
|
|
140
|
+
return 0
|
|
141
|
+
;;
|
|
142
|
+
--type)
|
|
143
|
+
COMPREPLY=( $(compgen -W "global project architecture patterns decisions learnings" -- \${cur}) )
|
|
144
|
+
return 0
|
|
145
|
+
;;
|
|
146
|
+
--value-stream)
|
|
147
|
+
COMPREPLY=( $(compgen -W "order_to_delivery procure_to_pay returns_management product_lifecycle customer_service marketing_sales finance_accounting hr_operations it_operations general" -- \${cur}) )
|
|
148
|
+
return 0
|
|
149
|
+
;;
|
|
150
|
+
--action)
|
|
151
|
+
COMPREPLY=( $(compgen -W "manual semi_automated fully_automated" -- \${cur}) )
|
|
152
|
+
return 0
|
|
153
|
+
;;
|
|
154
|
+
--work-status)
|
|
155
|
+
COMPREPLY=( $(compgen -W "planning in_progress review completed on_hold" -- \${cur}) )
|
|
156
|
+
return 0
|
|
157
|
+
;;
|
|
158
|
+
esac
|
|
159
|
+
|
|
160
|
+
# If current word starts with -, complete options
|
|
161
|
+
if [[ \${cur} == -* ]]; then
|
|
162
|
+
COMPREPLY=( $(compgen -W "\${common_opts} --id --project --description --name --help" -- \${cur}) )
|
|
163
|
+
return 0
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
# Default: complete with main commands
|
|
167
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
168
|
+
COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
return 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
complete -F _husky_completions husky
|
|
175
|
+
`;
|
|
176
|
+
}
|
|
177
|
+
function generateZshCompletion() {
|
|
178
|
+
const subcommandCases = Object.entries(COMMANDS)
|
|
179
|
+
.filter(([, subs]) => subs.length > 0)
|
|
180
|
+
.map(([cmd, subs]) => ` ${cmd})
|
|
181
|
+
_values 'subcommand' ${subs.map(s => `'${s}'`).join(" ")}
|
|
182
|
+
;;`)
|
|
183
|
+
.join("\n");
|
|
184
|
+
return `#compdef husky
|
|
185
|
+
# Zsh completion for husky CLI
|
|
186
|
+
# Generated by husky completion zsh
|
|
187
|
+
|
|
188
|
+
_husky() {
|
|
189
|
+
local -a commands
|
|
190
|
+
commands=(
|
|
191
|
+
'task:Manage tasks'
|
|
192
|
+
'project:Manage projects and knowledge'
|
|
193
|
+
'roadmap:Manage roadmaps'
|
|
194
|
+
'workflow:Manage workflows and workflow steps'
|
|
195
|
+
'idea:Manage ideas'
|
|
196
|
+
'department:Manage departments'
|
|
197
|
+
'vm:Manage VM sessions'
|
|
198
|
+
'jules:Manage Jules AI coding sessions'
|
|
199
|
+
'process:Manage processes'
|
|
200
|
+
'strategy:Manage business strategy'
|
|
201
|
+
'settings:Manage application settings'
|
|
202
|
+
'vm-config:Manage VM configurations'
|
|
203
|
+
'config:Manage CLI configuration'
|
|
204
|
+
'changelog:Generate and manage changelogs'
|
|
205
|
+
'explain:Explain CLI commands for AI agents'
|
|
206
|
+
'completion:Generate shell completion scripts'
|
|
207
|
+
'agent:Interactive agent mode'
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
_arguments -C \\
|
|
211
|
+
'1: :->command' \\
|
|
212
|
+
'*: :->args'
|
|
213
|
+
|
|
214
|
+
case $state in
|
|
215
|
+
command)
|
|
216
|
+
_describe -t commands 'husky commands' commands
|
|
217
|
+
;;
|
|
218
|
+
args)
|
|
219
|
+
case $words[2] in
|
|
220
|
+
${subcommandCases}
|
|
221
|
+
esac
|
|
222
|
+
;;
|
|
223
|
+
esac
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# Common options completion
|
|
227
|
+
_husky_common_options() {
|
|
228
|
+
_arguments \\
|
|
229
|
+
'--json[Output as JSON]' \\
|
|
230
|
+
'--force[Skip confirmation]' \\
|
|
231
|
+
'--status[Filter by status]:status:(backlog in_progress review done pending running completed failed)' \\
|
|
232
|
+
'--priority[Priority level]:priority:(low medium high urgent must should could wont)' \\
|
|
233
|
+
'--id[Task/Resource ID]:id:' \\
|
|
234
|
+
'--project[Project ID]:project:' \\
|
|
235
|
+
'--help[Show help]'
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
compdef _husky husky
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function generateFishCompletion() {
|
|
242
|
+
const mainCommandCompletions = Object.entries(COMMANDS)
|
|
243
|
+
.map(([cmd, _]) => {
|
|
244
|
+
const desc = getCommandDescription(cmd);
|
|
245
|
+
return `complete -c husky -n "__fish_use_subcommand" -a "${cmd}" -d "${desc}"`;
|
|
246
|
+
})
|
|
247
|
+
.join("\n");
|
|
248
|
+
const subcommandCompletions = Object.entries(COMMANDS)
|
|
249
|
+
.filter(([, subs]) => subs.length > 0)
|
|
250
|
+
.flatMap(([cmd, subs]) => subs.map(sub => `complete -c husky -n "__fish_seen_subcommand_from ${cmd}" -a "${sub}" -d "${getSubcommandDescription(cmd, sub)}"`))
|
|
251
|
+
.join("\n");
|
|
252
|
+
return `# Fish completion for husky CLI
|
|
253
|
+
# Generated by husky completion fish
|
|
254
|
+
|
|
255
|
+
# Disable file completion by default
|
|
256
|
+
complete -c husky -f
|
|
257
|
+
|
|
258
|
+
# Main commands
|
|
259
|
+
${mainCommandCompletions}
|
|
260
|
+
|
|
261
|
+
# Subcommands
|
|
262
|
+
${subcommandCompletions}
|
|
263
|
+
|
|
264
|
+
# Common options
|
|
265
|
+
complete -c husky -l json -d "Output as JSON"
|
|
266
|
+
complete -c husky -l force -d "Skip confirmation"
|
|
267
|
+
complete -c husky -l help -d "Show help"
|
|
268
|
+
complete -c husky -l id -d "Task/Resource ID" -r
|
|
269
|
+
complete -c husky -l project -d "Project ID" -r
|
|
270
|
+
complete -c husky -l status -d "Filter by status" -r -a "backlog in_progress review done pending running completed failed"
|
|
271
|
+
complete -c husky -l priority -d "Priority level" -r -a "low medium high urgent must should could wont"
|
|
272
|
+
complete -c husky -l assignee -d "Assignee type" -r -a "human llm unassigned"
|
|
273
|
+
complete -c husky -l agent -d "Agent type" -r -a "claude-code gemini-cli aider custom"
|
|
274
|
+
complete -c husky -l type -d "Type filter" -r -a "global project architecture patterns decisions learnings"
|
|
275
|
+
complete -c husky -l value-stream -d "Value stream" -r -a "order_to_delivery procure_to_pay returns_management product_lifecycle customer_service marketing_sales finance_accounting hr_operations it_operations general"
|
|
276
|
+
complete -c husky -l action -d "Action type" -r -a "manual semi_automated fully_automated"
|
|
277
|
+
complete -c husky -l work-status -d "Work status" -r -a "planning in_progress review completed on_hold"
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
function getCommandDescription(cmd) {
|
|
281
|
+
const descriptions = {
|
|
282
|
+
task: "Manage tasks",
|
|
283
|
+
project: "Manage projects and knowledge",
|
|
284
|
+
roadmap: "Manage roadmaps",
|
|
285
|
+
workflow: "Manage workflows and workflow steps",
|
|
286
|
+
idea: "Manage ideas",
|
|
287
|
+
department: "Manage departments",
|
|
288
|
+
vm: "Manage VM sessions",
|
|
289
|
+
jules: "Manage Jules AI coding sessions",
|
|
290
|
+
process: "Manage processes",
|
|
291
|
+
strategy: "Manage business strategy",
|
|
292
|
+
settings: "Manage application settings",
|
|
293
|
+
"vm-config": "Manage VM configurations",
|
|
294
|
+
config: "Manage CLI configuration",
|
|
295
|
+
changelog: "Generate and manage changelogs",
|
|
296
|
+
explain: "Explain CLI commands for AI agents",
|
|
297
|
+
completion: "Generate shell completion scripts",
|
|
298
|
+
agent: "Interactive agent mode",
|
|
299
|
+
};
|
|
300
|
+
return descriptions[cmd] || cmd;
|
|
301
|
+
}
|
|
302
|
+
function getSubcommandDescription(cmd, sub) {
|
|
303
|
+
// Common subcommand descriptions
|
|
304
|
+
const common = {
|
|
305
|
+
list: "List all",
|
|
306
|
+
create: "Create new",
|
|
307
|
+
get: "Get details",
|
|
308
|
+
update: "Update",
|
|
309
|
+
delete: "Delete",
|
|
310
|
+
start: "Start",
|
|
311
|
+
stop: "Stop",
|
|
312
|
+
show: "Show",
|
|
313
|
+
};
|
|
314
|
+
// Command-specific descriptions
|
|
315
|
+
const specific = {
|
|
316
|
+
task: {
|
|
317
|
+
done: "Mark task as done",
|
|
318
|
+
status: "Report task progress status",
|
|
319
|
+
plan: "Submit execution plan for approval",
|
|
320
|
+
"wait-approval": "Wait for plan approval",
|
|
321
|
+
complete: "Mark task as complete with result",
|
|
322
|
+
"qa-start": "Start QA validation",
|
|
323
|
+
"qa-status": "Get QA validation status",
|
|
324
|
+
"qa-approve": "Manually approve QA",
|
|
325
|
+
"qa-reject": "Manually reject QA",
|
|
326
|
+
"qa-iteration": "Add a QA iteration result",
|
|
327
|
+
"qa-escalate": "Escalate QA to human review",
|
|
328
|
+
"merge-conflict": "Resolve a Git merge conflict using AI",
|
|
329
|
+
},
|
|
330
|
+
project: {
|
|
331
|
+
"add-knowledge": "Add a knowledge entry",
|
|
332
|
+
"list-knowledge": "List knowledge entries",
|
|
333
|
+
"delete-knowledge": "Delete a knowledge entry",
|
|
334
|
+
},
|
|
335
|
+
roadmap: {
|
|
336
|
+
"add-phase": "Add a phase to a roadmap",
|
|
337
|
+
"add-feature": "Add a feature to a roadmap phase",
|
|
338
|
+
generate: "Generate roadmap with AI",
|
|
339
|
+
"list-features": "List all features in a roadmap",
|
|
340
|
+
"update-feature": "Update a roadmap feature",
|
|
341
|
+
"delete-feature": "Delete a feature from a roadmap",
|
|
342
|
+
"convert-feature": "Convert a roadmap feature to a task",
|
|
343
|
+
},
|
|
344
|
+
workflow: {
|
|
345
|
+
"add-step": "Add a step to a workflow",
|
|
346
|
+
"update-step": "Update a workflow step",
|
|
347
|
+
"delete-step": "Delete a workflow step",
|
|
348
|
+
"generate-steps": "AI-generate steps from an SOP document",
|
|
349
|
+
"list-steps": "List all steps in a workflow",
|
|
350
|
+
},
|
|
351
|
+
idea: {
|
|
352
|
+
convert: "Convert an idea to a task",
|
|
353
|
+
},
|
|
354
|
+
vm: {
|
|
355
|
+
logs: "Get VM logs",
|
|
356
|
+
approve: "Approve VM session plan",
|
|
357
|
+
reject: "Reject VM session plan",
|
|
358
|
+
},
|
|
359
|
+
jules: {
|
|
360
|
+
message: "Send a message to a Jules session",
|
|
361
|
+
approve: "Approve a Jules session plan",
|
|
362
|
+
activities: "Get session activities",
|
|
363
|
+
sources: "List available Jules sources",
|
|
364
|
+
},
|
|
365
|
+
strategy: {
|
|
366
|
+
"set-vision": "Set/update the company vision",
|
|
367
|
+
"set-mission": "Set/update the company mission",
|
|
368
|
+
"add-value": "Add a company value",
|
|
369
|
+
"update-value": "Update a company value",
|
|
370
|
+
"delete-value": "Delete a company value",
|
|
371
|
+
"add-goal": "Add a strategic goal",
|
|
372
|
+
"update-goal": "Update a strategic goal",
|
|
373
|
+
"delete-goal": "Delete a strategic goal",
|
|
374
|
+
"add-persona": "Add a target audience persona",
|
|
375
|
+
"update-persona": "Update a target audience persona",
|
|
376
|
+
"delete-persona": "Delete a target audience persona",
|
|
377
|
+
},
|
|
378
|
+
config: {
|
|
379
|
+
set: "Set a configuration value",
|
|
380
|
+
test: "Test API connection",
|
|
381
|
+
},
|
|
382
|
+
changelog: {
|
|
383
|
+
generate: "Generate a changelog from git commits",
|
|
384
|
+
publish: "Publish a changelog",
|
|
385
|
+
},
|
|
386
|
+
completion: {
|
|
387
|
+
bash: "Output bash completion script",
|
|
388
|
+
zsh: "Output zsh completion script",
|
|
389
|
+
fish: "Output fish completion script",
|
|
390
|
+
install: "Print installation instructions",
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
if (specific[cmd]?.[sub]) {
|
|
394
|
+
return specific[cmd][sub];
|
|
395
|
+
}
|
|
396
|
+
if (common[sub]) {
|
|
397
|
+
return `${common[sub]} ${cmd}`;
|
|
398
|
+
}
|
|
399
|
+
return sub;
|
|
400
|
+
}
|
package/dist/commands/config.js
CHANGED
|
@@ -4,6 +4,29 @@ import { join } from "path";
|
|
|
4
4
|
import { homedir } from "os";
|
|
5
5
|
const CONFIG_DIR = join(homedir(), ".husky");
|
|
6
6
|
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
// API Key validation - must be at least 16 characters and alphanumeric with dashes/underscores
|
|
8
|
+
function validateApiKey(key) {
|
|
9
|
+
if (key.length < 16) {
|
|
10
|
+
return { valid: false, error: "API key must be at least 16 characters long" };
|
|
11
|
+
}
|
|
12
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(key)) {
|
|
13
|
+
return { valid: false, error: "API key must only contain letters, numbers, dashes, and underscores" };
|
|
14
|
+
}
|
|
15
|
+
return { valid: true };
|
|
16
|
+
}
|
|
17
|
+
// URL validation
|
|
18
|
+
function validateApiUrl(url) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
22
|
+
return { valid: false, error: "API URL must use http or https protocol" };
|
|
23
|
+
}
|
|
24
|
+
return { valid: true };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { valid: false, error: "Invalid URL format" };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
7
30
|
export function getConfig() {
|
|
8
31
|
try {
|
|
9
32
|
if (!existsSync(CONFIG_FILE)) {
|
|
@@ -22,6 +45,12 @@ function saveConfig(config) {
|
|
|
22
45
|
}
|
|
23
46
|
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
24
47
|
}
|
|
48
|
+
// Helper to set a single config value (used by interactive mode)
|
|
49
|
+
export function setConfig(key, value) {
|
|
50
|
+
const config = getConfig();
|
|
51
|
+
config[key] = value;
|
|
52
|
+
saveConfig(config);
|
|
53
|
+
}
|
|
25
54
|
export const configCommand = new Command("config")
|
|
26
55
|
.description("Manage CLI configuration");
|
|
27
56
|
// husky config set <key> <value>
|
|
@@ -31,9 +60,19 @@ configCommand
|
|
|
31
60
|
.action((key, value) => {
|
|
32
61
|
const config = getConfig();
|
|
33
62
|
if (key === "api-url") {
|
|
63
|
+
const validation = validateApiUrl(value);
|
|
64
|
+
if (!validation.valid) {
|
|
65
|
+
console.error(`Error: ${validation.error}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
34
68
|
config.apiUrl = value;
|
|
35
69
|
}
|
|
36
70
|
else if (key === "api-key") {
|
|
71
|
+
const validation = validateApiKey(value);
|
|
72
|
+
if (!validation.valid) {
|
|
73
|
+
console.error(`Error: ${validation.error}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
37
76
|
config.apiKey = value;
|
|
38
77
|
}
|
|
39
78
|
else {
|
|
@@ -42,7 +81,7 @@ configCommand
|
|
|
42
81
|
process.exit(1);
|
|
43
82
|
}
|
|
44
83
|
saveConfig(config);
|
|
45
|
-
console.log(
|
|
84
|
+
console.log(`Set ${key} = ${key === "api-key" ? "***" : value}`);
|
|
46
85
|
});
|
|
47
86
|
// husky config get <key>
|
|
48
87
|
configCommand
|
|
@@ -71,3 +110,64 @@ configCommand
|
|
|
71
110
|
console.log(` api-url: ${config.apiUrl || "(not set)"}`);
|
|
72
111
|
console.log(` api-key: ${config.apiKey ? "***" : "(not set)"}`);
|
|
73
112
|
});
|
|
113
|
+
// husky config test
|
|
114
|
+
configCommand
|
|
115
|
+
.command("test")
|
|
116
|
+
.description("Test API connection with configured credentials")
|
|
117
|
+
.action(async () => {
|
|
118
|
+
const config = getConfig();
|
|
119
|
+
// Check if configuration is complete
|
|
120
|
+
if (!config.apiUrl) {
|
|
121
|
+
console.error("Error: API URL not configured. Run: husky config set api-url <url>");
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
if (!config.apiKey) {
|
|
125
|
+
console.error("Error: API key not configured. Run: husky config set api-key <key>");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
console.log("Testing API connection...");
|
|
129
|
+
try {
|
|
130
|
+
const url = new URL("/api/tasks", config.apiUrl);
|
|
131
|
+
const res = await fetch(url.toString(), {
|
|
132
|
+
headers: {
|
|
133
|
+
"x-api-key": config.apiKey,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
if (res.ok) {
|
|
137
|
+
console.log(`API connection successful (API URL: ${config.apiUrl})`);
|
|
138
|
+
}
|
|
139
|
+
else if (res.status === 401) {
|
|
140
|
+
console.error(`API connection failed: Unauthorized (HTTP 401)`);
|
|
141
|
+
console.error(" Check your API key with: husky config set api-key <key>");
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
else if (res.status === 403) {
|
|
145
|
+
console.error(`API connection failed: Forbidden (HTTP 403)`);
|
|
146
|
+
console.error(" Your API key may not have the required permissions");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.error(`API connection failed: HTTP ${res.status}`);
|
|
151
|
+
try {
|
|
152
|
+
const body = await res.json();
|
|
153
|
+
if (body.error) {
|
|
154
|
+
console.error(` Error: ${body.error}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Ignore JSON parse errors
|
|
159
|
+
}
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
165
|
+
console.error(`API connection failed: Could not connect to ${config.apiUrl}`);
|
|
166
|
+
console.error(" Check your API URL with: husky config set api-url <url>");
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
console.error(`API connection failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
170
|
+
}
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
});
|