@ondrej-svec/hog 1.4.0 → 1.5.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
@@ -9,7 +9,7 @@ Your personal command deck — a unified task dashboard for GitHub Projects and
9
9
 
10
10
  ```sh
11
11
  npm install -g @ondrej-svec/hog
12
- hog init # interactive setup wizard
12
+ hog init # interactive setup wizard
13
13
  hog board --live
14
14
  ```
15
15
 
@@ -17,18 +17,22 @@ Requires **Node.js 22+** and the [GitHub CLI](https://cli.github.com/) (`gh auth
17
17
 
18
18
  ## Features
19
19
 
20
- **Unified Dashboard** — See GitHub issues from multiple repos and TickTick tasks in one view. Filter by repo, assignee, or backlog status.
20
+ **Unified Dashboard** — GitHub issues from multiple repos and TickTick tasks in one view. Filter by repo, assignee, or backlog status.
21
21
 
22
22
  **Interactive TUI** — Vim-style navigation (`j`/`k`), section collapsing, search (`/`), multi-select with bulk actions, and a detail panel on wide terminals.
23
23
 
24
- **Issue Actions** — Pick up issues (`p`), assign/unassign (`a`/`u`), change status (`m`), comment (`c`), create issues (`n`) — all without leaving the terminal.
24
+ **Issue Actions** — Pick up issues (`p`), assign/unassign (`a`/`u`), change status (`m`), comment (`c`), create issues (`n`), add/remove labels (`l`) — all without leaving the terminal.
25
+
26
+ **Natural Language Issue Creation** — Press `I` and type `fix login bug #backend @alice due friday`. hog extracts the title, labels, assignee, and due date automatically. Optional LLM enhancement via OpenRouter.
27
+
28
+ **Multi-Line Comments** — Press `ctrl+e` in the comment overlay to open your `$EDITOR` (vim, nano, VS Code, etc.) for longer notes.
29
+
30
+ **Copy Link** — Press `y` to copy the selected issue's URL to your clipboard.
25
31
 
26
32
  **Focus Mode** — Built-in Pomodoro timer (`f`). Lock onto an issue and focus for 25 minutes (configurable).
27
33
 
28
34
  **Auto-Refresh** — Background refresh with age indicators (green/yellow/red) and failure tracking. Manual refresh with `r`.
29
35
 
30
- **Toast Notifications** — Every async operation shows clear feedback. Errors persist with retry hints.
31
-
32
36
  **Board Profiles** — Multiple board configurations for different contexts (work, personal, etc.).
33
37
 
34
38
  **TickTick Optional** — Works with just GitHub. Enable TickTick integration when you want it.
@@ -40,20 +44,94 @@ Requires **Node.js 22+** and the [GitHub CLI](https://cli.github.com/) (`gh auth
40
44
  | Key | Action |
41
45
  |-----|--------|
42
46
  | `j` / `k` | Navigate down / up |
47
+ | `↓` / `↑` | Navigate down / up |
43
48
  | `Tab` / `Shift+Tab` | Next / previous section |
44
- | `Enter` | Open in browser (item) or toggle (section) |
45
- | `Space` | Toggle section or multi-select |
49
+ | `Space` | Toggle section (on header) or enter multi-select (on issue) |
50
+ | `Enter` | Open issue in browser (item) or toggle collapse (section) |
46
51
  | `/` | Search |
52
+ | `n` | Create issue (form wizard) |
53
+ | `I` | Create issue from natural language |
47
54
  | `p` | Pick issue (assign + sync to TickTick) |
48
55
  | `a` / `u` | Assign / unassign |
49
- | `m` | Change status |
50
- | `c` | Comment |
51
- | `n` | Create issue |
52
- | `f` | Focus mode (Pomodoro) |
53
- | `r` | Refresh |
54
- | `?` | Help |
56
+ | `m` | Change project status |
57
+ | `l` | Add / remove labels |
58
+ | `c` | Add comment |
59
+ | `ctrl+e` | Open `$EDITOR` for multi-line comment |
60
+ | `y` | Copy issue URL to clipboard |
61
+ | `f` | Focus mode (Pomodoro timer) |
62
+ | `C` | Collapse all sections |
63
+ | `r` / `R` | Refresh |
64
+ | `?` | Toggle help |
55
65
  | `q` | Quit |
56
66
 
67
+ ### Multi-Select
68
+
69
+ Press `Space` on any issue to enter multi-select mode, then:
70
+
71
+ | Key | Action |
72
+ |-----|--------|
73
+ | `Space` | Toggle item selection |
74
+ | `Enter` / `m` | Open bulk action menu |
75
+ | `Escape` | Clear selection and exit multi-select |
76
+
77
+ ## Natural Language Issue Creation
78
+
79
+ Press `I` on the board to open the NL input. Type a description in plain English:
80
+
81
+ ```
82
+ fix auth timeout on mobile #backend #bug @alice due friday
83
+ ```
84
+
85
+ hog extracts:
86
+ - **Title** — `fix auth timeout on mobile`
87
+ - **Labels** — `backend`, `bug` (validated against repo labels)
88
+ - **Assignee** — `alice`
89
+ - **Due date** — parsed from `due friday`, `due end of month`, `due 2026-03-01`, etc.
90
+
91
+ A live preview shows the parsed fields before you confirm with `Enter`.
92
+
93
+ ### Heuristic Tokens
94
+
95
+ These are extracted without any API key:
96
+
97
+ | Token | Example | Extracts |
98
+ |-------|---------|---------|
99
+ | `#word` | `#backend` | label |
100
+ | `@user` | `@alice` | assignee (`@me` → your GitHub login) |
101
+ | `due <expr>` | `due friday` | due date (chrono-node) |
102
+
103
+ Everything else becomes the title.
104
+
105
+ ### LLM Enhancement (optional)
106
+
107
+ With an [OpenRouter](https://openrouter.ai) API key, hog sends ambiguous input to an LLM for richer title cleanup and inference. The heuristic tokens still take priority — LLM only fills gaps.
108
+
109
+ Set up during `hog init`, or any time with:
110
+
111
+ ```sh
112
+ hog config ai:set-key sk-or-... # store key
113
+ hog config ai:clear-key # remove key
114
+ hog config ai:status # show active source
115
+ ```
116
+
117
+ Or set an environment variable (takes priority over the stored key):
118
+
119
+ ```sh
120
+ export OPENROUTER_API_KEY=sk-or-...
121
+ # or
122
+ export ANTHROPIC_API_KEY=sk-ant-...
123
+ ```
124
+
125
+ ### Agent-Native: `hog issue create`
126
+
127
+ Create issues non-interactively from scripts or AI agents:
128
+
129
+ ```sh
130
+ hog issue create "fix login bug #backend @alice due friday" --repo owner/repo
131
+ hog issue create "add dark mode" --repo owner/repo --dry-run # preview only
132
+ hog issue create "add dark mode" --repo owner/repo --json # structured output
133
+ ```
134
+
57
135
  ## Commands
58
136
 
59
137
  ### `hog board`
@@ -69,6 +147,15 @@ hog board --repo myrepo --json # filter by repo
69
147
  hog board --profile work --live # use a named profile
70
148
  ```
71
149
 
150
+ ### `hog issue`
151
+
152
+ Manage issues from the command line.
153
+
154
+ ```sh
155
+ hog issue create "fix login bug #backend due friday" --repo owner/repo
156
+ hog issue create "add dark mode" --repo owner/repo --dry-run
157
+ ```
158
+
72
159
  ### `hog pick`
73
160
 
74
161
  Assign a GitHub issue to yourself and create a linked TickTick task.
@@ -82,8 +169,8 @@ hog pick myrepo/145
82
169
  Manage TickTick tasks directly.
83
170
 
84
171
  ```sh
85
- hog task list # list tasks
86
- hog task add "Ship the feature" # create task
172
+ hog task list
173
+ hog task add "Ship the feature"
87
174
  hog task add "Bug fix" -p high -t "urgent"
88
175
  hog task complete <taskId>
89
176
  hog task update <taskId> --title "New title" -p medium
@@ -97,22 +184,31 @@ hog task use-project <projectId> # set default project
97
184
  View and manage configuration.
98
185
 
99
186
  ```sh
100
- hog config show # show full config
101
- hog config repos # list tracked repos
187
+ hog config show
188
+
189
+ # Repos
190
+ hog config repos
102
191
  hog config repos:add owner/repo --project-number 1 --status-field-id PVTSSF_xxx --completion-type closeIssue
103
192
  hog config repos:rm reponame
104
193
 
105
- hog config ticktick:enable # enable TickTick integration
106
- hog config ticktick:disable # disable TickTick integration
194
+ # TickTick
195
+ hog config ticktick:enable
196
+ hog config ticktick:disable
197
+
198
+ # AI / natural language issue creation
199
+ hog config ai:set-key sk-or-... # store OpenRouter key
200
+ hog config ai:clear-key # remove stored key
201
+ hog config ai:status # show active source and provider
107
202
 
108
- hog config profile:create work # create profile from current config
203
+ # Profiles
204
+ hog config profile:create work
109
205
  hog config profile:delete work
110
- hog config profile:default work # set default profile
206
+ hog config profile:default work
111
207
  ```
112
208
 
113
209
  ### `hog init`
114
210
 
115
- Interactive setup wizard. Detects your GitHub user, lets you pick repos, and configures everything.
211
+ Interactive setup wizard. Detects your GitHub user, picks repos, configures projects, and optionally sets up an OpenRouter key for AI-enhanced issue creation.
116
212
 
117
213
  ```sh
118
214
  hog init # interactive setup
@@ -131,7 +227,7 @@ hog sync status # show sync mappings
131
227
 
132
228
  ## Configuration
133
229
 
134
- Config lives at `~/.config/hog/config.json`. Created by `hog init` or manually.
230
+ Config lives at `~/.config/hog/config.json`. Created by `hog init` or edited manually.
135
231
 
136
232
  ```jsonc
137
233
  {
@@ -150,25 +246,27 @@ Config lives at `~/.config/hog/config.json`. Created by `hog init` or manually.
150
246
  "refreshInterval": 60, // seconds (min: 10)
151
247
  "backlogLimit": 20,
152
248
  "assignee": "your-github-username",
153
- "focusDuration": 1500 // seconds (25 min)
249
+ "focusDuration": 1500 // seconds (25 min default)
154
250
  },
155
251
  "ticktick": {
156
252
  "enabled": true // set false to use without TickTick
157
253
  },
158
- "profiles": {}, // named board profiles
159
- "defaultProfile": "" // profile to use by default
254
+ "profiles": {},
255
+ "defaultProfile": ""
160
256
  }
161
257
  ```
162
258
 
259
+ Credentials (TickTick OAuth token, OpenRouter API key) are stored separately in `~/.config/hog/auth.json` with `0600` permissions.
260
+
163
261
  ### Status Groups
164
262
 
165
- By default, hog auto-detects status columns from your GitHub Project. Override per-repo with `statusGroups`:
263
+ By default, hog auto-detects status columns from your GitHub Project. Override per-repo:
166
264
 
167
265
  ```json
168
266
  "statusGroups": ["In Progress", "In Review", "Todo,Backlog"]
169
267
  ```
170
268
 
171
- Each entry is a section. Comma-separated values merge into one section (header = first value). Terminal statuses (Done, Shipped, Closed, etc.) are always hidden.
269
+ Each entry is a board section. Comma-separated values merge into one section (header = first value). Terminal statuses (Done, Shipped, Closed, etc.) are always hidden.
172
270
 
173
271
  ### Profiles
174
272
 
@@ -185,6 +283,7 @@ hog board --profile personal --live
185
283
  - **Node.js 22+**
186
284
  - **GitHub CLI** (`gh`) — authenticated via `gh auth login`
187
285
  - **TickTick account** — optional, for task sync
286
+ - **OpenRouter API key** — optional, for AI-enhanced issue creation (`hog config ai:set-key`)
188
287
 
189
288
  ## License
190
289
 
package/dist/cli.js CHANGED
@@ -1222,8 +1222,8 @@ function useActions({
1222
1222
  });
1223
1223
  }, [toast, refresh]);
1224
1224
  const handleCreateIssue = useCallback(
1225
- async (repo, title, labels) => {
1226
- const args = ["issue", "create", "--repo", repo, "--title", title];
1225
+ async (repo, title, body, labels) => {
1226
+ const args = ["issue", "create", "--repo", repo, "--title", title, "--body", body];
1227
1227
  if (labels && labels.length > 0) {
1228
1228
  for (const label of labels) {
1229
1229
  args.push("--label", label);
@@ -2778,13 +2778,13 @@ function CreateIssueForm({
2778
2778
  currentLabels: [],
2779
2779
  labelCache: labelCache ?? {},
2780
2780
  onConfirm: (addLabels) => {
2781
- onSubmit(selectedRepo.name, title, addLabels.length > 0 ? addLabels : void 0);
2781
+ onSubmit(selectedRepo.name, title, "", addLabels.length > 0 ? addLabels : void 0);
2782
2782
  },
2783
2783
  onCancel: () => {
2784
- onSubmit(selectedRepo.name, title);
2784
+ onSubmit(selectedRepo.name, title, "");
2785
2785
  },
2786
2786
  onError: () => {
2787
- onSubmit(selectedRepo.name, title);
2787
+ onSubmit(selectedRepo.name, title, "");
2788
2788
  }
2789
2789
  }
2790
2790
  )
@@ -2820,7 +2820,7 @@ function CreateIssueForm({
2820
2820
  setTitle(trimmed);
2821
2821
  setField("labels");
2822
2822
  } else {
2823
- onSubmit(selectedRepo.name, trimmed);
2823
+ onSubmit(selectedRepo.name, trimmed, "");
2824
2824
  }
2825
2825
  }
2826
2826
  }
@@ -3018,8 +3018,12 @@ var init_help_overlay = __esm({
3018
3018
  });
3019
3019
 
3020
3020
  // src/board/components/nl-create-overlay.tsx
3021
+ import { spawnSync as spawnSync2 } from "child_process";
3022
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
3023
+ import { tmpdir as tmpdir2 } from "os";
3024
+ import { join as join4 } from "path";
3021
3025
  import { Spinner as Spinner2, TextInput as TextInput3 } from "@inkjs/ui";
3022
- import { Box as Box9, Text as Text9, useInput as useInput9 } from "ink";
3026
+ import { Box as Box9, Text as Text9, useInput as useInput9, useStdin as useStdin2 } from "ink";
3023
3027
  import { useCallback as useCallback9, useEffect as useEffect5, useRef as useRef9, useState as useState9 } from "react";
3024
3028
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3025
3029
  function NlCreateOverlay({
@@ -3028,15 +3032,28 @@ function NlCreateOverlay({
3028
3032
  labelCache,
3029
3033
  onSubmit,
3030
3034
  onCancel,
3035
+ onPauseRefresh,
3036
+ onResumeRefresh,
3031
3037
  onLlmFallback
3032
3038
  }) {
3033
3039
  const [, setInput] = useState9("");
3034
3040
  const [isParsing, setIsParsing] = useState9(false);
3035
3041
  const [parsed, setParsed] = useState9(null);
3036
3042
  const [parseError, setParseError] = useState9(null);
3037
- const [createError, setCreateError] = useState9(null);
3043
+ const [step, setStep] = useState9("input");
3044
+ const [body, setBody] = useState9("");
3045
+ const [editingBody, setEditingBody] = useState9(false);
3038
3046
  const submittedRef = useRef9(false);
3039
3047
  const parseParamsRef = useRef9(null);
3048
+ const onSubmitRef = useRef9(onSubmit);
3049
+ const onCancelRef = useRef9(onCancel);
3050
+ const onPauseRef = useRef9(onPauseRefresh);
3051
+ const onResumeRef = useRef9(onResumeRefresh);
3052
+ onSubmitRef.current = onSubmit;
3053
+ onCancelRef.current = onCancel;
3054
+ onPauseRef.current = onPauseRefresh;
3055
+ onResumeRef.current = onResumeRefresh;
3056
+ const { setRawMode } = useStdin2();
3040
3057
  const defaultRepoIdx = defaultRepoName ? Math.max(
3041
3058
  0,
3042
3059
  repos.findIndex((r) => r.name === defaultRepoName)
@@ -3044,19 +3061,19 @@ function NlCreateOverlay({
3044
3061
  const [repoIdx, setRepoIdx] = useState9(defaultRepoIdx);
3045
3062
  const selectedRepo = repos[repoIdx];
3046
3063
  useInput9((inputChar, key) => {
3047
- if (isParsing) return;
3064
+ if (isParsing || editingBody) return;
3048
3065
  if (key.escape) {
3066
+ if (step === "body") {
3067
+ setStep("input");
3068
+ setParsed((p) => p);
3069
+ return;
3070
+ }
3049
3071
  onCancel();
3050
3072
  return;
3051
3073
  }
3052
- if (parsed) {
3074
+ if (parsed && step === "input") {
3053
3075
  if (key.return) {
3054
- if (submittedRef.current) return;
3055
- submittedRef.current = true;
3056
- if (!selectedRepo) return;
3057
- setCreateError(null);
3058
- const labels = buildLabelList(parsed);
3059
- onSubmit(selectedRepo.name, parsed.title, labels.length > 0 ? labels : void 0);
3076
+ setStep("body");
3060
3077
  return;
3061
3078
  }
3062
3079
  if (inputChar === "r") {
@@ -3064,7 +3081,43 @@ function NlCreateOverlay({
3064
3081
  return;
3065
3082
  }
3066
3083
  }
3084
+ if (step === "body" && inputChar === "") {
3085
+ setEditingBody(true);
3086
+ }
3067
3087
  });
3088
+ useEffect5(() => {
3089
+ if (!editingBody) return;
3090
+ const editorEnv = process.env["VISUAL"] ?? process.env["EDITOR"] ?? "vi";
3091
+ const [cmd, ...extraArgs] = editorEnv.split(" ").filter(Boolean);
3092
+ if (!cmd) {
3093
+ setEditingBody(false);
3094
+ return;
3095
+ }
3096
+ let tmpDir = null;
3097
+ let tmpFile = null;
3098
+ try {
3099
+ onPauseRef.current?.();
3100
+ tmpDir = mkdtempSync2(join4(tmpdir2(), "hog-body-"));
3101
+ tmpFile = join4(tmpDir, "body.md");
3102
+ writeFileSync4(tmpFile, body);
3103
+ const inkInstance = getInkInstance();
3104
+ inkInstance?.clear();
3105
+ setRawMode(false);
3106
+ spawnSync2(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
3107
+ const content = readFileSync4(tmpFile, "utf-8");
3108
+ setRawMode(true);
3109
+ setBody(content.trimEnd());
3110
+ } finally {
3111
+ onResumeRef.current?.();
3112
+ if (tmpFile) {
3113
+ try {
3114
+ rmSync2(tmpDir, { recursive: true, force: true });
3115
+ } catch {
3116
+ }
3117
+ }
3118
+ setEditingBody(false);
3119
+ }
3120
+ }, [editingBody, body, setRawMode]);
3068
3121
  const handleInputSubmit = useCallback9(
3069
3122
  (text) => {
3070
3123
  const trimmed = text.trim();
@@ -3103,6 +3156,45 @@ function NlCreateOverlay({
3103
3156
  /* @__PURE__ */ jsx9(Spinner2, { label: "Parsing..." })
3104
3157
  ] });
3105
3158
  }
3159
+ if (parsed && step === "body") {
3160
+ if (editingBody) {
3161
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3162
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
3163
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "Opening editor for body\u2026" })
3164
+ ] });
3165
+ }
3166
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3167
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "\u2728 Creating Issue" }),
3168
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3169
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Title: " }),
3170
+ /* @__PURE__ */ jsx9(Text9, { children: parsed.title })
3171
+ ] }),
3172
+ /* @__PURE__ */ jsxs9(Box9, { children: [
3173
+ /* @__PURE__ */ jsx9(Text9, { color: "cyan", children: "body: " }),
3174
+ /* @__PURE__ */ jsx9(
3175
+ TextInput3,
3176
+ {
3177
+ defaultValue: body,
3178
+ placeholder: "optional description (ctrl+e for editor)",
3179
+ onChange: setBody,
3180
+ onSubmit: (text) => {
3181
+ if (submittedRef.current) return;
3182
+ submittedRef.current = true;
3183
+ if (!selectedRepo) return;
3184
+ const labels = buildLabelList(parsed);
3185
+ onSubmitRef.current(
3186
+ selectedRepo.name,
3187
+ parsed.title,
3188
+ text.trim(),
3189
+ labels.length > 0 ? labels : void 0
3190
+ );
3191
+ }
3192
+ }
3193
+ )
3194
+ ] }),
3195
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create ctrl+e:editor Esc:back" })
3196
+ ] });
3197
+ }
3106
3198
  if (parsed) {
3107
3199
  const labels = buildLabelList(parsed);
3108
3200
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
@@ -3132,8 +3224,7 @@ function NlCreateOverlay({
3132
3224
  /* @__PURE__ */ jsx9(Text9, { children: formatDue(parsed.dueDate) })
3133
3225
  ] }) : null,
3134
3226
  parsed.dueDate && selectedRepo && !hasDueLabelInCache(labelCache, selectedRepo.name) ? /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "\u26A0 No due:* label in this repo \u2014 will try to create label on submit" }) : null,
3135
- createError ? /* @__PURE__ */ jsx9(Text9, { color: "red", children: createError }) : null,
3136
- /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:create Esc:cancel" })
3227
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Enter:add body Esc:cancel" })
3137
3228
  ] });
3138
3229
  }
3139
3230
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
@@ -3172,6 +3263,7 @@ var init_nl_create_overlay = __esm({
3172
3263
  "src/board/components/nl-create-overlay.tsx"() {
3173
3264
  "use strict";
3174
3265
  init_ai();
3266
+ init_ink_instance();
3175
3267
  }
3176
3268
  });
3177
3269
 
@@ -3411,6 +3503,8 @@ function OverlayRenderer({
3411
3503
  labelCache,
3412
3504
  onSubmit: onCreateIssue,
3413
3505
  onCancel: onExitOverlay,
3506
+ onPauseRefresh,
3507
+ onResumeRefresh,
3414
3508
  onLlmFallback
3415
3509
  }
3416
3510
  ) : null
@@ -3711,7 +3805,7 @@ var init_toast_container = __esm({
3711
3805
  });
3712
3806
 
3713
3807
  // src/board/components/dashboard.tsx
3714
- import { execFileSync as execFileSync3, spawnSync as spawnSync2 } from "child_process";
3808
+ import { execFileSync as execFileSync3, spawnSync as spawnSync3 } from "child_process";
3715
3809
  import { Spinner as Spinner4 } from "@inkjs/ui";
3716
3810
  import { Box as Box16, Text as Text16, useApp, useStdout } from "ink";
3717
3811
  import { useCallback as useCallback10, useEffect as useEffect6, useMemo as useMemo2, useRef as useRef11, useState as useState11 } from "react";
@@ -4046,8 +4140,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
4046
4140
  const pendingPickRef = useRef11(null);
4047
4141
  const labelCacheRef = useRef11({});
4048
4142
  const handleCreateIssueWithPrompt = useCallback10(
4049
- (repo, title, labels) => {
4050
- actions.handleCreateIssue(repo, title, labels).then((result) => {
4143
+ (repo, title, body, labels) => {
4144
+ actions.handleCreateIssue(repo, title, body, labels).then((result) => {
4051
4145
  if (result) {
4052
4146
  pendingPickRef.current = result;
4053
4147
  ui.enterConfirmPick();
@@ -4212,7 +4306,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
4212
4306
  toast.info(`${label} \u2014 ${found.issue.url}`);
4213
4307
  return;
4214
4308
  }
4215
- const result = spawnSync2(cmd, args, {
4309
+ const result = spawnSync3(cmd, args, {
4216
4310
  input: found.issue.url,
4217
4311
  stdio: ["pipe", "pipe", "pipe"]
4218
4312
  });
@@ -5556,7 +5650,7 @@ function resolveProjectId(projectId) {
5556
5650
  process.exit(1);
5557
5651
  }
5558
5652
  var program = new Command();
5559
- program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.4.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
5653
+ program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.5.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
5560
5654
  const opts = thisCommand.opts();
5561
5655
  if (opts.json) setFormat("json");
5562
5656
  if (opts.human) setFormat("human");
@@ -5993,7 +6087,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
5993
6087
  console.error("[dry-run] Skipping issue creation.");
5994
6088
  return;
5995
6089
  }
5996
- const args = ["issue", "create", "--repo", repo, "--title", parsed.title];
6090
+ const args = ["issue", "create", "--repo", repo, "--title", parsed.title, "--body", ""];
5997
6091
  for (const label of labels) {
5998
6092
  args.push("--label", label);
5999
6093
  }