@ondrej-svec/hog 1.16.2 → 1.17.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 +339 -18
- 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,10 @@ function useKeyboard({
|
|
|
2171
2183
|
}
|
|
2172
2184
|
return;
|
|
2173
2185
|
}
|
|
2186
|
+
if (input2 === "C") {
|
|
2187
|
+
handleLaunchClaude();
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2174
2190
|
}
|
|
2175
2191
|
if (ui.canAct) {
|
|
2176
2192
|
const digit = parseInt(input2, 10);
|
|
@@ -2309,6 +2325,7 @@ function useKeyboard({
|
|
|
2309
2325
|
handleEnterEditIssue,
|
|
2310
2326
|
handleUndo,
|
|
2311
2327
|
handleToggleLog,
|
|
2328
|
+
handleLaunchClaude,
|
|
2312
2329
|
showDetailPanel
|
|
2313
2330
|
]
|
|
2314
2331
|
);
|
|
@@ -2834,6 +2851,225 @@ var init_use_ui_state = __esm({
|
|
|
2834
2851
|
}
|
|
2835
2852
|
});
|
|
2836
2853
|
|
|
2854
|
+
// src/board/launch-claude.ts
|
|
2855
|
+
var launch_claude_exports = {};
|
|
2856
|
+
__export(launch_claude_exports, {
|
|
2857
|
+
buildPrompt: () => buildPrompt,
|
|
2858
|
+
launchClaude: () => launchClaude
|
|
2859
|
+
});
|
|
2860
|
+
import { spawn, spawnSync } from "child_process";
|
|
2861
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2862
|
+
function buildPrompt(issue) {
|
|
2863
|
+
return `Issue #${issue.number}: ${issue.title}
|
|
2864
|
+
URL: ${issue.url}`;
|
|
2865
|
+
}
|
|
2866
|
+
function isClaudeInPath() {
|
|
2867
|
+
const result = spawnSync("which", ["claude"], { stdio: "pipe" });
|
|
2868
|
+
return result.status === 0;
|
|
2869
|
+
}
|
|
2870
|
+
function isInTmux() {
|
|
2871
|
+
return !!process.env["TMUX"];
|
|
2872
|
+
}
|
|
2873
|
+
function isInSsh() {
|
|
2874
|
+
return !!(process.env["SSH_CLIENT"] ?? process.env["SSH_TTY"]);
|
|
2875
|
+
}
|
|
2876
|
+
function detectTerminalApp() {
|
|
2877
|
+
return process.env["TERM_PROGRAM"];
|
|
2878
|
+
}
|
|
2879
|
+
function resolveCommand(opts) {
|
|
2880
|
+
if (opts.startCommand) return opts.startCommand;
|
|
2881
|
+
return { command: "claude", extraArgs: [] };
|
|
2882
|
+
}
|
|
2883
|
+
function launchViaTmux(opts) {
|
|
2884
|
+
const { localPath, issue, repoFullName } = opts;
|
|
2885
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
2886
|
+
const prompt = buildPrompt(issue);
|
|
2887
|
+
const windowName = `claude-${issue.number}`;
|
|
2888
|
+
const tmuxArgs = [
|
|
2889
|
+
"new-window",
|
|
2890
|
+
"-d",
|
|
2891
|
+
// don't steal focus from hog board
|
|
2892
|
+
"-c",
|
|
2893
|
+
localPath,
|
|
2894
|
+
"-n",
|
|
2895
|
+
windowName
|
|
2896
|
+
];
|
|
2897
|
+
if (repoFullName) {
|
|
2898
|
+
tmuxArgs.push("-e", `HOG_REPO=${repoFullName}`);
|
|
2899
|
+
}
|
|
2900
|
+
tmuxArgs.push("-e", `HOG_ISSUE=${issue.number}`);
|
|
2901
|
+
tmuxArgs.push(command, ...extraArgs, "--", prompt);
|
|
2902
|
+
const child = spawn("tmux", tmuxArgs, { stdio: "ignore", detached: true });
|
|
2903
|
+
child.unref();
|
|
2904
|
+
return { ok: true, value: void 0 };
|
|
2905
|
+
}
|
|
2906
|
+
function launchViaTerminalApp(terminalApp, opts) {
|
|
2907
|
+
const { localPath, issue } = opts;
|
|
2908
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
2909
|
+
const prompt = buildPrompt(issue);
|
|
2910
|
+
const fullCmd = [command, ...extraArgs, "--", prompt].join(" ");
|
|
2911
|
+
switch (terminalApp) {
|
|
2912
|
+
case "iTerm": {
|
|
2913
|
+
const script = `tell application "iTerm"
|
|
2914
|
+
create window with default profile command "bash -c 'cd ${localPath} && ${fullCmd}'"
|
|
2915
|
+
end tell`;
|
|
2916
|
+
const result = spawnSync("osascript", ["-e", script], { stdio: "ignore" });
|
|
2917
|
+
if (result.status !== 0) {
|
|
2918
|
+
return {
|
|
2919
|
+
ok: false,
|
|
2920
|
+
error: {
|
|
2921
|
+
kind: "terminal-failed",
|
|
2922
|
+
message: `iTerm2 launch failed. Is iTerm2 installed and running?`
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2925
|
+
}
|
|
2926
|
+
return { ok: true, value: void 0 };
|
|
2927
|
+
}
|
|
2928
|
+
case "Terminal": {
|
|
2929
|
+
const child = spawn("open", ["-a", "Terminal", localPath], {
|
|
2930
|
+
stdio: "ignore",
|
|
2931
|
+
detached: true
|
|
2932
|
+
});
|
|
2933
|
+
child.unref();
|
|
2934
|
+
return { ok: true, value: void 0 };
|
|
2935
|
+
}
|
|
2936
|
+
case "Ghostty": {
|
|
2937
|
+
const child = spawn(
|
|
2938
|
+
"open",
|
|
2939
|
+
["-na", "Ghostty", "--args", `--working-directory=${localPath}`],
|
|
2940
|
+
{ stdio: "ignore", detached: true }
|
|
2941
|
+
);
|
|
2942
|
+
child.unref();
|
|
2943
|
+
return { ok: true, value: void 0 };
|
|
2944
|
+
}
|
|
2945
|
+
case "WezTerm": {
|
|
2946
|
+
const child = spawn("wezterm", ["start", "--cwd", localPath], {
|
|
2947
|
+
stdio: "ignore",
|
|
2948
|
+
detached: true
|
|
2949
|
+
});
|
|
2950
|
+
child.unref();
|
|
2951
|
+
return { ok: true, value: void 0 };
|
|
2952
|
+
}
|
|
2953
|
+
case "Kitty": {
|
|
2954
|
+
const child = spawn(
|
|
2955
|
+
"kitty",
|
|
2956
|
+
["--directory", localPath, command, ...extraArgs, "--", prompt],
|
|
2957
|
+
{
|
|
2958
|
+
stdio: "ignore",
|
|
2959
|
+
detached: true
|
|
2960
|
+
}
|
|
2961
|
+
);
|
|
2962
|
+
child.unref();
|
|
2963
|
+
return { ok: true, value: void 0 };
|
|
2964
|
+
}
|
|
2965
|
+
case "Alacritty": {
|
|
2966
|
+
const child = spawn(
|
|
2967
|
+
"alacritty",
|
|
2968
|
+
["--command", "bash", "-c", `cd ${localPath} && ${fullCmd}`],
|
|
2969
|
+
{ stdio: "ignore", detached: true }
|
|
2970
|
+
);
|
|
2971
|
+
child.unref();
|
|
2972
|
+
return { ok: true, value: void 0 };
|
|
2973
|
+
}
|
|
2974
|
+
default:
|
|
2975
|
+
return {
|
|
2976
|
+
ok: false,
|
|
2977
|
+
error: {
|
|
2978
|
+
kind: "terminal-app-not-found",
|
|
2979
|
+
message: `Unknown terminal app: ${terminalApp}`
|
|
2980
|
+
}
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function launchViaDetectedTerminal(opts) {
|
|
2985
|
+
const { terminalApp } = opts;
|
|
2986
|
+
if (terminalApp) {
|
|
2987
|
+
return launchViaTerminalApp(terminalApp, opts);
|
|
2988
|
+
}
|
|
2989
|
+
const termProgram = detectTerminalApp();
|
|
2990
|
+
if (termProgram === "iTerm.app") {
|
|
2991
|
+
return launchViaTerminalApp("iTerm", opts);
|
|
2992
|
+
}
|
|
2993
|
+
if (termProgram === "Apple_Terminal") {
|
|
2994
|
+
return launchViaTerminalApp("Terminal", opts);
|
|
2995
|
+
}
|
|
2996
|
+
if (termProgram === "WezTerm") {
|
|
2997
|
+
return launchViaTerminalApp("WezTerm", opts);
|
|
2998
|
+
}
|
|
2999
|
+
if (termProgram === "ghostty") {
|
|
3000
|
+
return launchViaTerminalApp("Ghostty", opts);
|
|
3001
|
+
}
|
|
3002
|
+
if (process.env["KITTY_WINDOW_ID"]) {
|
|
3003
|
+
return launchViaTerminalApp("Kitty", opts);
|
|
3004
|
+
}
|
|
3005
|
+
if (process.platform === "darwin") {
|
|
3006
|
+
return launchViaTerminalApp("Terminal", opts);
|
|
3007
|
+
}
|
|
3008
|
+
const { localPath, issue } = opts;
|
|
3009
|
+
const { command, extraArgs } = resolveCommand(opts);
|
|
3010
|
+
const prompt = buildPrompt(issue);
|
|
3011
|
+
const child = spawn(
|
|
3012
|
+
"xdg-terminal-exec",
|
|
3013
|
+
["bash", "-c", `cd ${localPath} && ${[command, ...extraArgs, "--", prompt].join(" ")}`],
|
|
3014
|
+
{ stdio: "ignore", detached: true }
|
|
3015
|
+
);
|
|
3016
|
+
child.unref();
|
|
3017
|
+
return { ok: true, value: void 0 };
|
|
3018
|
+
}
|
|
3019
|
+
function launchClaude(opts) {
|
|
3020
|
+
const { localPath, launchMode = "auto" } = opts;
|
|
3021
|
+
if (!existsSync5(localPath)) {
|
|
3022
|
+
return {
|
|
3023
|
+
ok: false,
|
|
3024
|
+
error: {
|
|
3025
|
+
kind: "directory-not-found",
|
|
3026
|
+
message: `Directory not found: ${localPath}. Check localPath config.`
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
if (!isClaudeInPath()) {
|
|
3031
|
+
return {
|
|
3032
|
+
ok: false,
|
|
3033
|
+
error: {
|
|
3034
|
+
kind: "claude-not-found",
|
|
3035
|
+
message: "claude binary not found in PATH. Install Claude Code first."
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
if (isInSsh() && !isInTmux() && launchMode !== "tmux") {
|
|
3040
|
+
return {
|
|
3041
|
+
ok: false,
|
|
3042
|
+
error: {
|
|
3043
|
+
kind: "ssh-no-tmux",
|
|
3044
|
+
message: "Running over SSH without tmux \u2014 start tmux to enable Claude Code launch."
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
}
|
|
3048
|
+
const useTmux = launchMode === "tmux" || launchMode === "auto" && isInTmux();
|
|
3049
|
+
if (useTmux) {
|
|
3050
|
+
const result = launchViaTmux(opts);
|
|
3051
|
+
if (!result.ok) {
|
|
3052
|
+
if (launchMode === "tmux") {
|
|
3053
|
+
return {
|
|
3054
|
+
ok: false,
|
|
3055
|
+
error: {
|
|
3056
|
+
kind: "tmux-failed",
|
|
3057
|
+
message: "tmux launch failed. Is tmux running?"
|
|
3058
|
+
}
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
return launchViaDetectedTerminal(opts);
|
|
3062
|
+
}
|
|
3063
|
+
return result;
|
|
3064
|
+
}
|
|
3065
|
+
return launchViaDetectedTerminal(opts);
|
|
3066
|
+
}
|
|
3067
|
+
var init_launch_claude = __esm({
|
|
3068
|
+
"src/board/launch-claude.ts"() {
|
|
3069
|
+
"use strict";
|
|
3070
|
+
}
|
|
3071
|
+
});
|
|
3072
|
+
|
|
2837
3073
|
// src/board/components/action-log.tsx
|
|
2838
3074
|
import { Box, Text } from "ink";
|
|
2839
3075
|
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
@@ -3147,7 +3383,7 @@ function HintBar({
|
|
|
3147
3383
|
if (uiMode === "overlay:detail") {
|
|
3148
3384
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
3149
3385
|
/* @__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" })
|
|
3386
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: " Esc:close e:edit c:comment y:copy-link C:claude ? help" })
|
|
3151
3387
|
] });
|
|
3152
3388
|
}
|
|
3153
3389
|
if (uiMode.startsWith("overlay:")) {
|
|
@@ -3157,7 +3393,7 @@ function HintBar({
|
|
|
3157
3393
|
0: "j/k:scroll Esc:close ? help",
|
|
3158
3394
|
1: "j/k:move Enter:filter 0-4:panel ? help",
|
|
3159
3395
|
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`,
|
|
3396
|
+
3: `j/k:move p:pick m:status c:comment C:claude /:search n:new 0-4:panel${hasUndoable ? " u:undo" : ""} ? help q:quit`,
|
|
3161
3397
|
4: "j/k:scroll Enter:jump r:refresh 0-4:panel ? help"
|
|
3162
3398
|
};
|
|
3163
3399
|
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
@@ -3258,7 +3494,7 @@ var init_ink_instance = __esm({
|
|
|
3258
3494
|
});
|
|
3259
3495
|
|
|
3260
3496
|
// src/board/components/comment-input.tsx
|
|
3261
|
-
import { spawnSync } from "child_process";
|
|
3497
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
3262
3498
|
import { mkdtempSync, readFileSync as readFileSync4, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3263
3499
|
import { tmpdir } from "os";
|
|
3264
3500
|
import { join as join4 } from "path";
|
|
@@ -3312,7 +3548,7 @@ function CommentInput({
|
|
|
3312
3548
|
const inkInstance2 = getInkInstance();
|
|
3313
3549
|
inkInstance2?.clear();
|
|
3314
3550
|
setRawMode(false);
|
|
3315
|
-
|
|
3551
|
+
spawnSync2(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
3316
3552
|
const content = readFileSync4(tmpFile, "utf-8").trim();
|
|
3317
3553
|
setRawMode(true);
|
|
3318
3554
|
if (content) {
|
|
@@ -3618,7 +3854,7 @@ var init_create_issue_form = __esm({
|
|
|
3618
3854
|
});
|
|
3619
3855
|
|
|
3620
3856
|
// src/board/components/edit-issue-overlay.tsx
|
|
3621
|
-
import { spawnSync as
|
|
3857
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
3622
3858
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
3623
3859
|
import { tmpdir as tmpdir2 } from "os";
|
|
3624
3860
|
import { join as join5 } from "path";
|
|
@@ -3743,7 +3979,7 @@ function EditIssueOverlay({
|
|
|
3743
3979
|
setRawMode(false);
|
|
3744
3980
|
while (true) {
|
|
3745
3981
|
writeFileSync5(tmpFile, currentContent);
|
|
3746
|
-
const result =
|
|
3982
|
+
const result = spawnSync3(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
3747
3983
|
if (result.status !== 0 || result.signal !== null || result.error) {
|
|
3748
3984
|
break;
|
|
3749
3985
|
}
|
|
@@ -4241,7 +4477,8 @@ var init_help_overlay = __esm({
|
|
|
4241
4477
|
{ key: "y", desc: "Copy issue link to clipboard" },
|
|
4242
4478
|
{ key: "n", desc: "Create new issue" },
|
|
4243
4479
|
{ key: "I", desc: "Natural-language issue create" },
|
|
4244
|
-
{ key: "l", desc: "Manage labels" }
|
|
4480
|
+
{ key: "l", desc: "Manage labels" },
|
|
4481
|
+
{ key: "C", desc: "Launch Claude Code session for this issue" }
|
|
4245
4482
|
]
|
|
4246
4483
|
},
|
|
4247
4484
|
{
|
|
@@ -4257,7 +4494,7 @@ var init_help_overlay = __esm({
|
|
|
4257
4494
|
});
|
|
4258
4495
|
|
|
4259
4496
|
// src/board/components/nl-create-overlay.tsx
|
|
4260
|
-
import { spawnSync as
|
|
4497
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4261
4498
|
import { mkdtempSync as mkdtempSync3, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
4262
4499
|
import { tmpdir as tmpdir3 } from "os";
|
|
4263
4500
|
import { join as join6 } from "path";
|
|
@@ -4342,7 +4579,7 @@ function NlCreateOverlay({
|
|
|
4342
4579
|
const inkInstance2 = getInkInstance();
|
|
4343
4580
|
inkInstance2?.clear();
|
|
4344
4581
|
setRawMode(false);
|
|
4345
|
-
|
|
4582
|
+
spawnSync4(cmd, [...extraArgs, tmpFile], { stdio: "inherit" });
|
|
4346
4583
|
const content = readFileSync6(tmpFile, "utf-8");
|
|
4347
4584
|
setRawMode(true);
|
|
4348
4585
|
setBody(content.trimEnd());
|
|
@@ -5195,7 +5432,7 @@ var init_toast_container = __esm({
|
|
|
5195
5432
|
});
|
|
5196
5433
|
|
|
5197
5434
|
// src/board/components/dashboard.tsx
|
|
5198
|
-
import { execFileSync as execFileSync3, spawnSync as
|
|
5435
|
+
import { execFileSync as execFileSync3, spawnSync as spawnSync5 } from "child_process";
|
|
5199
5436
|
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
5200
5437
|
import { Box as Box24, Text as Text23, useApp, useStdout } from "ink";
|
|
5201
5438
|
import { useCallback as useCallback12, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef13, useState as useState16 } from "react";
|
|
@@ -5724,7 +5961,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5724
5961
|
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
5725
5962
|
return;
|
|
5726
5963
|
}
|
|
5727
|
-
const result =
|
|
5964
|
+
const result = spawnSync5(cmd, args, {
|
|
5728
5965
|
input: found.issue.url,
|
|
5729
5966
|
stdio: ["pipe", "pipe", "pipe"]
|
|
5730
5967
|
});
|
|
@@ -5737,6 +5974,31 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5737
5974
|
toast.info(`${label} \u2014 ${found.issue.url}`);
|
|
5738
5975
|
}
|
|
5739
5976
|
}, [repos, nav.selectedId, config2.repos, toast]);
|
|
5977
|
+
const handleLaunchClaude = useCallback12(() => {
|
|
5978
|
+
const found = findSelectedIssueWithRepo(repos, nav.selectedId);
|
|
5979
|
+
if (!found) return;
|
|
5980
|
+
const rc = config2.repos.find((r) => r.name === found.repoName);
|
|
5981
|
+
if (!rc?.localPath) {
|
|
5982
|
+
toast.info(
|
|
5983
|
+
`Set localPath for ${rc?.shortName ?? found.repoName} in ~/.config/hog/config.json to enable Claude Code launch`
|
|
5984
|
+
);
|
|
5985
|
+
return;
|
|
5986
|
+
}
|
|
5987
|
+
const resolvedStartCommand = rc.claudeStartCommand ?? config2.board.claudeStartCommand;
|
|
5988
|
+
const result = launchClaude({
|
|
5989
|
+
localPath: rc.localPath,
|
|
5990
|
+
issue: { number: found.issue.number, title: found.issue.title, url: found.issue.url },
|
|
5991
|
+
...resolvedStartCommand ? { startCommand: resolvedStartCommand } : {},
|
|
5992
|
+
launchMode: config2.board.claudeLaunchMode ?? "auto",
|
|
5993
|
+
...config2.board.claudeTerminalApp ? { terminalApp: config2.board.claudeTerminalApp } : {},
|
|
5994
|
+
repoFullName: found.repoName
|
|
5995
|
+
});
|
|
5996
|
+
if (!result.ok) {
|
|
5997
|
+
toast.error(result.error.message);
|
|
5998
|
+
return;
|
|
5999
|
+
}
|
|
6000
|
+
toast.info(`Claude Code session opened in ${rc.shortName ?? found.repoName}`);
|
|
6001
|
+
}, [repos, nav.selectedId, config2.repos, config2.board, toast]);
|
|
5740
6002
|
const multiSelectType = useMemo3(() => {
|
|
5741
6003
|
for (const id of multiSelect.selected) {
|
|
5742
6004
|
if (id.startsWith("tt:")) return "ticktick";
|
|
@@ -5850,7 +6112,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
5850
6112
|
handleEnterFuzzyPicker: ui.enterFuzzyPicker,
|
|
5851
6113
|
handleEnterEditIssue: ui.enterEditIssue,
|
|
5852
6114
|
handleUndo: undoLast,
|
|
5853
|
-
handleToggleLog: () => setLogVisible((v) => !v)
|
|
6115
|
+
handleToggleLog: () => setLogVisible((v) => !v),
|
|
6116
|
+
handleLaunchClaude
|
|
5854
6117
|
},
|
|
5855
6118
|
onSearchEscape,
|
|
5856
6119
|
panelFocus,
|
|
@@ -6082,6 +6345,7 @@ var init_dashboard = __esm({
|
|
|
6082
6345
|
init_use_panel_focus();
|
|
6083
6346
|
init_use_toast();
|
|
6084
6347
|
init_use_ui_state();
|
|
6348
|
+
init_launch_claude();
|
|
6085
6349
|
init_action_log();
|
|
6086
6350
|
init_activity_panel();
|
|
6087
6351
|
init_detail_panel();
|
|
@@ -7415,7 +7679,7 @@ function resolveProjectId(projectId) {
|
|
|
7415
7679
|
process.exit(1);
|
|
7416
7680
|
}
|
|
7417
7681
|
var program = new Command();
|
|
7418
|
-
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.
|
|
7682
|
+
program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.17.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
7419
7683
|
const opts = thisCommand.opts();
|
|
7420
7684
|
if (opts.json) setFormat("json");
|
|
7421
7685
|
if (opts.human) setFormat("human");
|
|
@@ -7564,6 +7828,63 @@ program.command("pick <issueRef>").description("Pick up an issue: assign to self
|
|
|
7564
7828
|
}
|
|
7565
7829
|
}
|
|
7566
7830
|
});
|
|
7831
|
+
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) => {
|
|
7832
|
+
const cfg = loadFullConfig();
|
|
7833
|
+
const ref = await resolveRef(issueRef, cfg);
|
|
7834
|
+
const rc = ref.repo;
|
|
7835
|
+
if (!rc.localPath) {
|
|
7836
|
+
errorOut(
|
|
7837
|
+
`Set localPath for ${rc.shortName} in ~/.config/hog/config.json to enable Claude Code launch`,
|
|
7838
|
+
{ repo: rc.shortName }
|
|
7839
|
+
);
|
|
7840
|
+
}
|
|
7841
|
+
const startCommand = rc.claudeStartCommand ?? cfg.board.claudeStartCommand;
|
|
7842
|
+
const launchMode = cfg.board.claudeLaunchMode ?? "auto";
|
|
7843
|
+
const terminalApp = cfg.board.claudeTerminalApp;
|
|
7844
|
+
if (opts.dryRun) {
|
|
7845
|
+
if (useJson()) {
|
|
7846
|
+
jsonOut({
|
|
7847
|
+
ok: true,
|
|
7848
|
+
dryRun: true,
|
|
7849
|
+
would: {
|
|
7850
|
+
localPath: rc.localPath,
|
|
7851
|
+
command: startCommand?.command ?? "claude",
|
|
7852
|
+
extraArgs: startCommand?.extraArgs ?? [],
|
|
7853
|
+
launchMode,
|
|
7854
|
+
terminalApp: terminalApp ?? null,
|
|
7855
|
+
issueNumber: ref.issueNumber,
|
|
7856
|
+
repo: rc.shortName
|
|
7857
|
+
}
|
|
7858
|
+
});
|
|
7859
|
+
} else {
|
|
7860
|
+
console.log(`[dry-run] Would launch Claude Code for ${rc.shortName}#${ref.issueNumber}`);
|
|
7861
|
+
console.log(` localPath: ${rc.localPath}`);
|
|
7862
|
+
console.log(` command: ${startCommand?.command ?? "claude"}`);
|
|
7863
|
+
console.log(` launchMode: ${launchMode}`);
|
|
7864
|
+
if (terminalApp) console.log(` terminalApp: ${terminalApp}`);
|
|
7865
|
+
}
|
|
7866
|
+
return;
|
|
7867
|
+
}
|
|
7868
|
+
const { launchClaude: launchClaude2 } = await Promise.resolve().then(() => (init_launch_claude(), launch_claude_exports));
|
|
7869
|
+
const { fetchIssueAsync: fetchIssueAsync2 } = await Promise.resolve().then(() => (init_github(), github_exports));
|
|
7870
|
+
const issue = await fetchIssueAsync2(rc.name, ref.issueNumber);
|
|
7871
|
+
const result = launchClaude2({
|
|
7872
|
+
localPath: rc.localPath,
|
|
7873
|
+
issue: { number: issue.number, title: issue.title, url: issue.url },
|
|
7874
|
+
...startCommand ? { startCommand } : {},
|
|
7875
|
+
launchMode,
|
|
7876
|
+
...terminalApp ? { terminalApp } : {},
|
|
7877
|
+
repoFullName: rc.name
|
|
7878
|
+
});
|
|
7879
|
+
if (!result.ok) {
|
|
7880
|
+
errorOut(result.error.message, { kind: result.error.kind });
|
|
7881
|
+
}
|
|
7882
|
+
if (useJson()) {
|
|
7883
|
+
jsonOut({ ok: true, data: { repo: rc.shortName, issue: ref.issueNumber } });
|
|
7884
|
+
} else {
|
|
7885
|
+
console.log(`Claude Code session opened in ${rc.shortName}#${ref.issueNumber}`);
|
|
7886
|
+
}
|
|
7887
|
+
});
|
|
7567
7888
|
var config = program.command("config").description("Manage hog configuration");
|
|
7568
7889
|
config.command("show").description("Show full configuration").action(() => {
|
|
7569
7890
|
const cfg = loadFullConfig();
|