@simonfestl/husky-cli 0.9.3 → 0.9.6

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.
@@ -4,3 +4,5 @@
4
4
  */
5
5
  export declare function generateLLMContext(): string;
6
6
  export declare function printLLMContext(): void;
7
+ import { Command } from "commander";
8
+ export declare const llmCommand: Command;
@@ -2,7 +2,7 @@
2
2
  * LLM Context Generator
3
3
  * Outputs markdown reference for LLM agents
4
4
  */
5
- const VERSION = "0.9.3";
5
+ const VERSION = "0.9.6";
6
6
  export function generateLLMContext() {
7
7
  return `# Husky CLI Reference (v${VERSION})
8
8
 
@@ -57,6 +57,8 @@ husky task create # Create new task
57
57
  husky task start <id> # Start working on task
58
58
  husky task done <id> [--pr <url>] # Mark task as done
59
59
  husky task update <id> # Update task fields
60
+ husky task assign <id> <assignee> # Assign task
61
+ husky task log <id> # View task activity log
60
62
  \`\`\`
61
63
 
62
64
  ### Projects
@@ -64,13 +66,106 @@ husky task update <id> # Update task fields
64
66
  husky project list # List projects
65
67
  husky project get <id> # Get project details
66
68
  husky project create # Create new project
69
+ husky project tasks <id> # List project tasks
67
70
  \`\`\`
68
71
 
69
- ### Worktrees (Git)
72
+ ### Roadmaps
73
+ \`\`\`bash
74
+ husky roadmap list # List roadmaps
75
+ husky roadmap get <id> # Get roadmap details
76
+ husky roadmap create # Create new roadmap
77
+ husky roadmap milestones <id> # List milestones
78
+ husky roadmap add-milestone <id> # Add milestone to roadmap
79
+ \`\`\`
80
+
81
+ ### Ideas
82
+ \`\`\`bash
83
+ husky idea list # List ideas
84
+ husky idea get <id> # Get idea details
85
+ husky idea create # Create new idea
86
+ husky idea vote <id> # Vote on idea
87
+ \`\`\`
88
+
89
+ ### Workflows
90
+ \`\`\`bash
91
+ husky workflow list # List workflows
92
+ husky workflow get <id> # Get workflow details
93
+ husky workflow create # Create new workflow
94
+ husky workflow run <id> # Execute workflow
95
+ \`\`\`
96
+
97
+ ### Departments
98
+ \`\`\`bash
99
+ husky department list # List departments
100
+ husky department get <id> # Get department details
101
+ husky department members <id> # List department members
102
+ \`\`\`
103
+
104
+ ### Processes
105
+ \`\`\`bash
106
+ husky process list # List processes
107
+ husky process get <id> # Get process details
108
+ husky process run <id> # Run process
109
+ \`\`\`
110
+
111
+ ### Strategy
112
+ \`\`\`bash
113
+ husky strategy list # List strategy items
114
+ husky strategy get <id> # Get strategy details
115
+ husky strategy update <id> # Update strategy
116
+ \`\`\`
117
+
118
+ ### Changelog
119
+ \`\`\`bash
120
+ husky changelog generate # Generate changelog from commits
121
+ husky changelog list # List changelogs
122
+ \`\`\`
123
+
124
+ ### Worktrees (Git Isolation)
70
125
  \`\`\`bash
71
126
  husky worktree list # List worktrees
72
127
  husky worktree create <name> # Create isolated worktree
73
- husky worktree delete <name> # Delete worktree
128
+ husky worktree create <name> --task-id <id> # Create and register with task
129
+ husky worktree merge <name> # Merge back to base branch
130
+ husky worktree push <name> # Push branch to remote
131
+ husky worktree pr <name> -t "Title" # Create pull request
132
+ husky worktree remove <name> # Remove worktree
133
+ husky worktree sync-stats <name> --task-id <id> # Sync stats to dashboard
134
+ \`\`\`
135
+
136
+ ### VM Sessions (Cloud Agents)
137
+ \`\`\`bash
138
+ husky vm list # List VM sessions
139
+ husky vm create # Create new VM
140
+ husky vm status <id> # Get VM status
141
+ husky vm ssh <id> # SSH into VM
142
+ husky vm delete <id> # Delete VM
143
+ \`\`\`
144
+
145
+ ### Jules Sessions (AI Agent)
146
+ \`\`\`bash
147
+ husky jules list # List Jules sessions
148
+ husky jules create # Create new session
149
+ husky jules status <id> # Get session status
150
+ husky jules logs <id> # View session logs
151
+ \`\`\`
152
+
153
+ ### Workers (Multi-Agent Coordination)
154
+ \`\`\`bash
155
+ husky worker whoami # Show current worker identity
156
+ husky worker register # Register worker with API
157
+ husky worker list # List all workers
158
+ husky worker sessions # List active sessions
159
+ husky worker activity # Who is working on what
160
+ \`\`\`
161
+
162
+ ### Utility Commands
163
+ \`\`\`bash
164
+ husky explain <command> # Explain CLI commands
165
+ husky settings list # List dashboard settings
166
+ husky settings set <key> <value> # Update setting
167
+ husky completion bash|zsh|fish # Generate shell completion
168
+ husky llm # Output LLM reference docs
74
169
  \`\`\`
75
170
 
76
171
  ---
@@ -157,3 +252,10 @@ husky biz qdrant search <coll> "<q>" # Semantic search
157
252
  export function printLLMContext() {
158
253
  console.log(generateLLMContext());
159
254
  }
255
+ // Command export for subcommand registration
256
+ import { Command } from "commander";
257
+ export const llmCommand = new Command("llm")
258
+ .description("Output LLM reference documentation (markdown)")
259
+ .action(() => {
260
+ printLLMContext();
261
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const serviceAccountCommand: Command;
@@ -0,0 +1,180 @@
1
+ import { Command } from "commander";
2
+ const API_URL = process.env.HUSKY_API_URL || "https://huskyv0-dashboard-474966775596.europe-west1.run.app";
3
+ const API_KEY = process.env.HUSKY_API_KEY || "";
4
+ async function fetchApi(path, options = {}) {
5
+ const response = await fetch(`${API_URL}${path}`, {
6
+ ...options,
7
+ headers: {
8
+ "Content-Type": "application/json",
9
+ "x-api-key": API_KEY,
10
+ ...options.headers,
11
+ },
12
+ });
13
+ if (!response.ok) {
14
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
15
+ throw new Error(error.error || `HTTP ${response.status}`);
16
+ }
17
+ return response.json();
18
+ }
19
+ function formatPersona(personaType) {
20
+ const icons = {
21
+ "support-agent": "šŸŽ§",
22
+ "research-agent": "šŸ”¬",
23
+ "accounting-agent": "šŸ“Š",
24
+ "marketing-agent": "šŸ“¢",
25
+ "developer-agent": "šŸ’»",
26
+ "devops-agent": "šŸ”§",
27
+ };
28
+ return `${icons[personaType] || "šŸ‘¤"} ${personaType}`;
29
+ }
30
+ function formatPermissionLevel(level) {
31
+ const colors = {
32
+ "read-only": "\x1b[90m", // gray
33
+ "developer": "\x1b[34m", // blue
34
+ "admin": "\x1b[31m", // red
35
+ };
36
+ const reset = "\x1b[0m";
37
+ return `${colors[level] || ""}${level}${reset}`;
38
+ }
39
+ export const serviceAccountCommand = new Command("sa")
40
+ .description("Manage Agent Service Accounts")
41
+ .addHelpText("after", `
42
+ Examples:
43
+ husky sa list List all service account bindings
44
+ husky sa get developer-agent Show binding for developer persona
45
+ husky sa sync Sync bindings from GCP config
46
+ husky sa verify Verify current VM's service account
47
+ `);
48
+ // List all bindings
49
+ serviceAccountCommand
50
+ .command("list")
51
+ .description("List all service account bindings")
52
+ .option("--json", "Output as JSON")
53
+ .action(async (options) => {
54
+ try {
55
+ const data = await fetchApi("/api/service-account-bindings");
56
+ const bindings = (data.bindings || []);
57
+ if (options.json) {
58
+ console.log(JSON.stringify(bindings, null, 2));
59
+ return;
60
+ }
61
+ if (bindings.length === 0) {
62
+ console.log("No service account bindings found.");
63
+ console.log("Run 'husky sa sync' to create bindings from GCP config.");
64
+ return;
65
+ }
66
+ console.log("\nšŸ“‹ Service Account Bindings\n");
67
+ console.log("━".repeat(80));
68
+ for (const binding of bindings) {
69
+ console.log(`${formatPersona(binding.personaType)}`);
70
+ console.log(` Email: ${binding.serviceAccountEmail}`);
71
+ console.log(` Permission: ${formatPermissionLevel(binding.permissionLevel)}`);
72
+ console.log(` Roles: ${binding.iamRoles.join(", ") || "none"}`);
73
+ if (binding.lastSyncedAt) {
74
+ console.log(` Last Synced: ${new Date(binding.lastSyncedAt).toLocaleString()}`);
75
+ }
76
+ console.log("━".repeat(80));
77
+ }
78
+ console.log(`\nTotal: ${bindings.length} bindings`);
79
+ }
80
+ catch (error) {
81
+ console.error("Error:", error instanceof Error ? error.message : error);
82
+ process.exit(1);
83
+ }
84
+ });
85
+ // Get specific binding
86
+ serviceAccountCommand
87
+ .command("get <persona>")
88
+ .description("Get service account binding for a persona")
89
+ .option("--json", "Output as JSON")
90
+ .action(async (persona, options) => {
91
+ try {
92
+ // First list all and filter by persona
93
+ const data = await fetchApi("/api/service-account-bindings");
94
+ const bindings = (data.bindings || []);
95
+ const binding = bindings.find((b) => b.personaType === persona);
96
+ if (!binding) {
97
+ console.log(`No binding found for persona: ${persona}`);
98
+ console.log("\nAvailable personas:");
99
+ bindings.forEach((b) => console.log(` - ${b.personaType}`));
100
+ return;
101
+ }
102
+ if (options.json) {
103
+ console.log(JSON.stringify(binding, null, 2));
104
+ return;
105
+ }
106
+ console.log(`\n${formatPersona(binding.personaType)}\n`);
107
+ console.log(`Email: ${binding.serviceAccountEmail}`);
108
+ console.log(`Permission: ${formatPermissionLevel(binding.permissionLevel)}`);
109
+ console.log(`Project: ${binding.projectId}`);
110
+ console.log(`Roles:`);
111
+ binding.iamRoles.forEach((role) => console.log(` - ${role}`));
112
+ if (binding.lastSyncedAt) {
113
+ console.log(`Last Synced: ${new Date(binding.lastSyncedAt).toLocaleString()}`);
114
+ }
115
+ console.log(`Created: ${new Date(binding.createdAt).toLocaleString()}`);
116
+ }
117
+ catch (error) {
118
+ console.error("Error:", error instanceof Error ? error.message : error);
119
+ process.exit(1);
120
+ }
121
+ });
122
+ // Sync from GCP
123
+ serviceAccountCommand
124
+ .command("sync")
125
+ .description("Sync service account bindings from GCP config")
126
+ .action(async () => {
127
+ try {
128
+ console.log("šŸ”„ Syncing service account bindings...\n");
129
+ const data = await fetchApi("/api/service-account-bindings/sync", {
130
+ method: "POST",
131
+ });
132
+ console.log(`āœ… Synced ${data.synced} bindings\n`);
133
+ for (const result of (data.results || [])) {
134
+ const icon = result.status === "synced" ? "āœ“" : "āœ—";
135
+ console.log(` ${icon} ${result.personaType}: ${result.email || result.status}`);
136
+ }
137
+ console.log("\nRun 'husky sa list' to see all bindings.");
138
+ }
139
+ catch (error) {
140
+ console.error("Error:", error instanceof Error ? error.message : error);
141
+ process.exit(1);
142
+ }
143
+ });
144
+ // Verify current VM's SA (for use on VMs)
145
+ serviceAccountCommand
146
+ .command("verify")
147
+ .description("Verify current VM's service account (run on VM)")
148
+ .action(async () => {
149
+ try {
150
+ // Check if running on GCP by querying metadata service
151
+ const metadataUrl = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email";
152
+ console.log("šŸ” Checking GCP metadata service...\n");
153
+ const response = await fetch(metadataUrl, {
154
+ headers: { "Metadata-Flavor": "Google" },
155
+ }).catch(() => null);
156
+ if (!response || !response.ok) {
157
+ console.log("āŒ Not running on a GCP VM (metadata service unavailable)");
158
+ console.log("\nThis command is designed to run on GCP Compute Engine VMs.");
159
+ return;
160
+ }
161
+ const saEmail = await response.text();
162
+ console.log(`āœ… Running as service account: ${saEmail}`);
163
+ // Try to match to a persona
164
+ const personaMatch = saEmail.match(/husky-(\w+)@/);
165
+ if (personaMatch) {
166
+ const persona = personaMatch[1] + "-agent";
167
+ console.log(` Persona: ${formatPersona(persona)}`);
168
+ }
169
+ // Report to Husky if we have a task ID
170
+ const taskId = process.env.HUSKY_TASK_ID;
171
+ if (taskId) {
172
+ console.log(`\nšŸ“¤ Reporting to Husky (task: ${taskId})...`);
173
+ // The VM startup script handles this, but we can also report here
174
+ }
175
+ }
176
+ catch (error) {
177
+ console.error("Error:", error instanceof Error ? error.message : error);
178
+ process.exit(1);
179
+ }
180
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const servicesCommand: Command;