@simonfestl/husky-cli 0.9.7 → 1.0.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/README.md CHANGED
@@ -111,19 +111,6 @@ husky vm update <session-id> --status approved
111
111
  husky vm delete <session-id>
112
112
  ```
113
113
 
114
- ### Jules Session Management
115
-
116
- ```bash
117
- husky jules list
118
- husky jules create --task-id <id> --prompt "..."
119
- husky jules get <session-id>
120
- husky jules message <session-id> --content "..."
121
- husky jules approve <session-id>
122
- husky jules activities <session-id>
123
- husky jules update <session-id> --status completed
124
- husky jules delete <session-id>
125
- ```
126
-
127
114
  ### Business Strategy
128
115
 
129
116
  ```bash
@@ -283,6 +270,25 @@ husky --version
283
270
 
284
271
  ## Changelog
285
272
 
273
+ ### v1.0.0 (2026-01-08) - Supervisor Architecture
274
+
275
+ **BREAKING CHANGES:**
276
+ - Removed: `husky jules` commands (replaced by supervisor architecture)
277
+ - Removed: `husky services` commands (deprecated)
278
+ - Removed: VM Pool management (now automated by supervisor)
279
+ - Deprecated: `husky agent` commands (use `husky task` instead)
280
+
281
+ **New Features:**
282
+ - Added: `husky chat` command for supervisor communication
283
+ - Added: Project resolver to prevent orphaned tasks
284
+ - Added: Implementation plans via `husky task plan`
285
+ - Added: QA review pipeline support
286
+
287
+ **Improvements:**
288
+ - Version now read dynamically from package.json
289
+ - Simplified task workflow
290
+ - Scope-based API key system support
291
+
286
292
  ### v0.5.0 (2026-01-06)
287
293
  - Full Dashboard feature parity (69 new commands)
288
294
  - Added: project, workflow, idea, department, vm, jules, process, strategy, settings, vm-config commands
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const chatCommand: Command;
@@ -0,0 +1,162 @@
1
+ import { Command } from "commander";
2
+ import { getConfig } from "./config.js";
3
+ export const chatCommand = new Command("chat")
4
+ .description("Communicate with the dashboard chat");
5
+ chatCommand
6
+ .command("pending")
7
+ .description("Get pending messages from user")
8
+ .option("--json", "Output as JSON")
9
+ .action(async (options) => {
10
+ const config = getConfig();
11
+ if (!config.apiUrl) {
12
+ console.error("Error: API URL not configured.");
13
+ process.exit(1);
14
+ }
15
+ try {
16
+ const res = await fetch(`${config.apiUrl}/api/chat/pending`, {
17
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
18
+ });
19
+ if (!res.ok) {
20
+ throw new Error(`API error: ${res.status}`);
21
+ }
22
+ const data = await res.json();
23
+ const messages = data.messages || [];
24
+ if (options.json) {
25
+ console.log(JSON.stringify(messages, null, 2));
26
+ return;
27
+ }
28
+ if (messages.length === 0) {
29
+ console.log("No pending messages.");
30
+ return;
31
+ }
32
+ console.log("\n Pending Messages");
33
+ console.log(" " + "─".repeat(60));
34
+ for (const msg of messages) {
35
+ const time = new Date(msg.createdAt).toLocaleTimeString();
36
+ console.log(` [${time}] ${msg.content.slice(0, 60)}${msg.content.length > 60 ? "..." : ""}`);
37
+ if (msg.taskId) {
38
+ console.log(` Task: ${msg.taskId}`);
39
+ }
40
+ }
41
+ console.log("");
42
+ }
43
+ catch (error) {
44
+ console.error("Error fetching messages:", error);
45
+ process.exit(1);
46
+ }
47
+ });
48
+ chatCommand
49
+ .command("list")
50
+ .description("List recent chat messages")
51
+ .option("--limit <n>", "Number of messages", "20")
52
+ .option("--json", "Output as JSON")
53
+ .action(async (options) => {
54
+ const config = getConfig();
55
+ if (!config.apiUrl) {
56
+ console.error("Error: API URL not configured.");
57
+ process.exit(1);
58
+ }
59
+ try {
60
+ const res = await fetch(`${config.apiUrl}/api/chat?limit=${options.limit}`, {
61
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
62
+ });
63
+ if (!res.ok) {
64
+ throw new Error(`API error: ${res.status}`);
65
+ }
66
+ const data = await res.json();
67
+ const messages = data.messages || [];
68
+ if (options.json) {
69
+ console.log(JSON.stringify(messages, null, 2));
70
+ return;
71
+ }
72
+ if (messages.length === 0) {
73
+ console.log("No messages.");
74
+ return;
75
+ }
76
+ console.log("\n Chat History");
77
+ console.log(" " + "─".repeat(60));
78
+ for (const msg of messages) {
79
+ const time = new Date(msg.createdAt).toLocaleTimeString();
80
+ const role = msg.role === "user" ? "USER" : msg.role === "supervisor" ? "SUPV" : "SYS";
81
+ const icon = msg.role === "user" ? "👤" : msg.role === "supervisor" ? "🤖" : "⚙️";
82
+ console.log(` ${icon} [${time}] ${role}: ${msg.content.slice(0, 50)}${msg.content.length > 50 ? "..." : ""}`);
83
+ }
84
+ console.log("");
85
+ }
86
+ catch (error) {
87
+ console.error("Error fetching messages:", error);
88
+ process.exit(1);
89
+ }
90
+ });
91
+ chatCommand
92
+ .command("send <message>")
93
+ .description("Send a message as supervisor")
94
+ .option("--task-id <id>", "Link to a specific task")
95
+ .action(async (message, options) => {
96
+ const config = getConfig();
97
+ if (!config.apiUrl) {
98
+ console.error("Error: API URL not configured.");
99
+ process.exit(1);
100
+ }
101
+ try {
102
+ const res = await fetch(`${config.apiUrl}/api/chat/supervisor`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
107
+ },
108
+ body: JSON.stringify({
109
+ content: message,
110
+ taskId: options.taskId,
111
+ }),
112
+ });
113
+ if (!res.ok) {
114
+ throw new Error(`API error: ${res.status}`);
115
+ }
116
+ console.log("Message sent.");
117
+ }
118
+ catch (error) {
119
+ console.error("Error sending message:", error);
120
+ process.exit(1);
121
+ }
122
+ });
123
+ chatCommand
124
+ .command("reply <messageId> <response>")
125
+ .description("Reply to a specific user message")
126
+ .option("--task-id <id>", "Link to a specific task")
127
+ .action(async (messageId, response, options) => {
128
+ const config = getConfig();
129
+ if (!config.apiUrl) {
130
+ console.error("Error: API URL not configured.");
131
+ process.exit(1);
132
+ }
133
+ try {
134
+ const res = await fetch(`${config.apiUrl}/api/chat/${messageId}/reply`, {
135
+ method: "POST",
136
+ headers: {
137
+ "Content-Type": "application/json",
138
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
139
+ },
140
+ body: JSON.stringify({
141
+ content: response,
142
+ taskId: options.taskId,
143
+ }),
144
+ });
145
+ if (!res.ok) {
146
+ throw new Error(`API error: ${res.status}`);
147
+ }
148
+ await fetch(`${config.apiUrl}/api/chat`, {
149
+ method: "PATCH",
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
153
+ },
154
+ body: JSON.stringify({ messageIds: [messageId] }),
155
+ });
156
+ console.log("Reply sent and message marked as read.");
157
+ }
158
+ catch (error) {
159
+ console.error("Error replying:", error);
160
+ process.exit(1);
161
+ }
162
+ });
@@ -20,7 +20,6 @@ const COMMANDS = {
20
20
  idea: ["list", "create", "get", "update", "delete", "convert"],
21
21
  department: ["list", "create", "get", "update", "delete"],
22
22
  vm: ["list", "create", "get", "update", "delete", "start", "stop", "logs", "approve", "reject"],
23
- jules: ["list", "create", "get", "update", "delete", "message", "approve", "activities", "sources"],
24
23
  process: ["list", "create", "get", "update", "delete"],
25
24
  strategy: [
26
25
  "show", "set-vision", "set-mission", "add-value", "update-value", "delete-value",
@@ -195,7 +194,6 @@ _husky() {
195
194
  'idea:Manage ideas'
196
195
  'department:Manage departments'
197
196
  'vm:Manage VM sessions'
198
- 'jules:Manage Jules AI coding sessions'
199
197
  'process:Manage processes'
200
198
  'strategy:Manage business strategy'
201
199
  'settings:Manage application settings'
@@ -286,7 +284,6 @@ function getCommandDescription(cmd) {
286
284
  idea: "Manage ideas",
287
285
  department: "Manage departments",
288
286
  vm: "Manage VM sessions",
289
- jules: "Manage Jules AI coding sessions",
290
287
  process: "Manage processes",
291
288
  strategy: "Manage business strategy",
292
289
  settings: "Manage application settings",
@@ -356,12 +353,6 @@ function getSubcommandDescription(cmd, sub) {
356
353
  approve: "Approve VM session plan",
357
354
  reject: "Reject VM session plan",
358
355
  },
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
356
  strategy: {
366
357
  "set-vision": "Set/update the company vision",
367
358
  "set-mission": "Set/update the company mission",
@@ -1,5 +1,6 @@
1
1
  import { select, input, confirm } from "@inquirer/prompts";
2
2
  import { ensureConfig, pressEnterToContinue, truncate } from "./utils.js";
3
+ import { resolveProject } from "../../lib/project-resolver.js";
3
4
  export async function tasksMenu() {
4
5
  const config = ensureConfig();
5
6
  const menuItems = [
@@ -157,7 +158,6 @@ async function createTask(config) {
157
158
  ],
158
159
  default: "medium",
159
160
  });
160
- // Optional: Link to project
161
161
  const projects = await fetchProjects(config);
162
162
  let projectId;
163
163
  if (projects.length > 0) {
@@ -166,8 +166,49 @@ async function createTask(config) {
166
166
  default: false,
167
167
  });
168
168
  if (linkToProject) {
169
- const projectChoices = projects.map((p) => ({ name: p.name, value: p.id }));
170
- projectId = await select({ message: "Select project:", choices: projectChoices });
169
+ const selectionMethod = await select({
170
+ message: "How to select project?",
171
+ choices: [
172
+ { name: "Choose from list", value: "list" },
173
+ { name: "Type project name", value: "type" },
174
+ ],
175
+ });
176
+ if (selectionMethod === "list") {
177
+ const projectChoices = projects.map((p) => ({ name: p.name, value: p.id }));
178
+ projectId = await select({ message: "Select project:", choices: projectChoices });
179
+ }
180
+ else {
181
+ const projectInput = await input({
182
+ message: "Project name or ID:",
183
+ validate: (value) => (value.length > 0 ? true : "Project name required"),
184
+ });
185
+ const resolved = await resolveProject(projectInput, config);
186
+ if (!resolved) {
187
+ console.log(`\n ❌ Project "${projectInput}" not found.`);
188
+ console.log(" Available projects:");
189
+ for (const p of projects) {
190
+ console.log(` - ${p.name}`);
191
+ }
192
+ console.log("");
193
+ await pressEnterToContinue();
194
+ return;
195
+ }
196
+ if (resolved.resolvedBy === "fuzzy-match") {
197
+ const confirmFuzzy = await confirm({
198
+ message: `Did you mean "${resolved.projectName}"? (${Math.round(resolved.confidence * 100)}% match)`,
199
+ default: true,
200
+ });
201
+ if (!confirmFuzzy) {
202
+ console.log("\n Cancelled.\n");
203
+ await pressEnterToContinue();
204
+ return;
205
+ }
206
+ }
207
+ if (resolved.resolvedBy !== "exact-id") {
208
+ console.log(` ℹ️ Resolved to: ${resolved.projectName}`);
209
+ }
210
+ projectId = resolved.projectId;
211
+ }
171
212
  }
172
213
  }
173
214
  const res = await fetch(`${config.apiUrl}/api/tasks`, {
@@ -254,16 +295,59 @@ async function updateTask(config) {
254
295
  });
255
296
  break;
256
297
  case "project":
257
- const projects = await fetchProjects(config);
258
- if (projects.length === 0) {
298
+ const projectsForUpdate = await fetchProjects(config);
299
+ if (projectsForUpdate.length === 0) {
259
300
  console.log("\n No projects available.\n");
260
301
  await pressEnterToContinue();
261
302
  return;
262
303
  }
263
- const projectChoices = projects.map((p) => ({ name: p.name, value: p.id }));
264
- projectChoices.push({ name: "Remove project link", value: "__none__" });
265
- const projectId = await select({ message: "Select project:", choices: projectChoices });
266
- updateData.projectId = projectId === "__none__" ? "" : projectId;
304
+ const projectSelectionMethod = await select({
305
+ message: "How to select project?",
306
+ choices: [
307
+ { name: "Choose from list", value: "list" },
308
+ { name: "Type project name", value: "type" },
309
+ { name: "Remove project link", value: "remove" },
310
+ ],
311
+ });
312
+ if (projectSelectionMethod === "remove") {
313
+ updateData.projectId = "";
314
+ }
315
+ else if (projectSelectionMethod === "list") {
316
+ const projectChoicesForUpdate = projectsForUpdate.map((p) => ({ name: p.name, value: p.id }));
317
+ updateData.projectId = await select({ message: "Select project:", choices: projectChoicesForUpdate });
318
+ }
319
+ else {
320
+ const projectInputForUpdate = await input({
321
+ message: "Project name or ID:",
322
+ validate: (value) => (value.length > 0 ? true : "Project name required"),
323
+ });
324
+ const resolvedForUpdate = await resolveProject(projectInputForUpdate, config);
325
+ if (!resolvedForUpdate) {
326
+ console.log(`\n ❌ Project "${projectInputForUpdate}" not found.`);
327
+ console.log(" Available projects:");
328
+ for (const p of projectsForUpdate) {
329
+ console.log(` - ${p.name}`);
330
+ }
331
+ console.log("");
332
+ await pressEnterToContinue();
333
+ return;
334
+ }
335
+ if (resolvedForUpdate.resolvedBy === "fuzzy-match") {
336
+ const confirmFuzzyUpdate = await confirm({
337
+ message: `Did you mean "${resolvedForUpdate.projectName}"? (${Math.round(resolvedForUpdate.confidence * 100)}% match)`,
338
+ default: true,
339
+ });
340
+ if (!confirmFuzzyUpdate) {
341
+ console.log("\n Cancelled.\n");
342
+ await pressEnterToContinue();
343
+ return;
344
+ }
345
+ }
346
+ if (resolvedForUpdate.resolvedBy !== "exact-id") {
347
+ console.log(` ℹ️ Resolved to: ${resolvedForUpdate.projectName}`);
348
+ }
349
+ updateData.projectId = resolvedForUpdate.projectId;
350
+ }
267
351
  break;
268
352
  case "title":
269
353
  updateData.title = await input({
@@ -8,7 +8,6 @@ import { departmentsMenu } from "./interactive/departments.js";
8
8
  import { processesMenu } from "./interactive/processes.js";
9
9
  import { workflowsMenu } from "./interactive/workflows.js";
10
10
  import { vmSessionsMenu } from "./interactive/vm-sessions.js";
11
- import { julesSessionsMenu } from "./interactive/jules-sessions.js";
12
11
  import { roadmapsMenu } from "./interactive/roadmaps.js";
13
12
  import { strategyMenu } from "./interactive/strategy.js";
14
13
  import { changelogMenu } from "./interactive/changelog.js";
@@ -34,7 +33,6 @@ export async function runInteractiveMode() {
34
33
  { name: "Processes", value: "processes", description: "Manage processes" },
35
34
  { name: "---", value: "separator2", description: "" },
36
35
  { name: "VM Sessions", value: "vm", description: "Manage VM sessions" },
37
- { name: "Jules Sessions", value: "jules", description: "Manage Jules AI sessions" },
38
36
  { name: "Worktrees", value: "worktrees", description: "Manage Git worktrees for agent isolation" },
39
37
  { name: "---", value: "separator3", description: "" },
40
38
  { name: "Business Operations", value: "business", description: "Billbee, Zendesk, SeaTable, Qdrant" },
@@ -75,9 +73,6 @@ export async function runInteractiveMode() {
75
73
  case "vm":
76
74
  await vmSessionsMenu();
77
75
  break;
78
- case "jules":
79
- await julesSessionsMenu();
80
- break;
81
76
  case "worktrees":
82
77
  await worktreesMenu();
83
78
  break;
@@ -1,7 +1,3 @@
1
- /**
2
- * LLM Context Generator
3
- * Outputs markdown reference for LLM agents
4
- */
5
1
  export declare function generateLLMContext(): string;
6
2
  export declare function printLLMContext(): void;
7
3
  import { Command } from "commander";
@@ -1,10 +1,8 @@
1
- /**
2
- * LLM Context Generator
3
- * Outputs markdown reference for LLM agents
4
- */
5
- const VERSION = "0.9.7";
1
+ import { createRequire } from "module";
2
+ const require = createRequire(import.meta.url);
3
+ const packageJson = require("../../package.json");
6
4
  export function generateLLMContext() {
7
- return `# Husky CLI Reference (v${VERSION})
5
+ return `# Husky CLI Reference (v${packageJson.version})
8
6
 
9
7
  > [!CAUTION]
10
8
  > ## MANDATORY: You MUST Use Husky CLI
@@ -146,14 +144,6 @@ husky vm ssh <id> # SSH into VM
146
144
  husky vm delete <id> # Delete VM
147
145
  \`\`\`
148
146
 
149
- ### Jules Sessions (AI Agent)
150
- \`\`\`bash
151
- husky jules list # List Jules sessions
152
- husky jules create # Create new session
153
- husky jules status <id> # Get session status
154
- husky jules logs <id> # View session logs
155
- \`\`\`
156
-
157
147
  ### Workers (Multi-Agent Coordination)
158
148
  \`\`\`bash
159
149
  husky worker whoami # Show current worker identity
@@ -6,6 +6,7 @@ import { paginateList, printPaginated } from "../lib/pagination.js";
6
6
  import { ensureWorkerRegistered, generateSessionId, registerSession } from "../lib/worker.js";
7
7
  import { WorktreeManager } from "../lib/worktree.js";
8
8
  import { execSync } from "child_process";
9
+ import { resolveProject, fetchProjects, formatProjectList } from "../lib/project-resolver.js";
9
10
  export const taskCommand = new Command("task")
10
11
  .description("Manage tasks");
11
12
  // Helper: Get task ID from --id flag or HUSKY_TASK_ID env var
@@ -281,6 +282,7 @@ taskCommand
281
282
  .command("done <id>")
282
283
  .description("Mark task as done")
283
284
  .option("--pr <url>", "Link to PR")
285
+ .option("--skip-qa", "Skip QA review and mark as done directly")
284
286
  .action(async (id, options) => {
285
287
  const config = getConfig();
286
288
  if (!config.apiUrl) {
@@ -294,25 +296,36 @@ taskCommand
294
296
  "Content-Type": "application/json",
295
297
  ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
296
298
  },
297
- body: JSON.stringify({ prUrl: options.pr }),
299
+ body: JSON.stringify({
300
+ prUrl: options.pr,
301
+ skipQA: options.skipQa === true,
302
+ }),
298
303
  });
299
304
  if (!res.ok) {
300
305
  throw new Error(`API error: ${res.status}`);
301
306
  }
302
307
  const task = await res.json();
303
- console.log(`✓ Completed: ${task.title}`);
308
+ if (task.qaTriggered) {
309
+ console.log(`✓ Task moved to review: ${task.title}`);
310
+ console.log(` QA pipeline triggered - awaiting verification`);
311
+ }
312
+ else {
313
+ console.log(`✓ Completed: ${task.title}`);
314
+ }
315
+ if (task.message) {
316
+ console.log(` ${task.message}`);
317
+ }
304
318
  }
305
319
  catch (error) {
306
320
  console.error("Error completing task:", error);
307
321
  process.exit(1);
308
322
  }
309
323
  });
310
- // husky task create <title>
311
324
  taskCommand
312
325
  .command("create <title>")
313
326
  .description("Create a new task")
314
327
  .option("-d, --description <desc>", "Task description")
315
- .option("--project <projectId>", "Project ID")
328
+ .option("--project <project>", "Project name or ID")
316
329
  .option("--path <path>", "Path in project")
317
330
  .option("-p, --priority <priority>", "Priority (low, medium, high)", "medium")
318
331
  .action(async (title, options) => {
@@ -321,6 +334,29 @@ taskCommand
321
334
  console.error("Error: API URL not configured.");
322
335
  process.exit(1);
323
336
  }
337
+ let resolvedProjectId;
338
+ const resolverConfig = { apiUrl: config.apiUrl, apiKey: config.apiKey };
339
+ if (options.project) {
340
+ const resolved = await resolveProject(options.project, resolverConfig);
341
+ if (!resolved) {
342
+ const projects = await fetchProjects(resolverConfig);
343
+ console.error(`\n❌ Project "${options.project}" not found.\n`);
344
+ console.error(formatProjectList(projects));
345
+ process.exit(1);
346
+ }
347
+ if (resolved.resolvedBy === "name-match") {
348
+ console.log(`ℹ️ Resolved "${options.project}" → ${resolved.projectName} (${resolved.projectId})`);
349
+ }
350
+ if (resolved.resolvedBy === "fuzzy-match") {
351
+ const confirmed = await confirm(`Did you mean "${resolved.projectName}"? (${Math.round(resolved.confidence * 100)}% match)`);
352
+ if (!confirmed) {
353
+ console.log("Cancelled.");
354
+ process.exit(0);
355
+ }
356
+ console.log(`ℹ️ Using project: ${resolved.projectName} (${resolved.projectId})`);
357
+ }
358
+ resolvedProjectId = resolved.projectId;
359
+ }
324
360
  try {
325
361
  const res = await fetch(`${config.apiUrl}/api/tasks`, {
326
362
  method: "POST",
@@ -331,13 +367,14 @@ taskCommand
331
367
  body: JSON.stringify({
332
368
  title,
333
369
  description: options.description,
334
- projectId: options.project,
370
+ projectId: resolvedProjectId,
335
371
  linkedPath: options.path,
336
372
  priority: options.priority,
337
373
  }),
338
374
  });
339
375
  if (!res.ok) {
340
- throw new Error(`API error: ${res.status}`);
376
+ const errorData = await res.json().catch(() => ({}));
377
+ throw new Error(errorData.error || `API error: ${res.status}`);
341
378
  }
342
379
  const task = await res.json();
343
380
  console.log(`✓ Created: #${task.id} ${task.title}`);
@@ -347,7 +384,6 @@ taskCommand
347
384
  process.exit(1);
348
385
  }
349
386
  });
350
- // husky task update <id>
351
387
  taskCommand
352
388
  .command("update <id>")
353
389
  .description("Update task properties")
@@ -356,12 +392,12 @@ taskCommand
356
392
  .option("--status <status>", "New status (e.g., backlog, in_progress, review, done, or custom status)")
357
393
  .option("--priority <priority>", "New priority (low, medium, high, urgent)")
358
394
  .option("--assignee <assignee>", "New assignee (human, llm, unassigned)")
359
- .option("--project <projectId>", "Link to project")
395
+ .option("--project <project>", "Link to project (name or ID)")
360
396
  .option("--no-worktree", "Skip automatic worktree creation when starting task")
361
397
  .option("--json", "Output as JSON")
362
398
  .action(async (id, options) => {
363
399
  const config = ensureConfig();
364
- // Build update payload with only changed fields
400
+ const resolverConfig = { apiUrl: config.apiUrl, apiKey: config.apiKey };
365
401
  const updates = {};
366
402
  if (options.title)
367
403
  updates.title = options.title;
@@ -373,8 +409,27 @@ taskCommand
373
409
  updates.priority = options.priority;
374
410
  if (options.assignee)
375
411
  updates.assignee = options.assignee;
376
- if (options.project)
377
- updates.projectId = options.project;
412
+ if (options.project) {
413
+ const resolved = await resolveProject(options.project, resolverConfig);
414
+ if (!resolved) {
415
+ const projects = await fetchProjects(resolverConfig);
416
+ console.error(`\n❌ Project "${options.project}" not found.\n`);
417
+ console.error(formatProjectList(projects));
418
+ process.exit(1);
419
+ }
420
+ if (resolved.resolvedBy === "name-match") {
421
+ console.log(`ℹ️ Resolved "${options.project}" → ${resolved.projectName} (${resolved.projectId})`);
422
+ }
423
+ if (resolved.resolvedBy === "fuzzy-match") {
424
+ const confirmed = await confirm(`Did you mean "${resolved.projectName}"? (${Math.round(resolved.confidence * 100)}% match)`);
425
+ if (!confirmed) {
426
+ console.log("Cancelled.");
427
+ process.exit(0);
428
+ }
429
+ console.log(`ℹ️ Using project: ${resolved.projectName} (${resolved.projectId})`);
430
+ }
431
+ updates.projectId = resolved.projectId;
432
+ }
378
433
  if (Object.keys(updates).length === 0) {
379
434
  console.error("Error: No update options provided. Use --help for available options.");
380
435
  process.exit(1);
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ import { createRequire } from "module";
3
4
  import { taskCommand } from "./commands/task.js";
4
5
  import { configCommand } from "./commands/config.js";
5
6
  import { agentCommand } from "./commands/agent.js";
@@ -10,7 +11,6 @@ import { projectCommand } from "./commands/project.js";
10
11
  import { ideaCommand } from "./commands/idea.js";
11
12
  import { departmentCommand } from "./commands/department.js";
12
13
  import { workflowCommand } from "./commands/workflow.js";
13
- import { julesCommand } from "./commands/jules.js";
14
14
  import { vmCommand } from "./commands/vm.js";
15
15
  import { vmConfigCommand } from "./commands/vm-config.js";
16
16
  import { processCommand } from "./commands/process.js";
@@ -20,15 +20,18 @@ 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 { servicesCommand } from "./commands/services.js";
24
23
  import { printLLMContext, llmCommand } from "./commands/llm-context.js";
25
24
  import { runInteractiveMode } from "./commands/interactive.js";
26
25
  import { serviceAccountCommand } from "./commands/service-account.js";
26
+ import { chatCommand } from "./commands/chat.js";
27
+ // Read version from package.json
28
+ const require = createRequire(import.meta.url);
29
+ const packageJson = require("../package.json");
27
30
  const program = new Command();
28
31
  program
29
32
  .name("husky")
30
33
  .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
31
- .version("0.9.7")
34
+ .version(packageJson.version)
32
35
  .option("--llm", "Output LLM reference documentation (markdown)");
33
36
  program.addCommand(taskCommand);
34
37
  program.addCommand(configCommand);
@@ -40,7 +43,6 @@ program.addCommand(projectCommand);
40
43
  program.addCommand(ideaCommand);
41
44
  program.addCommand(departmentCommand);
42
45
  program.addCommand(workflowCommand);
43
- program.addCommand(julesCommand);
44
46
  program.addCommand(vmCommand);
45
47
  program.addCommand(vmConfigCommand);
46
48
  program.addCommand(processCommand);
@@ -50,8 +52,8 @@ program.addCommand(completionCommand);
50
52
  program.addCommand(worktreeCommand);
51
53
  program.addCommand(workerCommand);
52
54
  program.addCommand(bizCommand);
53
- program.addCommand(servicesCommand);
54
55
  program.addCommand(serviceAccountCommand);
56
+ program.addCommand(chatCommand);
55
57
  program.addCommand(llmCommand);
56
58
  // Handle --llm flag specially
57
59
  if (process.argv.includes("--llm")) {