@simonfestl/husky-cli 0.6.0 → 0.6.2

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
@@ -39,6 +39,8 @@ husky
39
39
  husky task list # List all tasks
40
40
  husky task list --status in_progress # Filter by status
41
41
  husky task list --json # JSON output
42
+ husky task list -i # Interactive pagination
43
+ husky task list --per-page 10 --page 2 # Paginated output
42
44
  husky task create "Fix login bug" --priority high
43
45
  husky task get <task-id>
44
46
  husky task start <task-id>
@@ -50,7 +52,9 @@ husky task delete <task-id>
50
52
  ### Project Management
51
53
 
52
54
  ```bash
53
- husky project list
55
+ husky project list # List all projects
56
+ husky project list -i # Interactive pagination
57
+ husky project list --per-page 5 --page 1 # Paginated output
54
58
  husky project create "New Project" --description "..."
55
59
  husky project get <project-id>
56
60
  husky project update <project-id> --status active
@@ -63,7 +67,9 @@ husky project delete-knowledge <project-id> <knowledge-id>
63
67
  ### Workflow Management
64
68
 
65
69
  ```bash
66
- husky workflow list
70
+ husky workflow list # List all workflows
71
+ husky workflow list -i # Interactive pagination
72
+ husky workflow list --per-page 5 # Paginated output
67
73
  husky workflow create "Onboarding" --department <id>
68
74
  husky workflow get <workflow-id>
69
75
  husky workflow update <workflow-id> --name "Updated"
@@ -78,7 +84,9 @@ husky workflow generate-mermaid <workflow-id> # Mermaid diagram
78
84
  ### Idea Management
79
85
 
80
86
  ```bash
81
- husky idea list
87
+ husky idea list # List all ideas
88
+ husky idea list -i # Interactive pagination
89
+ husky idea list --per-page 10 # Paginated output
82
90
  husky idea create "New feature idea" --category feature
83
91
  husky idea get <idea-id>
84
92
  husky idea update <idea-id> --status approved
@@ -160,6 +168,8 @@ husky roadmap create "Q1 2025" --description "..."
160
168
  husky roadmap update <roadmap-id> --name "Updated"
161
169
  husky roadmap delete <roadmap-id>
162
170
  husky roadmap add-phase <roadmap-id> --name "Phase 1"
171
+ husky roadmap update-phase <roadmap-id> <phase-id> --name "Updated"
172
+ husky roadmap delete-phase <roadmap-id> <phase-id> --force
163
173
  husky roadmap add-feature <roadmap-id> --phase <id> --title "Feature"
164
174
  husky roadmap list-features <roadmap-id>
165
175
  husky roadmap update-feature <roadmap-id> <feature-id> --status done
@@ -187,6 +197,20 @@ husky vm-config update <config-id> --machine-type e2-standard-2
187
197
  husky vm-config delete <config-id>
188
198
  ```
189
199
 
200
+ ### Git Worktree Management
201
+
202
+ ```bash
203
+ husky worktree list # List all worktrees
204
+ husky worktree create <session-name> # Create worktree
205
+ husky worktree info <session-name> # Show details
206
+ husky worktree status [session-name] # Show status
207
+ husky worktree cd <session-name> # Print path
208
+ husky worktree merge <session-name> # Merge to base
209
+ husky worktree remove <session-name> # Remove worktree
210
+ husky worktree branches # List husky/* branches
211
+ husky worktree cleanup # Clean stale worktrees
212
+ ```
213
+
190
214
  ### Settings
191
215
 
192
216
  ```bash
@@ -221,6 +245,35 @@ husky completion zsh >> ~/.zshrc
221
245
  husky completion fish > ~/.config/fish/completions/husky.fish
222
246
  ```
223
247
 
248
+ ## Pagination
249
+
250
+ List commands support pagination with two modes:
251
+
252
+ ### Interactive Pagination (`-i`)
253
+
254
+ Navigate through results using arrow keys:
255
+
256
+ ```bash
257
+ husky task list -i # Interactive mode with arrow key navigation
258
+ husky project list -i # Works for projects, ideas, workflows
259
+ ```
260
+
261
+ Features:
262
+ - Use `↑`/`↓` to navigate items
263
+ - `←` Previous page / `→` Next page
264
+ - Press `Enter` to select and view details
265
+ - Press `Esc` or select "Exit" to return
266
+
267
+ ### Static Pagination (`--page`, `--per-page`)
268
+
269
+ For scripting or quick page views:
270
+
271
+ ```bash
272
+ husky task list --per-page 10 # Show first 10 items
273
+ husky task list --per-page 10 --page 2 # Show items 11-20
274
+ husky idea list -n 5 -p 3 # Short form: 5 items, page 3
275
+ ```
276
+
224
277
  ## Environment Variables
225
278
 
226
279
  | Variable | Description |
@@ -283,6 +336,22 @@ husky --version
283
336
 
284
337
  ## Changelog
285
338
 
339
+ ### v0.6.2 (2026-01-06)
340
+ - Added: Interactive pagination for list commands (`-i` flag)
341
+ - Added: Static pagination (`--page`, `--per-page` flags)
342
+ - Improved: Tasks, projects, ideas, workflows now support pagination
343
+
344
+ ### v0.6.1 (2026-01-06)
345
+ - Added: Roadmap phase management (`update-phase`, `delete-phase`)
346
+ - Fixed: Various bug fixes
347
+
348
+ ### v0.6.0 (2026-01-06)
349
+ - Added: Git Worktree support for multi-agent isolation
350
+ - Added: `husky worktree` commands (create, list, merge, remove, etc.)
351
+ - Added: MergeLock mechanism for safe concurrent operations
352
+ - Refactored: Interactive mode into modular components
353
+ - Improved: Based on Auto-Claude's worktree architecture
354
+
286
355
  ### v0.5.0 (2026-01-06)
287
356
  - Full Dashboard feature parity (69 new commands)
288
357
  - Added: project, workflow, idea, department, vm, jules, process, strategy, settings, vm-config commands
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
+ import { paginateList, printPaginated } from "../lib/pagination.js";
3
4
  export const ideaCommand = new Command("idea")
4
5
  .description("Manage ideas");
5
6
  // Helper: Ensure API is configured
@@ -11,12 +12,22 @@ function ensureConfig() {
11
12
  }
12
13
  return config;
13
14
  }
15
+ // Status labels and icons
16
+ const STATUS_CONFIG = {
17
+ draft: { label: "Draft", icon: "📝" },
18
+ active: { label: "Active", icon: "💡" },
19
+ archived: { label: "Archived", icon: "📦" },
20
+ converted: { label: "Converted", icon: "✅" },
21
+ };
14
22
  // husky idea list
15
23
  ideaCommand
16
24
  .command("list")
17
25
  .description("List all ideas")
18
26
  .option("--status <status>", "Filter by status (draft, active, archived, converted)")
19
27
  .option("--json", "Output as JSON")
28
+ .option("-p, --page <num>", "Page number (starts at 1)", "1")
29
+ .option("-n, --per-page <num>", "Items per page (default: all)")
30
+ .option("-i, --interactive", "Interactive pagination with arrow keys")
20
31
  .action(async (options) => {
21
32
  const config = ensureConfig();
22
33
  try {
@@ -33,10 +44,47 @@ ideaCommand
33
44
  const ideas = await res.json();
34
45
  if (options.json) {
35
46
  console.log(JSON.stringify(ideas, null, 2));
47
+ return;
36
48
  }
37
- else {
38
- printIdeas(ideas);
49
+ // Interactive pagination mode
50
+ if (options.interactive) {
51
+ await paginateList({
52
+ items: ideas,
53
+ pageSize: 10,
54
+ title: "Ideas",
55
+ emptyMessage: "No ideas found.",
56
+ renderItem: (idea) => {
57
+ const cfg = STATUS_CONFIG[idea.status] || { label: idea.status, icon: "○" };
58
+ return ` ${cfg.icon} ${idea.title.slice(0, 40).padEnd(40)} │ ${cfg.label}`;
59
+ },
60
+ selectableItems: true,
61
+ onSelect: async (idea) => {
62
+ console.clear();
63
+ console.log(`\n Idea: ${idea.title}`);
64
+ console.log(" " + "─".repeat(50));
65
+ console.log(` ID: ${idea.id}`);
66
+ console.log(` Status: ${STATUS_CONFIG[idea.status]?.label || idea.status}`);
67
+ if (idea.category)
68
+ console.log(` Category: ${idea.category}`);
69
+ if (idea.description)
70
+ console.log(` Desc: ${idea.description}`);
71
+ console.log("");
72
+ },
73
+ });
74
+ return;
75
+ }
76
+ // Simple pagination mode
77
+ if (options.perPage) {
78
+ const pageNum = parseInt(options.page, 10) - 1;
79
+ const pageSize = parseInt(options.perPage, 10);
80
+ printPaginated(ideas, pageNum, pageSize, (idea) => {
81
+ const cfg = STATUS_CONFIG[idea.status] || { label: idea.status, icon: "○" };
82
+ return ` ${cfg.icon} ${idea.id.slice(0, 8)} │ ${idea.title}`;
83
+ }, "Ideas");
84
+ return;
39
85
  }
86
+ // Default list
87
+ printIdeas(ideas);
40
88
  }
41
89
  catch (error) {
42
90
  console.error("Error fetching ideas:", error);
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
+ import { paginateList, printPaginated } from "../lib/pagination.js";
3
4
  export const projectCommand = new Command("project")
4
5
  .description("Manage projects and knowledge");
5
6
  // Helper: Ensure API is configured
@@ -37,6 +38,9 @@ projectCommand
37
38
  .option("--status <status>", "Filter by status (active, archived)")
38
39
  .option("--work-status <workStatus>", "Filter by work status (planning, in_progress, review, completed, on_hold)")
39
40
  .option("--archived", "Include archived projects")
41
+ .option("-p, --page <num>", "Page number (starts at 1)", "1")
42
+ .option("-n, --per-page <num>", "Items per page (default: all)")
43
+ .option("-i, --interactive", "Interactive pagination with arrow keys")
40
44
  .action(async (options) => {
41
45
  const config = ensureConfig();
42
46
  try {
@@ -60,10 +64,51 @@ projectCommand
60
64
  }
61
65
  if (options.json) {
62
66
  console.log(JSON.stringify(projects, null, 2));
67
+ return;
63
68
  }
64
- else {
65
- printProjects(projects);
69
+ // Interactive pagination mode
70
+ if (options.interactive) {
71
+ await paginateList({
72
+ items: projects,
73
+ pageSize: 10,
74
+ title: "Projects",
75
+ emptyMessage: "No projects found.",
76
+ renderItem: (project) => {
77
+ const statusIcon = project.status === "archived" ? "📦" : "📁";
78
+ const workStatusLabel = WORK_STATUS_LABELS[project.workStatus] || project.workStatus;
79
+ return ` ${statusIcon} ${project.name.slice(0, 30).padEnd(30)} │ ${workStatusLabel}`;
80
+ },
81
+ selectableItems: true,
82
+ onSelect: async (project) => {
83
+ console.clear();
84
+ console.log(`\n Project: ${project.name}`);
85
+ console.log(" " + "─".repeat(50));
86
+ console.log(` ID: ${project.id}`);
87
+ console.log(` Status: ${project.status}`);
88
+ console.log(` Work: ${WORK_STATUS_LABELS[project.workStatus]}`);
89
+ if (project.description)
90
+ console.log(` Desc: ${project.description}`);
91
+ if (project.techStack)
92
+ console.log(` Tech: ${project.techStack}`);
93
+ if (project.githubRepo)
94
+ console.log(` GitHub: ${project.githubRepo}`);
95
+ console.log("");
96
+ },
97
+ });
98
+ return;
99
+ }
100
+ // Simple pagination mode
101
+ if (options.perPage) {
102
+ const pageNum = parseInt(options.page, 10) - 1;
103
+ const pageSize = parseInt(options.perPage, 10);
104
+ printPaginated(projects, pageNum, pageSize, (project) => {
105
+ const statusIcon = project.status === "archived" ? "📦" : "📁";
106
+ return ` ${statusIcon} ${project.id.slice(0, 8)} │ ${project.name}`;
107
+ }, "Projects");
108
+ return;
66
109
  }
110
+ // Default: standard list
111
+ printProjects(projects);
67
112
  }
68
113
  catch (error) {
69
114
  console.error("Error fetching projects:", error);
@@ -152,6 +152,113 @@ roadmapCommand
152
152
  process.exit(1);
153
153
  }
154
154
  });
155
+ // husky roadmap update-phase <roadmapId> <phaseId>
156
+ roadmapCommand
157
+ .command("update-phase <roadmapId> <phaseId>")
158
+ .description("Update a roadmap phase")
159
+ .option("-n, --name <name>", "New phase name")
160
+ .option("-d, --description <description>", "New phase description")
161
+ .option("--status <status>", "Phase status (planned, in_progress, completed)")
162
+ .option("--order <order>", "Phase order (0-based)")
163
+ .action(async (roadmapId, phaseId, options) => {
164
+ const config = ensureConfig();
165
+ // Build update payload
166
+ const updateData = { phaseId };
167
+ if (options.name)
168
+ updateData.name = options.name;
169
+ if (options.description)
170
+ updateData.description = options.description;
171
+ if (options.status) {
172
+ const validStatuses = ["planned", "in_progress", "completed"];
173
+ if (!validStatuses.includes(options.status)) {
174
+ console.error(`Error: Invalid status "${options.status}". Must be one of: ${validStatuses.join(", ")}`);
175
+ process.exit(1);
176
+ }
177
+ updateData.status = options.status;
178
+ }
179
+ if (options.order !== undefined) {
180
+ const order = parseInt(options.order, 10);
181
+ if (isNaN(order) || order < 0) {
182
+ console.error("Error: Order must be a non-negative integer");
183
+ process.exit(1);
184
+ }
185
+ updateData.order = order;
186
+ }
187
+ if (Object.keys(updateData).length === 1) {
188
+ console.error("Error: No update options provided. Use -n/--name, -d/--description, --status, or --order");
189
+ process.exit(1);
190
+ }
191
+ try {
192
+ const res = await fetch(`${config.apiUrl}/api/roadmaps/${roadmapId}/phases`, {
193
+ method: "PATCH",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ ...(config.apiKey ? { "x-api-key": config.apiKey } : {}),
197
+ },
198
+ body: JSON.stringify(updateData),
199
+ });
200
+ if (!res.ok) {
201
+ if (res.status === 404) {
202
+ console.error(`Error: Roadmap or phase not found`);
203
+ }
204
+ else {
205
+ const errorBody = await res.json().catch(() => ({}));
206
+ console.error(`Error: API returned ${res.status}`, errorBody.error || "");
207
+ }
208
+ process.exit(1);
209
+ }
210
+ const roadmap = await res.json();
211
+ const updatedPhase = roadmap.phases.find((p) => p.id === phaseId);
212
+ console.log(`✓ Phase updated successfully`);
213
+ if (updatedPhase) {
214
+ console.log(` Name: ${updatedPhase.name}`);
215
+ console.log(` Status: ${updatedPhase.status}`);
216
+ console.log(` Order: ${updatedPhase.order}`);
217
+ }
218
+ }
219
+ catch (error) {
220
+ console.error("Error updating phase:", error);
221
+ process.exit(1);
222
+ }
223
+ });
224
+ // husky roadmap delete-phase <roadmapId> <phaseId>
225
+ roadmapCommand
226
+ .command("delete-phase <roadmapId> <phaseId>")
227
+ .description("Delete a phase from a roadmap (including all its features)")
228
+ .option("--force", "Skip confirmation")
229
+ .action(async (roadmapId, phaseId, options) => {
230
+ const config = ensureConfig();
231
+ if (!options.force) {
232
+ console.log("Warning: This will permanently delete the phase and all its features.");
233
+ console.log("Use --force to confirm deletion.");
234
+ process.exit(1);
235
+ }
236
+ try {
237
+ const url = new URL(`/api/roadmaps/${roadmapId}/phases`, config.apiUrl);
238
+ url.searchParams.set("phaseId", phaseId);
239
+ const res = await fetch(url.toString(), {
240
+ method: "DELETE",
241
+ headers: config.apiKey ? { "x-api-key": config.apiKey } : {},
242
+ });
243
+ if (!res.ok) {
244
+ if (res.status === 404) {
245
+ console.error(`Error: Roadmap or phase not found`);
246
+ }
247
+ else {
248
+ const errorBody = await res.json().catch(() => ({}));
249
+ console.error(`Error: API returned ${res.status}`, errorBody.error || "");
250
+ }
251
+ process.exit(1);
252
+ }
253
+ const roadmap = await res.json();
254
+ console.log(`✓ Phase deleted`);
255
+ console.log(` Remaining phases: ${roadmap.phases.length}`);
256
+ }
257
+ catch (error) {
258
+ console.error("Error deleting phase:", error);
259
+ process.exit(1);
260
+ }
261
+ });
155
262
  // husky roadmap add-feature <roadmapId> <phaseId> <title>
156
263
  roadmapCommand
157
264
  .command("add-feature <roadmapId> <phaseId> <title>")
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
3
  import * as fs from "fs";
4
4
  import * as readline from "readline";
5
+ import { paginateList, printPaginated } from "../lib/pagination.js";
5
6
  export const taskCommand = new Command("task")
6
7
  .description("Manage tasks");
7
8
  // Helper: Get task ID from --id flag or HUSKY_TASK_ID env var
@@ -40,6 +41,10 @@ taskCommand
40
41
  .command("list")
41
42
  .description("List all tasks")
42
43
  .option("-s, --status <status>", "Filter by status")
44
+ .option("-p, --page <num>", "Page number (starts at 1)", "1")
45
+ .option("-n, --per-page <num>", "Items per page (default: all)")
46
+ .option("-i, --interactive", "Interactive pagination with arrow keys")
47
+ .option("--json", "Output as JSON")
43
48
  .action(async (options) => {
44
49
  const config = getConfig();
45
50
  if (!config.apiUrl) {
@@ -58,6 +63,49 @@ taskCommand
58
63
  throw new Error(`API error: ${res.status}`);
59
64
  }
60
65
  const tasks = await res.json();
66
+ // JSON output
67
+ if (options.json) {
68
+ console.log(JSON.stringify(tasks, null, 2));
69
+ return;
70
+ }
71
+ // Interactive pagination mode
72
+ if (options.interactive) {
73
+ await paginateList({
74
+ items: tasks,
75
+ pageSize: 10,
76
+ title: "Tasks",
77
+ emptyMessage: "No tasks found.",
78
+ renderItem: (task) => {
79
+ const statusIcon = task.status === "done" ? "✓" : task.status === "in_progress" ? "▶" : "○";
80
+ const priorityIcon = task.priority === "urgent" ? "🔴" : task.priority === "high" ? "🟠" : "";
81
+ return ` ${statusIcon} ${task.id.slice(0, 8)} │ ${task.title.slice(0, 40).padEnd(40)} ${priorityIcon}`;
82
+ },
83
+ selectableItems: true,
84
+ onSelect: async (task) => {
85
+ console.clear();
86
+ console.log(`\n Task: ${task.title}`);
87
+ console.log(" " + "─".repeat(50));
88
+ console.log(` ID: ${task.id}`);
89
+ console.log(` Status: ${task.status}`);
90
+ console.log(` Priority: ${task.priority}`);
91
+ if (task.agent)
92
+ console.log(` Agent: ${task.agent}`);
93
+ console.log("");
94
+ },
95
+ });
96
+ return;
97
+ }
98
+ // Simple pagination mode
99
+ if (options.perPage) {
100
+ const pageNum = parseInt(options.page, 10) - 1;
101
+ const pageSize = parseInt(options.perPage, 10);
102
+ printPaginated(tasks, pageNum, pageSize, (task) => {
103
+ const statusIcon = task.status === "done" ? "✓" : task.status === "in_progress" ? "▶" : "○";
104
+ return ` ${statusIcon} ${task.id.slice(0, 8)} │ ${task.title}`;
105
+ }, "Tasks");
106
+ return;
107
+ }
108
+ // Default: grouped by status (original behavior)
61
109
  printTasks(tasks);
62
110
  }
63
111
  catch (error) {
@@ -1,5 +1,6 @@
1
1
  import { Command } from "commander";
2
2
  import { getConfig } from "./config.js";
3
+ import { paginateList, printPaginated } from "../lib/pagination.js";
3
4
  export const workflowCommand = new Command("workflow")
4
5
  .description("Manage workflows and workflow steps");
5
6
  // Helper: Ensure API is configured
@@ -11,12 +12,28 @@ function ensureConfig() {
11
12
  }
12
13
  return config;
13
14
  }
15
+ // Status icons
16
+ const STATUS_ICONS = {
17
+ draft: "📝",
18
+ active: "▶️",
19
+ paused: "⏸️",
20
+ archived: "📦",
21
+ };
22
+ // Action labels
23
+ const ACTION_LABELS = {
24
+ manual: "Manual",
25
+ semi_automated: "Semi-Auto",
26
+ fully_automated: "Auto",
27
+ };
14
28
  // husky workflow list
15
29
  workflowCommand
16
30
  .command("list")
17
31
  .description("List all workflows")
18
32
  .option("--value-stream <valueStream>", "Filter by value stream")
19
33
  .option("--json", "Output as JSON")
34
+ .option("-p, --page <num>", "Page number (starts at 1)", "1")
35
+ .option("-n, --per-page <num>", "Items per page (default: all)")
36
+ .option("-i, --interactive", "Interactive pagination with arrow keys")
20
37
  .action(async (options) => {
21
38
  const config = ensureConfig();
22
39
  try {
@@ -33,10 +50,49 @@ workflowCommand
33
50
  const workflows = await res.json();
34
51
  if (options.json) {
35
52
  console.log(JSON.stringify(workflows, null, 2));
36
- }
37
- else {
38
- printWorkflows(workflows);
39
- }
53
+ return;
54
+ }
55
+ // Interactive pagination mode
56
+ if (options.interactive) {
57
+ await paginateList({
58
+ items: workflows,
59
+ pageSize: 10,
60
+ title: "Workflows",
61
+ emptyMessage: "No workflows found.",
62
+ renderItem: (wf) => {
63
+ const icon = STATUS_ICONS[wf.status] || "○";
64
+ const action = ACTION_LABELS[wf.action] || wf.action;
65
+ return ` ${icon} ${wf.name.slice(0, 35).padEnd(35)} │ ${action}`;
66
+ },
67
+ selectableItems: true,
68
+ onSelect: async (wf) => {
69
+ console.clear();
70
+ console.log(`\n Workflow: ${wf.name}`);
71
+ console.log(" " + "─".repeat(50));
72
+ console.log(` ID: ${wf.id}`);
73
+ console.log(` Status: ${wf.status}`);
74
+ console.log(` Action: ${ACTION_LABELS[wf.action]}`);
75
+ console.log(` ValueStream: ${wf.valueStream}`);
76
+ console.log(` Autonomy: ${wf.autonomyWeight}%`);
77
+ if (wf.description)
78
+ console.log(` Desc: ${wf.description}`);
79
+ console.log("");
80
+ },
81
+ });
82
+ return;
83
+ }
84
+ // Simple pagination mode
85
+ if (options.perPage) {
86
+ const pageNum = parseInt(options.page, 10) - 1;
87
+ const pageSize = parseInt(options.perPage, 10);
88
+ printPaginated(workflows, pageNum, pageSize, (wf) => {
89
+ const icon = STATUS_ICONS[wf.status] || "○";
90
+ return ` ${icon} ${wf.id.slice(0, 8)} │ ${wf.name}`;
91
+ }, "Workflows");
92
+ return;
93
+ }
94
+ // Default list
95
+ printWorkflows(workflows);
40
96
  }
41
97
  catch (error) {
42
98
  console.error("Error fetching workflows:", error);
@@ -91,9 +147,9 @@ workflowCommand
91
147
  .command("create <name>")
92
148
  .description("Create a new workflow")
93
149
  .option("-d, --description <description>", "Workflow description")
94
- .option("--value-stream <valueStream>", "Value stream", "general")
95
- .option("--status <status>", "Workflow status (draft, active, paused, archived)", "draft")
96
- .option("--action <action>", "Action type (manual, semi_automated, fully_automated)", "manual")
150
+ .option("--value-stream <valueStream>", "Value stream (order_to_delivery, procure_to_pay, returns_management, product_lifecycle, customer_service, finance_admin)", "customer_service")
151
+ .option("--status <status>", "Workflow status (owner_only, approval_needed, supervised, delegated, automated)", "owner_only")
152
+ .option("--action <action>", "Action type (automate, hire, document, fix, ok)", "document")
97
153
  .option("--json", "Output as JSON")
98
154
  .action(async (name, options) => {
99
155
  const config = ensureConfig();
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ const program = new Command();
23
23
  program
24
24
  .name("husky")
25
25
  .description("CLI for Huskyv0 Task Orchestration with Claude Agent")
26
- .version("0.5.2");
26
+ .version("0.6.0");
27
27
  program.addCommand(taskCommand);
28
28
  program.addCommand(configCommand);
29
29
  program.addCommand(agentCommand);
@@ -0,0 +1,36 @@
1
+ export interface PaginationOptions<T> {
2
+ items: T[];
3
+ pageSize?: number;
4
+ renderItem: (item: T, index: number) => string;
5
+ title?: string;
6
+ emptyMessage?: string;
7
+ selectableItems?: boolean;
8
+ onSelect?: (item: T) => Promise<void>;
9
+ }
10
+ export interface PaginationResult<T> {
11
+ selectedItem: T | null;
12
+ action: "select" | "exit";
13
+ }
14
+ /**
15
+ * Interactive paginated list with arrow key navigation
16
+ *
17
+ * @example
18
+ * await paginateList({
19
+ * items: tasks,
20
+ * pageSize: 10,
21
+ * renderItem: (t, i) => `${t.id} - ${t.title}`,
22
+ * title: "Tasks",
23
+ * selectableItems: true,
24
+ * onSelect: async (task) => console.log(`Selected: ${task.title}`)
25
+ * });
26
+ */
27
+ export declare function paginateList<T>(options: PaginationOptions<T>): Promise<PaginationResult<T>>;
28
+ /**
29
+ * Simple paginated display without interactivity
30
+ * Useful for CLI commands that just want to show paginated output
31
+ */
32
+ export declare function printPaginated<T>(items: T[], page: number, pageSize: number, renderItem: (item: T, index: number) => string, title?: string): {
33
+ hasMore: boolean;
34
+ totalPages: number;
35
+ currentPage: number;
36
+ };
@@ -0,0 +1,180 @@
1
+ import { select } from "@inquirer/prompts";
2
+ const NAVIGATION_SEPARATOR = "──────────────";
3
+ /**
4
+ * Interactive paginated list with arrow key navigation
5
+ *
6
+ * @example
7
+ * await paginateList({
8
+ * items: tasks,
9
+ * pageSize: 10,
10
+ * renderItem: (t, i) => `${t.id} - ${t.title}`,
11
+ * title: "Tasks",
12
+ * selectableItems: true,
13
+ * onSelect: async (task) => console.log(`Selected: ${task.title}`)
14
+ * });
15
+ */
16
+ export async function paginateList(options) {
17
+ const { items, pageSize = 10, renderItem, title = "Items", emptyMessage = "No items found.", selectableItems = false, onSelect, } = options;
18
+ if (items.length === 0) {
19
+ console.log(`\n ${emptyMessage}`);
20
+ return { selectedItem: null, action: "exit" };
21
+ }
22
+ const totalPages = Math.ceil(items.length / pageSize);
23
+ let currentPage = 0;
24
+ while (true) {
25
+ const startIndex = currentPage * pageSize;
26
+ const endIndex = Math.min(startIndex + pageSize, items.length);
27
+ const pageItems = items.slice(startIndex, endIndex);
28
+ // Clear screen and show header
29
+ console.clear();
30
+ console.log(`\n ${title} (${items.length} total)`);
31
+ console.log(` Page ${currentPage + 1} of ${totalPages}`);
32
+ console.log(" " + "─".repeat(50));
33
+ // Build choices for current page
34
+ const choices = [];
35
+ // Add page items
36
+ pageItems.forEach((item, idx) => {
37
+ const globalIndex = startIndex + idx;
38
+ const displayText = renderItem(item, globalIndex);
39
+ choices.push({
40
+ name: displayText,
41
+ value: selectableItems ? `item:${globalIndex}` : `view:${globalIndex}`,
42
+ description: selectableItems ? "Select this item" : undefined,
43
+ });
44
+ });
45
+ // Add separator
46
+ choices.push({
47
+ name: NAVIGATION_SEPARATOR,
48
+ value: "separator",
49
+ description: "",
50
+ });
51
+ // Navigation options
52
+ const navOptions = [];
53
+ if (currentPage > 0) {
54
+ navOptions.push({
55
+ name: "← Previous Page",
56
+ value: "prev",
57
+ description: `Go to page ${currentPage}`,
58
+ });
59
+ }
60
+ if (currentPage < totalPages - 1) {
61
+ navOptions.push({
62
+ name: "→ Next Page",
63
+ value: "next",
64
+ description: `Go to page ${currentPage + 2}`,
65
+ });
66
+ }
67
+ if (totalPages > 2) {
68
+ navOptions.push({
69
+ name: "⇢ Jump to Page...",
70
+ value: "jump",
71
+ description: `Go to a specific page (1-${totalPages})`,
72
+ });
73
+ }
74
+ navOptions.push({
75
+ name: "✕ Exit",
76
+ value: "exit",
77
+ description: "Return to previous menu",
78
+ });
79
+ choices.push(...navOptions);
80
+ try {
81
+ const answer = await select({
82
+ message: "Navigate with ↑↓, select with Enter:",
83
+ choices,
84
+ pageSize: pageSize + 5, // Room for navigation
85
+ });
86
+ if (answer === "separator") {
87
+ // User selected separator, ignore and continue
88
+ continue;
89
+ }
90
+ if (answer === "prev") {
91
+ currentPage = Math.max(0, currentPage - 1);
92
+ continue;
93
+ }
94
+ if (answer === "next") {
95
+ currentPage = Math.min(totalPages - 1, currentPage + 1);
96
+ continue;
97
+ }
98
+ if (answer === "jump") {
99
+ const jumpChoice = await selectPageNumber(totalPages, currentPage);
100
+ if (jumpChoice !== null) {
101
+ currentPage = jumpChoice;
102
+ }
103
+ continue;
104
+ }
105
+ if (answer === "exit") {
106
+ return { selectedItem: null, action: "exit" };
107
+ }
108
+ // Handle item selection
109
+ if (answer.startsWith("item:") || answer.startsWith("view:")) {
110
+ const index = parseInt(answer.split(":")[1], 10);
111
+ const selectedItem = items[index];
112
+ if (selectableItems && onSelect && selectedItem) {
113
+ await onSelect(selectedItem);
114
+ }
115
+ return { selectedItem, action: "select" };
116
+ }
117
+ }
118
+ catch (error) {
119
+ // User pressed Ctrl+C or escape
120
+ return { selectedItem: null, action: "exit" };
121
+ }
122
+ }
123
+ }
124
+ /**
125
+ * Helper to select a page number
126
+ */
127
+ async function selectPageNumber(totalPages, currentPage) {
128
+ const choices = [];
129
+ for (let i = 0; i < totalPages; i++) {
130
+ choices.push({
131
+ name: `Page ${i + 1}${i === currentPage ? " (current)" : ""}`,
132
+ value: i.toString(),
133
+ });
134
+ }
135
+ choices.push({
136
+ name: "Cancel",
137
+ value: "cancel",
138
+ });
139
+ try {
140
+ const answer = await select({
141
+ message: "Select page:",
142
+ choices,
143
+ pageSize: Math.min(totalPages + 1, 15),
144
+ });
145
+ if (answer === "cancel") {
146
+ return null;
147
+ }
148
+ return parseInt(answer, 10);
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ /**
155
+ * Simple paginated display without interactivity
156
+ * Useful for CLI commands that just want to show paginated output
157
+ */
158
+ export function printPaginated(items, page, pageSize, renderItem, title) {
159
+ const totalPages = Math.ceil(items.length / pageSize);
160
+ const currentPage = Math.min(Math.max(0, page), totalPages - 1);
161
+ const startIndex = currentPage * pageSize;
162
+ const endIndex = Math.min(startIndex + pageSize, items.length);
163
+ const pageItems = items.slice(startIndex, endIndex);
164
+ if (title) {
165
+ console.log(`\n ${title}`);
166
+ console.log(" " + "─".repeat(50));
167
+ }
168
+ pageItems.forEach((item, idx) => {
169
+ console.log(renderItem(item, startIndex + idx));
170
+ });
171
+ if (totalPages > 1) {
172
+ console.log("");
173
+ console.log(` Page ${currentPage + 1} of ${totalPages} (${items.length} total)`);
174
+ }
175
+ return {
176
+ hasMore: currentPage < totalPages - 1,
177
+ totalPages,
178
+ currentPage,
179
+ };
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simonfestl/husky-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "CLI for Huskyv0 Task Orchestration with Claude Agent SDK",
5
5
  "type": "module",
6
6
  "bin": {