@ondrej-svec/hog 1.16.2 → 1.18.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/dist/cli.js +350 -21
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js +15 -4
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
14
14
|
import { homedir } from "os";
|
|
15
|
-
import { join } from "path";
|
|
15
|
+
import { isAbsolute, join, normalize } from "path";
|
|
16
16
|
import { z } from "zod";
|
|
17
17
|
function migrateConfig(raw) {
|
|
18
18
|
const version = typeof raw["version"] === "number" ? raw["version"] : 1;
|
|
@@ -147,7 +147,7 @@ function requireAuth() {
|
|
|
147
147
|
}
|
|
148
148
|
return auth;
|
|
149
149
|
}
|
|
150
|
-
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA;
|
|
150
|
+
var CONFIG_DIR, AUTH_FILE, CONFIG_FILE, COMPLETION_ACTION_SCHEMA, REPO_NAME_PATTERN, CLAUDE_START_COMMAND_SCHEMA, REPO_CONFIG_SCHEMA, BOARD_CONFIG_SCHEMA, TICKTICK_CONFIG_SCHEMA, PROFILE_SCHEMA, HOG_CONFIG_SCHEMA;
|
|
151
151
|
var init_config = __esm({
|
|
152
152
|
"src/config.ts"() {
|
|
153
153
|
"use strict";
|
|
@@ -160,6 +160,10 @@ var init_config = __esm({
|
|
|
160
160
|
z.object({ type: z.literal("addLabel"), label: z.string() })
|
|
161
161
|
]);
|
|
162
162
|
REPO_NAME_PATTERN = /^[\w.-]+\/[\w.-]+$/;
|
|
163
|
+
CLAUDE_START_COMMAND_SCHEMA = z.object({
|
|
164
|
+
command: z.string().min(1),
|
|
165
|
+
extraArgs: z.array(z.string())
|
|
166
|
+
});
|
|
163
167
|
REPO_CONFIG_SCHEMA = z.object({
|
|
164
168
|
name: z.string().regex(REPO_NAME_PATTERN, "Must be owner/repo format"),
|
|
165
169
|
shortName: z.string().min(1),
|
|
@@ -167,13 +171,20 @@ var init_config = __esm({
|
|
|
167
171
|
statusFieldId: z.string().min(1),
|
|
168
172
|
dueDateFieldId: z.string().optional(),
|
|
169
173
|
completionAction: COMPLETION_ACTION_SCHEMA,
|
|
170
|
-
statusGroups: z.array(z.string()).optional()
|
|
174
|
+
statusGroups: z.array(z.string()).optional(),
|
|
175
|
+
localPath: z.string().refine((p) => isAbsolute(p), { message: "localPath must be an absolute path" }).refine((p) => normalize(p) === p, {
|
|
176
|
+
message: "localPath must be normalized (no .. segments)"
|
|
177
|
+
}).refine((p) => !p.includes("\0"), { message: "localPath must not contain null bytes" }).optional(),
|
|
178
|
+
claudeStartCommand: CLAUDE_START_COMMAND_SCHEMA.optional()
|
|
171
179
|
});
|
|
172
180
|
BOARD_CONFIG_SCHEMA = z.object({
|
|
173
181
|
refreshInterval: z.number().int().min(10).default(60),
|
|
174
182
|
backlogLimit: z.number().int().min(1).default(20),
|
|
175
183
|
assignee: z.string().min(1),
|
|
176
|
-
focusDuration: z.number().int().min(60).default(1500)
|
|
184
|
+
focusDuration: z.number().int().min(60).default(1500),
|
|
185
|
+
claudeStartCommand: CLAUDE_START_COMMAND_SCHEMA.optional(),
|
|
186
|
+
claudeLaunchMode: z.enum(["auto", "tmux", "terminal"]).optional(),
|
|
187
|
+
claudeTerminalApp: z.enum(["Terminal", "iTerm", "Ghostty", "WezTerm", "Kitty", "Alacritty"]).optional()
|
|
177
188
|
});
|
|
178
189
|
TICKTICK_CONFIG_SCHEMA = z.object({
|
|
179
190
|
enabled: z.boolean().default(true)
|
|
@@ -2070,7 +2081,8 @@ function useKeyboard({
|
|
|
2070
2081
|
handleEnterFuzzyPicker,
|
|
2071
2082
|
handleEnterEditIssue,
|
|
2072
2083
|
handleUndo,
|
|
2073
|
-
handleToggleLog
|
|
2084
|
+
handleToggleLog,
|
|
2085
|
+
handleLaunchClaude
|
|
2074
2086
|
} = actions;
|
|
2075
2087
|
const handleInput = useCallback4(
|
|
2076
2088
|
(input2, key) => {
|
|
@@ -2171,6 +2183,14 @@ function useKeyboard({
|
|
|
2171
2183
|
}
|
|
2172
2184
|
return;
|
|
2173
2185
|
}
|
|
2186
|
+
if (input2 === "C") {
|
|
2187
|
+
handleLaunchClaude();
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
if (input2 === "g") {
|
|
2191
|
+
handleOpen();
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2174
2194
|
}
|
|
2175
2195
|
if (ui.canAct) {
|
|
2176
2196
|
const digit = parseInt(input2, 10);
|
|
@@ -2266,7 +2286,11 @@ function useKeyboard({
|
|
|
2266
2286
|
onStatusEnter();
|
|
2267
2287
|
break;
|
|
2268
2288
|
case 3:
|
|
2269
|
-
|
|
2289
|
+
if (showDetailPanel) {
|
|
2290
|
+
panelFocus.focusPanel(0);
|
|
2291
|
+
} else {
|
|
2292
|
+
ui.enterDetail();
|
|
2293
|
+
}
|
|
2270
2294
|
break;
|
|
2271
2295
|
case 4:
|
|
2272
2296
|
onActivityEnter();
|
|
@@ -2309,6 +2333,7 @@ function useKeyboard({
|
|
|
2309
2333
|
handleEnterEditIssue,
|
|
2310
2334
|
handleUndo,
|
|
2311
2335
|
handleToggleLog,
|
|
2336
|
+
handleLaunchClaude,
|
|
2312
2337
|
showDetailPanel
|
|
2313
2338
|
]
|
|
2314
2339
|
);
|
|
@@ -2834,6 +2859,225 @@ var init_use_ui_state = __esm({
|
|
|
2834
2859
|
}
|
|
2835
2860
|
});
|
|
2836
2861
|
|
|
2862
|
+
// src/board/launch-claude.ts
|
|
2863
|
+
var launch_claude_exports = {};
|
|
2864
|
+
__export(launch_claude_exports, {
|
|
2865
|
+
buildPrompt: () => buildPrompt,
|
|
2866
|
+
launchClaude: () => launchClaude
|
|
2867
|
+
});
|
|
2868
|
+
import { spawn, spawnSync } from "child_process";
|
|
2869
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2870
|
+
function buildPrompt(issue) {
|
|
2871
|
+
return `Issue #${issue.number}: ${issue.title}
|
|
2872
|
+
URL: ${issue.url}`;
|
|
2873
|
+
}
|
|
2874
|
+
function isClaudeInPath() {
|
|
2875
|
+
const result = spawnSync("which", ["claude"], { stdio: "pipe" });
|
|
2876
|
+
return result.status === 0;
|
|
2877
|
+
}
|
|
2878
|
+
function isInTmux() {
|
|
2879
|
+
return !!process.env["TMUX"];
|
|
2880
|
+
}
|
|
2881
|
+
function isInSsh() {
|
|
2882
|
+
return !!(process.env["SSH_CLIENT"] ?? process.env["SSH_TTY"]);
|
|
2883
|
+
}
|
|
2884
|
+
function detectTerminalApp() {
|
|
2885
|
+
return process.env["TERM_PROGRAM"];
|
|
2886
|
+
}
|
|
2887
|
+
function resolveCommand(opts) {
|
|
2888
|
+
if (opts.startCommand) return opts.startCommand;
|
|
2889
|
+
return { command: "claude", extraArgs: [] };
|
|
2890
|
+
}
|
|
2891
|
+
function launchViaTmux(opts) {
|
|
2892
|
+
const { localPath, issue, repoFullName } = opts;
|
|
2893
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
2894
|
+
const prompt = buildPrompt(issue);
|
|
2895
|
+
const windowName = `claude-${issue.number}`;
|
|
2896
|
+
const tmuxArgs = [
|
|
2897
|
+
"new-window",
|
|
2898
|
+
"-d",
|
|
2899
|
+
// don't steal focus from hog board
|
|
2900
|
+
"-c",
|
|
2901
|
+
localPath,
|
|
2902
|
+
"-n",
|
|
2903
|
+
windowName
|
|
2904
|
+
];
|
|
2905
|
+
if (repoFullName) {
|
|
2906
|
+
tmuxArgs.push("-e", `HOG_REPO=${repoFullName}`);
|
|
2907
|
+
}
|
|
2908
|
+
tmuxArgs.push("-e", `HOG_ISSUE=${issue.number}`);
|
|
2909
|
+
tmuxArgs.push(command, ...extraArgs, "--", prompt);
|
|
2910
|
+
const child = spawn("tmux", tmuxArgs, { stdio: "ignore", detached: true });
|
|
2911
|
+
child.unref();
|
|
2912
|
+
return { ok: true, value: void 0 };
|
|
2913
|
+
}
|
|
2914
|
+
function launchViaTerminalApp(terminalApp, opts) {
|
|
2915
|
+
const { localPath, issue } = opts;
|
|
2916
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
2917
|
+
const prompt = buildPrompt(issue);
|
|
2918
|
+
const fullCmd = [command, ...extraArgs, "--", prompt].join(" ");
|
|
2919
|
+
switch (terminalApp) {
|
|
2920
|
+
case "iTerm": {
|
|
2921
|
+
const script = `tell application "iTerm"
|
|
2922
|
+
create window with default profile command "bash -c 'cd ${localPath} && ${fullCmd}'"
|
|
2923
|
+
end tell`;
|
|
2924
|
+
const result = spawnSync("osascript", ["-e", script], { stdio: "ignore" });
|
|
2925
|
+
if (result.status !== 0) {
|
|
2926
|
+
return {
|
|
2927
|
+
ok: false,
|
|
2928
|
+
error: {
|
|
2929
|
+
kind: "terminal-failed",
|
|
2930
|
+
message: `iTerm2 launch failed. Is iTerm2 installed and running?`
|
|
2931
|
+
}
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
return { ok: true, value: void 0 };
|
|
2935
|
+
}
|
|
2936
|
+
case "Terminal": {
|
|
2937
|
+
const child = spawn("open", ["-a", "Terminal", localPath], {
|
|
2938
|
+
stdio: "ignore",
|
|
2939
|
+
detached: true
|
|
2940
|
+
});
|
|
2941
|
+
child.unref();
|
|
2942
|
+
return { ok: true, value: void 0 };
|
|
2943
|
+
}
|
|
2944
|
+
case "Ghostty": {
|
|
2945
|
+
const child = spawn(
|
|
2946
|
+
"open",
|
|
2947
|
+
["-na", "Ghostty", "--args", `--working-directory=${localPath}`],
|
|
2948
|
+
{ stdio: "ignore", detached: true }
|
|
2949
|
+
);
|
|
2950
|
+
child.unref();
|
|
2951
|
+
return { ok: true, value: void 0 };
|
|
2952
|
+
}
|
|
2953
|
+
case "WezTerm": {
|
|
2954
|
+
const child = spawn("wezterm", ["start", "--cwd", localPath], {
|
|
2955
|
+
stdio: "ignore",
|
|
2956
|
+
detached: true
|
|
2957
|
+
});
|
|
2958
|
+
child.unref();
|
|
2959
|
+
return { ok: true, value: void 0 };
|
|
2960
|
+
}
|
|
2961
|
+
case "Kitty": {
|
|
2962
|
+
const child = spawn(
|
|
2963
|
+
"kitty",
|
|
2964
|
+
["--directory", localPath, command, ...extraArgs, "--", prompt],
|
|
2965
|
+
{
|
|
2966
|
+
stdio: "ignore",
|
|
2967
|
+
detached: true
|
|
2968
|
+
}
|
|
2969
|
+
);
|
|
2970
|
+
child.unref();
|
|
2971
|
+
return { ok: true, value: void 0 };
|
|
2972
|
+
}
|
|
2973
|
+
case "Alacritty": {
|
|
2974
|
+
const child = spawn(
|
|
2975
|
+
"alacritty",
|
|
2976
|
+
["--command", "bash", "-c", `cd ${localPath} && ${fullCmd}`],
|
|
2977
|
+
{ stdio: "ignore", detached: true }
|
|
2978
|
+
);
|
|
2979
|
+
child.unref();
|
|
2980
|
+
return { ok: true, value: void 0 };
|
|
2981
|
+
}
|
|
2982
|
+
default:
|
|
2983
|
+
return {
|
|
2984
|
+
ok: false,
|
|
2985
|
+
error: {
|
|
2986
|
+
kind: "terminal-app-not-found",
|
|
2987
|
+
message: `Unknown terminal app: ${terminalApp}`
|
|
2988
|
+
}
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
function launchViaDetectedTerminal(opts) {
|
|
2993
|
+
const { terminalApp } = opts;
|
|
2994
|
+
if (terminalApp) {
|
|
2995
|
+
return launchViaTerminalApp(terminalApp, opts);
|
|
2996
|
+
}
|
|
2997
|
+
const termProgram = detectTerminalApp();
|
|
2998
|
+
if (termProgram === "iTerm.app") {
|
|
2999
|
+
return launchViaTerminalApp("iTerm", opts);
|
|
3000
|
+
}
|
|
3001
|
+
if (termProgram === "Apple_Terminal") {
|
|
3002
|
+
return launchViaTerminalApp("Terminal", opts);
|
|
3003
|
+
}
|
|
3004
|
+
if (termProgram === "WezTerm") {
|
|
3005
|
+
return launchViaTerminalApp("WezTerm", opts);
|
|
3006
|
+
}
|
|
3007
|
+
if (termProgram === "ghostty") {
|
|
3008
|
+
return launchViaTerminalApp("Ghostty", opts);
|
|
3009
|
+
}
|
|
3010
|
+
if (process.env["KITTY_WINDOW_ID"]) {
|
|
3011
|
+
return launchViaTerminalApp("Kitty", opts);
|
|
3012
|
+
}
|
|
3013
|
+
if (process.platform === "darwin") {
|
|
3014
|
+
return launchViaTerminalApp("Terminal", opts);
|
|
3015
|
+
}
|
|
3016
|
+
const { localPath, issue } = opts;
|
|
3017
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
3018
|
+
const prompt = buildPrompt(issue);
|
|
3019
|
+
const child = spawn(
|
|
3020
|
+
"xdg-terminal-exec",
|
|
3021
|
+
["bash", "-c", `cd ${localPath} && ${[command, ...extraArgs, "--", prompt].join(" ")}`],
|
|
3022
|
+
{ stdio: "ignore", detached: true }
|
|
3023
|
+
);
|
|
3024
|
+
child.unref();
|
|
3025
|
+
return { ok: true, value: void 0 };
|
|
3026
|
+
}
|
|
3027
|
+
function launchClaude(opts) {
|
|
3028
|
+
const { localPath, launchMode = "auto" } = opts;
|
|
3029
|
+
if (!existsSync5(localPath)) {
|
|
3030
|
+
return {
|
|
3031
|
+
ok: false,
|
|
3032
|
+
error: {
|
|
3033
|
+
kind: "directory-not-found",
|
|
3034
|
+
message: `Directory not found: ${localPath}. Check localPath config.`
|
|
3035
|
+
}
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
if (!isClaudeInPath()) {
|
|
3039
|
+
return {
|
|
3040
|
+
ok: false,
|
|
3041
|
+
error: {
|
|
3042
|
+
kind: "claude-not-found",
|
|
3043
|
+
message: "claude binary not found in PATH. Install Claude Code first."
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
if (isInSsh() && !isInTmux() && launchMode !== "tmux") {
|
|
3048
|
+
return {
|
|
3049
|
+
ok: false,
|
|
3050
|
+
error: {
|
|
3051
|
+
kind: "ssh-no-tmux",
|
|
3052
|
+
message: "Running over SSH without tmux \u2014 start tmux to enable Claude Code launch."
|
|
3053
|
+
}
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
const useTmux = launchMode === "tmux" || launchMode === "auto" && isInTmux();
|
|
3057
|
+
if (useTmux) {
|
|
3058
|
+
const result = launchViaTmux(opts);
|
|
3059
|
+
if (!result.ok) {
|
|
3060
|
+
if (launchMode === "tmux") {
|
|
3061
|
+
return {
|
|
3062
|
+
ok: false,
|
|
3063
|
+
error: {
|
|
3064
|
+
kind: "tmux-failed",
|
|
3065
|
+
message: "tmux launch failed. Is tmux running?"
|
|
3066
|
+
}
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
return launchViaDetectedTerminal(opts);
|
|
3070
|
+
}
|
|
3071
|
+
return result;
|
|
3072
|
+
}
|
|
3073
|
+
return launchViaDetectedTerminal(opts);
|
|
3074
|
+
}
|
|
3075
|
+
var init_launch_claude = __esm({
|
|
3076
|
+
"src/board/launch-claude.ts"() {
|
|
3077
|
+
"use strict";
|
|
3078
|
+
}
|
|
3079
|
+
});
|
|
3080
|
+
|
|
2837
3081
|
// src/board/components/action-log.tsx
|
|
2838
3082
|
import { Box, Text } from "ink";
|
|
2839
3083
|
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
@@ -3147,7 +3391,7 @@ function HintBar({
|
|
|
3147
3391
|
if (uiMode === "overlay:detail") {
|
|
3148
3392
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
3149
3393
|
/* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "[DETAIL]" }),
|
|
3150
|
-
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: " Esc:close e:edit c:comment y:copy-link ? help" })
|
|
3394
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: " Esc:close e:edit c:comment g:open y:copy-link C:claude ? help" })
|
|
3151
3395
|
] });
|
|
3152
3396
|
}
|
|
3153
3397
|
if (uiMode.startsWith("overlay:")) {
|
|
@@ -3157,7 +3401,7 @@ function HintBar({
|
|
|
3157
3401
|
0: "j/k:scroll Esc:close ? help",
|
|
3158
3402
|
1: "j/k:move Enter:filter 0-4:panel ? help",
|
|
3159
3403
|
2: "j/k:move Enter:filter Esc:clear 0-4:panel ? help",
|
|
3160
|
-
3: `j/k:move p:pick m:status c:comment /:search n:new 0-4:panel${hasUndoable ? " u:undo" : ""} ? help q:quit`,
|
|
3404
|
+
3: `j/k:move Enter:detail g:open p:pick m:status c:comment C:claude /:search n:new 0-4:panel${hasUndoable ? " u:undo" : ""} ? help q:quit`,
|
|
3161
3405
|
4: "j/k:scroll Enter:jump r:refresh 0-4:panel ? help"
|
|
3162
3406
|
};
|
|
3163
3407
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
@@ -3258,7 +3502,7 @@ var init_ink_instance = __esm({
|
|
|
3258
3502
|
});
|
|
3259
3503
|
|
|
3260
3504
|
// src/board/components/comment-input.tsx
|
|
3261
|
-
import { spawnSync } from "child_process";
|
|
3505
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3262
3506
|
import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3263
3507
|
import { tmpdir } from "os";
|
|
3264
3508
|
import { join as join4 } from "path";
|
|
@@ -3312,7 +3556,7 @@ function CommentInput({
|
|
|
3312
3556
|
const inkInstance2 = getInkInstance();
|
|
3313
3557
|
inkInstance2?.clear();
|
|
3314
3558
|
setRawMode(false);
|
|
3315
|
-
|
|
3559
|
+
spawnSync2(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
3316
3560
|
const content = readFileSync4(tmpFile, "utf-8").trim();
|
|
3317
3561
|
setRawMode(true);
|
|
3318
3562
|
if (content) {
|
|
@@ -3618,7 +3862,7 @@ var init_create_issue_form = __esm({
|
|
|
3618
3862
|
});
|
|
3619
3863
|
|
|
3620
3864
|
// src/board/components/edit-issue-overlay.tsx
|
|
3621
|
-
import { spawnSync as
|
|
3865
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3622
3866
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3623
3867
|
import { tmpdir as tmpdir2 } from "os";
|
|
3624
3868
|
import { join as join5 } from "path";
|
|
@@ -3743,7 +3987,7 @@ function EditIssueOverlay({
|
|
|
3743
3987
|
setRawMode(false);
|
|
3744
3988
|
while (true) {
|
|
3745
3989
|
writeFileSync5(tmpFile, currentContent);
|
|
3746
|
-
const result =
|
|
3990
|
+
const result = spawnSync3(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
3747
3991
|
if (result.status !== 0 || result.signal !== null || result.error) {
|
|
3748
3992
|
break;
|
|
3749
3993
|
}
|
|
@@ -4216,7 +4460,7 @@ var init_help_overlay = __esm({
|
|
|
4216
4460
|
{
|
|
4217
4461
|
category: "View",
|
|
4218
4462
|
items: [
|
|
4219
|
-
{ key: "Enter", desc: "Open
|
|
4463
|
+
{ key: "Enter", desc: "Open detail panel" },
|
|
4220
4464
|
{ key: "0", desc: "Detail panel (full-screen on narrow terminals)" },
|
|
4221
4465
|
{ key: "Space", desc: "Multi-select item" },
|
|
4222
4466
|
{ key: "/", desc: "Search (inline filter)" },
|
|
@@ -4236,12 +4480,13 @@ var init_help_overlay = __esm({
|
|
|
4236
4480
|
{ key: "e", desc: "Edit issue in $EDITOR (title, assignee, status, labels)" },
|
|
4237
4481
|
{ key: "c", desc: "Comment on issue" },
|
|
4238
4482
|
{ key: "m", desc: "Move status" },
|
|
4239
|
-
{ key: "
|
|
4483
|
+
{ key: "g", desc: "Open issue in browser" },
|
|
4240
4484
|
{ key: "o", desc: "Open Slack thread" },
|
|
4241
4485
|
{ key: "y", desc: "Copy issue link to clipboard" },
|
|
4242
4486
|
{ key: "n", desc: "Create new issue" },
|
|
4243
4487
|
{ key: "I", desc: "Natural-language issue create" },
|
|
4244
|
-
{ key: "l", desc: "Manage labels" }
|
|
4488
|
+
{ key: "l", desc: "Manage labels" },
|
|
4489
|
+
{ key: "C", desc: "Launch Claude Code session for this issue" }
|
|
4245
4490
|
]
|
|
4246
4491
|
},
|
|
4247
4492
|
{
|
|
@@ -4257,7 +4502,7 @@ var init_help_overlay = __esm({
|
|
|
4257
4502
|
});
|
|
4258
4503
|
|
|
4259
4504
|
// src/board/components/nl-create-overlay.tsx
|
|
4260
|
-
import { spawnSync as
|
|
4505
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4261
4506
|
import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
4262
4507
|
import { tmpdir as tmpdir3 } from "os";
|
|
4263
4508
|
import { join as join6 } from "path";
|
|
@@ -4342,7 +4587,7 @@ function NlCreateOverlay({
|
|
|
4342
4587
|
const inkInstance2 = getInkInstance();
|
|
4343
4588
|
inkInstance2?.clear();
|
|
4344
4589
|
setRawMode(false);
|
|
4345
|
-
|
|
4590
|
+
spawnSync4(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
4346
4591
|
const content = readFileSync6(tmpFile, "utf-8");
|
|
4347
4592
|
setRawMode(true);
|
|
4348
4593
|
setBody(content.trimEnd());
|
|
@@ -5195,7 +5440,7 @@ var init_toast_container = __esm({
|
|
|
5195
5440
|
});
|
|
5196
5441
|
|
|
5197
5442
|
// src/board/components/dashboard.tsx
|
|
5198
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
5443
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync5 } from "child_process";
|
|
5199
5444
|
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
5200
5445
|
import { Box as Box24, Text as Text23, useApp, useStdout } from "ink";
|
|
5201
5446
|
import { useCallback as useCallback12, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef13, useState as useState16 } from "react";
|
|
@@ -5724,7 +5969,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5724
5969
|
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
5725
5970
|
return;
|
|
5726
5971
|
}
|
|
5727
|
-
const result =
|
|
5972
|
+
const result = spawnSync5(cmd, args, {
|
|
5728
5973
|
input: found.issue.url,
|
|
5729
5974
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5730
5975
|
});
|
|
@@ -5737,6 +5982,31 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5737
5982
|
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
5738
5983
|
}
|
|
5739
5984
|
}, [repos, nav.selectedId, config2.repos, toast]);
|
|
5985
|
+
const handleLaunchClaude = useCallback12(() => {
|
|
5986
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
5987
|
+
if (!found) return;
|
|
5988
|
+
const rc = config2.repos.find((r) => r.name === found.repoName);
|
|
5989
|
+
if (!rc?.localPath) {
|
|
5990
|
+
toast.info(
|
|
5991
|
+
`Set localPath for ${rc?.shortName ?? found.repoName} in ~/.config/hog/config.json to enable Claude Code launch`
|
|
5992
|
+
);
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
const resolvedStartCommand = rc.claudeStartCommand ?? config2.board.claudeStartCommand;
|
|
5996
|
+
const result = launchClaude({
|
|
5997
|
+
localPath: rc.localPath,
|
|
5998
|
+
issue: { number: found.issue.number, title: found.issue.title, url: found.issue.url },
|
|
5999
|
+
...resolvedStartCommand ? { startCommand: resolvedStartCommand } : {},
|
|
6000
|
+
launchMode: config2.board.claudeLaunchMode ?? "auto",
|
|
6001
|
+
...config2.board.claudeTerminalApp ? { terminalApp: config2.board.claudeTerminalApp } : {},
|
|
6002
|
+
repoFullName: found.repoName
|
|
6003
|
+
});
|
|
6004
|
+
if (!result.ok) {
|
|
6005
|
+
toast.error(result.error.message);
|
|
6006
|
+
return;
|
|
6007
|
+
}
|
|
6008
|
+
toast.info(`Claude Code session opened in ${rc.shortName ?? found.repoName}`);
|
|
6009
|
+
}, [repos, nav.selectedId, config2.repos, config2.board, toast]);
|
|
5740
6010
|
const multiSelectType = useMemo3(() => {
|
|
5741
6011
|
for (const id of multiSelect.selected) {
|
|
5742
6012
|
if (id.startsWith("tt:")) return "ticktick";
|
|
@@ -5850,7 +6120,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5850
6120
|
handleEnterFuzzyPicker: ui.enterFuzzyPicker,
|
|
5851
6121
|
handleEnterEditIssue: ui.enterEditIssue,
|
|
5852
6122
|
handleUndo: undoLast,
|
|
5853
|
-
handleToggleLog: () => setLogVisible((v) => !v)
|
|
6123
|
+
handleToggleLog: () => setLogVisible((v) => !v),
|
|
6124
|
+
handleLaunchClaude
|
|
5854
6125
|
},
|
|
5855
6126
|
onSearchEscape,
|
|
5856
6127
|
panelFocus,
|
|
@@ -6082,6 +6353,7 @@ var init_dashboard = __esm({
|
|
|
6082
6353
|
init_use_panel_focus();
|
|
6083
6354
|
init_use_toast();
|
|
6084
6355
|
init_use_ui_state();
|
|
6356
|
+
init_launch_claude();
|
|
6085
6357
|
init_action_log();
|
|
6086
6358
|
init_activity_panel();
|
|
6087
6359
|
init_detail_panel();
|
|
@@ -7415,7 +7687,7 @@ function resolveProjectId(projectId) {
|
|
|
7415
7687
|
process.exit(1);
|
|
7416
7688
|
}
|
|
7417
7689
|
var program = new Command();
|
|
7418
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
7690
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.18.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
7419
7691
|
const opts = thisCommand.opts();
|
|
7420
7692
|
if (opts.json) setFormat("json");
|
|
7421
7693
|
if (opts.human) setFormat("human");
|
|
@@ -7564,6 +7836,63 @@ program.command("pick <issueRef>").description("Pick up an issue: assign to self
|
|
|
7564
7836
|
}
|
|
7565
7837
|
}
|
|
7566
7838
|
});
|
|
7839
|
+
program.command("launch <issueRef>").description("Launch Claude Code for an issue in its local repo directory").option("--dry-run", "Print resolved config without spawning").action(async (issueRef, opts) => {
|
|
7840
|
+
const cfg = loadFullConfig();
|
|
7841
|
+
const ref = await resolveRef(issueRef, cfg);
|
|
7842
|
+
const rc = ref.repo;
|
|
7843
|
+
if (!rc.localPath) {
|
|
7844
|
+
errorOut(
|
|
7845
|
+
`Set localPath for ${rc.shortName} in ~/.config/hog/config.json to enable Claude Code launch`,
|
|
7846
|
+
{ repo: rc.shortName }
|
|
7847
|
+
);
|
|
7848
|
+
}
|
|
7849
|
+
const startCommand = rc.claudeStartCommand ?? cfg.board.claudeStartCommand;
|
|
7850
|
+
const launchMode = cfg.board.claudeLaunchMode ?? "auto";
|
|
7851
|
+
const terminalApp = cfg.board.claudeTerminalApp;
|
|
7852
|
+
if (opts.dryRun) {
|
|
7853
|
+
if (useJson()) {
|
|
7854
|
+
jsonOut({
|
|
7855
|
+
ok: true,
|
|
7856
|
+
dryRun: true,
|
|
7857
|
+
would: {
|
|
7858
|
+
localPath: rc.localPath,
|
|
7859
|
+
command: startCommand?.command ?? "claude",
|
|
7860
|
+
extraArgs: startCommand?.extraArgs ?? [],
|
|
7861
|
+
launchMode,
|
|
7862
|
+
terminalApp: terminalApp ?? null,
|
|
7863
|
+
issueNumber: ref.issueNumber,
|
|
7864
|
+
repo: rc.shortName
|
|
7865
|
+
}
|
|
7866
|
+
});
|
|
7867
|
+
} else {
|
|
7868
|
+
console.log(`[dry-run] Would launch Claude Code for ${rc.shortName}#${ref.issueNumber}`);
|
|
7869
|
+
console.log(` localPath: ${rc.localPath}`);
|
|
7870
|
+
console.log(` command: ${startCommand?.command ?? "claude"}`);
|
|
7871
|
+
console.log(` launchMode: ${launchMode}`);
|
|
7872
|
+
if (terminalApp) console.log(` terminalApp: ${terminalApp}`);
|
|
7873
|
+
}
|
|
7874
|
+
return;
|
|
7875
|
+
}
|
|
7876
|
+
const { launchClaude: launchClaude2 } = await Promise.resolve().then(() => (init_launch_claude(), launch_claude_exports));
|
|
7877
|
+
const { fetchIssueAsync: fetchIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7878
|
+
const issue = await fetchIssueAsync2(rc.name, ref.issueNumber);
|
|
7879
|
+
const result = launchClaude2({
|
|
7880
|
+
localPath: rc.localPath,
|
|
7881
|
+
issue: { number: issue.number, title: issue.title, url: issue.url },
|
|
7882
|
+
...startCommand ? { startCommand } : {},
|
|
7883
|
+
launchMode,
|
|
7884
|
+
...terminalApp ? { terminalApp } : {},
|
|
7885
|
+
repoFullName: rc.name
|
|
7886
|
+
});
|
|
7887
|
+
if (!result.ok) {
|
|
7888
|
+
errorOut(result.error.message, { kind: result.error.kind });
|
|
7889
|
+
}
|
|
7890
|
+
if (useJson()) {
|
|
7891
|
+
jsonOut({ ok: true, data: { repo: rc.shortName, issue: ref.issueNumber } });
|
|
7892
|
+
} else {
|
|
7893
|
+
console.log(`Claude Code session opened in ${rc.shortName}#${ref.issueNumber}`);
|
|
7894
|
+
}
|
|
7895
|
+
});
|
|
7567
7896
|
var config = program.command("config").description("Manage hog configuration");
|
|
7568
7897
|
config.command("show").description("Show full configuration").action(() => {
|
|
7569
7898
|
const cfg = loadFullConfig();
|