@iloom/cli 0.1.14
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/LICENSE +33 -0
- package/README.md +711 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js +13 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js.map +1 -0
- package/dist/ClaudeService-YSZ6EXWP.js +12 -0
- package/dist/ClaudeService-YSZ6EXWP.js.map +1 -0
- package/dist/GitHubService-F7Z3XJOS.js +11 -0
- package/dist/GitHubService-F7Z3XJOS.js.map +1 -0
- package/dist/LoomLauncher-MODG2SEM.js +263 -0
- package/dist/LoomLauncher-MODG2SEM.js.map +1 -0
- package/dist/NeonProvider-PAGPUH7F.js +12 -0
- package/dist/NeonProvider-PAGPUH7F.js.map +1 -0
- package/dist/PromptTemplateManager-7FINLRDE.js +9 -0
- package/dist/PromptTemplateManager-7FINLRDE.js.map +1 -0
- package/dist/SettingsManager-VAZF26S2.js +19 -0
- package/dist/SettingsManager-VAZF26S2.js.map +1 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js +146 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js.map +1 -0
- package/dist/add-issue-22JBNOML.js +54 -0
- package/dist/add-issue-22JBNOML.js.map +1 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +580 -0
- package/dist/agents/iloom-issue-analyzer.md +290 -0
- package/dist/agents/iloom-issue-complexity-evaluator.md +224 -0
- package/dist/agents/iloom-issue-enhancer.md +266 -0
- package/dist/agents/iloom-issue-implementer.md +262 -0
- package/dist/agents/iloom-issue-planner.md +358 -0
- package/dist/agents/iloom-issue-reviewer.md +63 -0
- package/dist/chunk-2ZPFJQ3B.js +63 -0
- package/dist/chunk-2ZPFJQ3B.js.map +1 -0
- package/dist/chunk-37DYYFVK.js +29 -0
- package/dist/chunk-37DYYFVK.js.map +1 -0
- package/dist/chunk-BLCTGFZN.js +121 -0
- package/dist/chunk-BLCTGFZN.js.map +1 -0
- package/dist/chunk-CP2NU2JC.js +545 -0
- package/dist/chunk-CP2NU2JC.js.map +1 -0
- package/dist/chunk-CWR2SANQ.js +39 -0
- package/dist/chunk-CWR2SANQ.js.map +1 -0
- package/dist/chunk-F3XBU2R7.js +110 -0
- package/dist/chunk-F3XBU2R7.js.map +1 -0
- package/dist/chunk-GEHQXLEI.js +130 -0
- package/dist/chunk-GEHQXLEI.js.map +1 -0
- package/dist/chunk-GYCR2LOU.js +143 -0
- package/dist/chunk-GYCR2LOU.js.map +1 -0
- package/dist/chunk-GZP4UGGM.js +48 -0
- package/dist/chunk-GZP4UGGM.js.map +1 -0
- package/dist/chunk-H4E4THUZ.js +55 -0
- package/dist/chunk-H4E4THUZ.js.map +1 -0
- package/dist/chunk-HPJJSYNS.js +644 -0
- package/dist/chunk-HPJJSYNS.js.map +1 -0
- package/dist/chunk-JBH2ZYYZ.js +220 -0
- package/dist/chunk-JBH2ZYYZ.js.map +1 -0
- package/dist/chunk-JNKJ7NJV.js +78 -0
- package/dist/chunk-JNKJ7NJV.js.map +1 -0
- package/dist/chunk-JQ7VOSTC.js +437 -0
- package/dist/chunk-JQ7VOSTC.js.map +1 -0
- package/dist/chunk-KQDEK2ZW.js +199 -0
- package/dist/chunk-KQDEK2ZW.js.map +1 -0
- package/dist/chunk-O2QWO64Z.js +179 -0
- package/dist/chunk-O2QWO64Z.js.map +1 -0
- package/dist/chunk-OC4H6HJD.js +248 -0
- package/dist/chunk-OC4H6HJD.js.map +1 -0
- package/dist/chunk-PR7FKQBG.js +120 -0
- package/dist/chunk-PR7FKQBG.js.map +1 -0
- package/dist/chunk-PXZBAC2M.js +250 -0
- package/dist/chunk-PXZBAC2M.js.map +1 -0
- package/dist/chunk-QEPVTTHD.js +383 -0
- package/dist/chunk-QEPVTTHD.js.map +1 -0
- package/dist/chunk-RSRO7564.js +203 -0
- package/dist/chunk-RSRO7564.js.map +1 -0
- package/dist/chunk-SJUQ2NDR.js +146 -0
- package/dist/chunk-SJUQ2NDR.js.map +1 -0
- package/dist/chunk-SPYPLHMK.js +177 -0
- package/dist/chunk-SPYPLHMK.js.map +1 -0
- package/dist/chunk-SSCQCCJ7.js +75 -0
- package/dist/chunk-SSCQCCJ7.js.map +1 -0
- package/dist/chunk-SSR5AVRJ.js +41 -0
- package/dist/chunk-SSR5AVRJ.js.map +1 -0
- package/dist/chunk-T7QPXANZ.js +315 -0
- package/dist/chunk-T7QPXANZ.js.map +1 -0
- package/dist/chunk-U3WU5OWO.js +203 -0
- package/dist/chunk-U3WU5OWO.js.map +1 -0
- package/dist/chunk-W3DQTW63.js +124 -0
- package/dist/chunk-W3DQTW63.js.map +1 -0
- package/dist/chunk-WKEWRSDB.js +151 -0
- package/dist/chunk-WKEWRSDB.js.map +1 -0
- package/dist/chunk-Y7SAGNUT.js +66 -0
- package/dist/chunk-Y7SAGNUT.js.map +1 -0
- package/dist/chunk-YETJNRQM.js +39 -0
- package/dist/chunk-YETJNRQM.js.map +1 -0
- package/dist/chunk-YYSKGAZT.js +384 -0
- package/dist/chunk-YYSKGAZT.js.map +1 -0
- package/dist/chunk-ZZZWQGTS.js +169 -0
- package/dist/chunk-ZZZWQGTS.js.map +1 -0
- package/dist/claude-7LUVDZZ4.js +17 -0
- package/dist/claude-7LUVDZZ4.js.map +1 -0
- package/dist/cleanup-3LUWPSM7.js +412 -0
- package/dist/cleanup-3LUWPSM7.js.map +1 -0
- package/dist/cli-overrides-XFZWY7CM.js +16 -0
- package/dist/cli-overrides-XFZWY7CM.js.map +1 -0
- package/dist/cli.js +603 -0
- package/dist/cli.js.map +1 -0
- package/dist/color-ZVALX37U.js +21 -0
- package/dist/color-ZVALX37U.js.map +1 -0
- package/dist/enhance-XJIQHVPD.js +166 -0
- package/dist/enhance-XJIQHVPD.js.map +1 -0
- package/dist/env-MDFL4ZXL.js +23 -0
- package/dist/env-MDFL4ZXL.js.map +1 -0
- package/dist/feedback-23CLXKFT.js +158 -0
- package/dist/feedback-23CLXKFT.js.map +1 -0
- package/dist/finish-CY4CIH6O.js +1608 -0
- package/dist/finish-CY4CIH6O.js.map +1 -0
- package/dist/git-LVRZ57GJ.js +43 -0
- package/dist/git-LVRZ57GJ.js.map +1 -0
- package/dist/ignite-WXEF2ID5.js +359 -0
- package/dist/ignite-WXEF2ID5.js.map +1 -0
- package/dist/index.d.ts +1341 -0
- package/dist/index.js +3058 -0
- package/dist/index.js.map +1 -0
- package/dist/init-RHACUR4E.js +123 -0
- package/dist/init-RHACUR4E.js.map +1 -0
- package/dist/installation-detector-VARGFFRZ.js +11 -0
- package/dist/installation-detector-VARGFFRZ.js.map +1 -0
- package/dist/logger-MKYH4UDV.js +12 -0
- package/dist/logger-MKYH4UDV.js.map +1 -0
- package/dist/mcp/chunk-6SDFJ42P.js +62 -0
- package/dist/mcp/chunk-6SDFJ42P.js.map +1 -0
- package/dist/mcp/claude-YHHHLSXH.js +249 -0
- package/dist/mcp/claude-YHHHLSXH.js.map +1 -0
- package/dist/mcp/color-QS5BFCNN.js +168 -0
- package/dist/mcp/color-QS5BFCNN.js.map +1 -0
- package/dist/mcp/github-comment-server.js +165 -0
- package/dist/mcp/github-comment-server.js.map +1 -0
- package/dist/mcp/terminal-SDCMDVD7.js +202 -0
- package/dist/mcp/terminal-SDCMDVD7.js.map +1 -0
- package/dist/open-X6BTENPV.js +278 -0
- package/dist/open-X6BTENPV.js.map +1 -0
- package/dist/prompt-ANTQWHUF.js +13 -0
- package/dist/prompt-ANTQWHUF.js.map +1 -0
- package/dist/prompts/issue-prompt.txt +230 -0
- package/dist/prompts/pr-prompt.txt +35 -0
- package/dist/prompts/regular-prompt.txt +14 -0
- package/dist/run-2JCPQAX3.js +278 -0
- package/dist/run-2JCPQAX3.js.map +1 -0
- package/dist/schema/settings.schema.json +221 -0
- package/dist/start-LWVRBJ6S.js +982 -0
- package/dist/start-LWVRBJ6S.js.map +1 -0
- package/dist/terminal-3D6TUAKJ.js +16 -0
- package/dist/terminal-3D6TUAKJ.js.map +1 -0
- package/dist/test-git-XPF4SZXJ.js +52 -0
- package/dist/test-git-XPF4SZXJ.js.map +1 -0
- package/dist/test-prefix-XGFXFAYN.js +68 -0
- package/dist/test-prefix-XGFXFAYN.js.map +1 -0
- package/dist/test-tabs-JRKY3QMM.js +69 -0
- package/dist/test-tabs-JRKY3QMM.js.map +1 -0
- package/dist/test-webserver-M2I3EV4J.js +62 -0
- package/dist/test-webserver-M2I3EV4J.js.map +1 -0
- package/dist/update-3ZT2XX2G.js +79 -0
- package/dist/update-3ZT2XX2G.js.map +1 -0
- package/dist/update-notifier-QSSEB5KC.js +11 -0
- package/dist/update-notifier-QSSEB5KC.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger_default
|
|
4
|
+
} from "./chunk-6SDFJ42P.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/color.ts
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
function getColorPalette() {
|
|
9
|
+
return [
|
|
10
|
+
// First 10 colors preserved for backward compatibility
|
|
11
|
+
{ r: 220, g: 235, b: 248 },
|
|
12
|
+
// 0: Soft blue
|
|
13
|
+
{ r: 248, g: 220, b: 235 },
|
|
14
|
+
// 1: Soft pink
|
|
15
|
+
{ r: 220, g: 248, b: 235 },
|
|
16
|
+
// 2: Soft green
|
|
17
|
+
{ r: 248, g: 240, b: 220 },
|
|
18
|
+
// 3: Soft cream
|
|
19
|
+
{ r: 240, g: 220, b: 248 },
|
|
20
|
+
// 4: Soft lavender
|
|
21
|
+
{ r: 220, g: 240, b: 248 },
|
|
22
|
+
// 5: Soft cyan
|
|
23
|
+
{ r: 235, g: 235, b: 235 },
|
|
24
|
+
// 6: Soft grey
|
|
25
|
+
{ r: 228, g: 238, b: 248 },
|
|
26
|
+
// 7: Soft ice blue
|
|
27
|
+
{ r: 248, g: 228, b: 238 },
|
|
28
|
+
// 8: Soft rose
|
|
29
|
+
{ r: 228, g: 248, b: 238 },
|
|
30
|
+
// 9: Soft mint
|
|
31
|
+
// 30 new colors (indices 10-39)
|
|
32
|
+
{ r: 235, g: 245, b: 250 },
|
|
33
|
+
// 10: Pale sky blue
|
|
34
|
+
{ r: 250, g: 235, b: 245 },
|
|
35
|
+
// 11: Pale orchid
|
|
36
|
+
{ r: 235, g: 250, b: 245 },
|
|
37
|
+
// 12: Pale seafoam
|
|
38
|
+
{ r: 250, g: 245, b: 235 },
|
|
39
|
+
// 13: Pale peach
|
|
40
|
+
{ r: 245, g: 235, b: 250 },
|
|
41
|
+
// 14: Pale periwinkle
|
|
42
|
+
{ r: 235, g: 245, b: 235 },
|
|
43
|
+
// 15: Pale sage
|
|
44
|
+
{ r: 245, g: 250, b: 235 },
|
|
45
|
+
// 16: Pale lemon
|
|
46
|
+
{ r: 245, g: 235, b: 235 },
|
|
47
|
+
// 17: Pale blush
|
|
48
|
+
{ r: 235, g: 235, b: 250 },
|
|
49
|
+
// 18: Pale lavender blue
|
|
50
|
+
{ r: 250, g: 235, b: 235 },
|
|
51
|
+
// 19: Pale coral
|
|
52
|
+
{ r: 235, g: 250, b: 250 },
|
|
53
|
+
// 20: Pale aqua
|
|
54
|
+
{ r: 240, g: 248, b: 255 },
|
|
55
|
+
// 21: Alice blue
|
|
56
|
+
{ r: 255, g: 240, b: 248 },
|
|
57
|
+
// 22: Lavender blush
|
|
58
|
+
{ r: 240, g: 255, b: 248 },
|
|
59
|
+
// 23: Honeydew tint
|
|
60
|
+
{ r: 255, g: 248, b: 240 },
|
|
61
|
+
// 24: Antique white
|
|
62
|
+
{ r: 248, g: 240, b: 255 },
|
|
63
|
+
// 25: Magnolia
|
|
64
|
+
{ r: 240, g: 248, b: 240 },
|
|
65
|
+
// 26: Mint cream tint
|
|
66
|
+
{ r: 248, g: 255, b: 240 },
|
|
67
|
+
// 27: Ivory tint
|
|
68
|
+
{ r: 248, g: 240, b: 240 },
|
|
69
|
+
// 28: Misty rose tint
|
|
70
|
+
{ r: 240, g: 240, b: 255 },
|
|
71
|
+
// 29: Ghost white tint
|
|
72
|
+
{ r: 255, g: 245, b: 238 },
|
|
73
|
+
// 30: Seashell
|
|
74
|
+
{ r: 245, g: 255, b: 250 },
|
|
75
|
+
// 31: Azure mist
|
|
76
|
+
{ r: 250, g: 245, b: 255 },
|
|
77
|
+
// 32: Lilac mist
|
|
78
|
+
{ r: 255, g: 250, b: 245 },
|
|
79
|
+
// 33: Snow peach
|
|
80
|
+
{ r: 238, g: 245, b: 255 },
|
|
81
|
+
// 34: Powder blue
|
|
82
|
+
{ r: 255, g: 238, b: 245 },
|
|
83
|
+
// 35: Pink lace
|
|
84
|
+
{ r: 245, g: 255, b: 238 },
|
|
85
|
+
// 36: Pale lime
|
|
86
|
+
{ r: 238, g: 255, b: 245 },
|
|
87
|
+
// 37: Pale turquoise
|
|
88
|
+
{ r: 245, g: 238, b: 255 },
|
|
89
|
+
// 38: Pale violet
|
|
90
|
+
{ r: 255, g: 245, b: 255 }
|
|
91
|
+
// 39: Pale magenta
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
function rgbToHex(r, g, b) {
|
|
95
|
+
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
|
|
96
|
+
throw new Error("RGB values must be between 0 and 255");
|
|
97
|
+
}
|
|
98
|
+
const rHex = r.toString(16).padStart(2, "0");
|
|
99
|
+
const gHex = g.toString(16).padStart(2, "0");
|
|
100
|
+
const bHex = b.toString(16).padStart(2, "0");
|
|
101
|
+
return `#${rHex}${gHex}${bHex}`;
|
|
102
|
+
}
|
|
103
|
+
function hexToRgb(hex) {
|
|
104
|
+
const cleanHex = hex.startsWith("#") ? hex.slice(1) : hex;
|
|
105
|
+
if (cleanHex.length !== 6 || !/^[0-9a-fA-F]{6}$/.test(cleanHex)) {
|
|
106
|
+
throw new Error("Invalid hex color format. Expected format: #RRGGBB or RRGGBB");
|
|
107
|
+
}
|
|
108
|
+
const r = parseInt(cleanHex.slice(0, 2), 16);
|
|
109
|
+
const g = parseInt(cleanHex.slice(2, 4), 16);
|
|
110
|
+
const b = parseInt(cleanHex.slice(4, 6), 16);
|
|
111
|
+
return { r, g, b };
|
|
112
|
+
}
|
|
113
|
+
function generateColorFromBranchName(branchName) {
|
|
114
|
+
const hash = createHash("sha256").update(branchName).digest("hex");
|
|
115
|
+
const hashPrefix = hash.slice(0, 8);
|
|
116
|
+
const palette = getColorPalette();
|
|
117
|
+
const hashAsInt = parseInt(hashPrefix, 16);
|
|
118
|
+
const index = hashAsInt % palette.length;
|
|
119
|
+
logger_default.debug(`[generateColorFromBranchName] Branch name: ${branchName}, Hash: ${hash}, Hash prefix: ${hashPrefix}, Hash as int: ${hashAsInt}, Index: ${index}`);
|
|
120
|
+
const rgb = palette[index];
|
|
121
|
+
if (!rgb) {
|
|
122
|
+
throw new Error(`Invalid color index: ${index}`);
|
|
123
|
+
}
|
|
124
|
+
const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
|
|
125
|
+
return {
|
|
126
|
+
rgb,
|
|
127
|
+
hex,
|
|
128
|
+
index
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function lightenColor(rgb, amount) {
|
|
132
|
+
const clamp = (value) => Math.min(255, Math.max(0, Math.round(value)));
|
|
133
|
+
return {
|
|
134
|
+
r: clamp(rgb.r + (255 - rgb.r) * amount),
|
|
135
|
+
g: clamp(rgb.g + (255 - rgb.g) * amount),
|
|
136
|
+
b: clamp(rgb.b + (255 - rgb.b) * amount)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function saturateColor(rgb, amount) {
|
|
140
|
+
const clamp = (value) => Math.min(255, Math.max(0, Math.round(value)));
|
|
141
|
+
const avg = (rgb.r + rgb.g + rgb.b) / 3;
|
|
142
|
+
return {
|
|
143
|
+
r: clamp(rgb.r + (rgb.r - avg) * amount),
|
|
144
|
+
g: clamp(rgb.g + (rgb.g - avg) * amount),
|
|
145
|
+
b: clamp(rgb.b + (rgb.b - avg) * amount)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function calculateForegroundColor(rgb) {
|
|
149
|
+
const toLinear = (channel) => {
|
|
150
|
+
const c = channel / 255;
|
|
151
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
152
|
+
};
|
|
153
|
+
const r = toLinear(rgb.r);
|
|
154
|
+
const g = toLinear(rgb.g);
|
|
155
|
+
const b = toLinear(rgb.b);
|
|
156
|
+
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
157
|
+
return luminance > 0.5 ? "#000000" : "#ffffff";
|
|
158
|
+
}
|
|
159
|
+
export {
|
|
160
|
+
calculateForegroundColor,
|
|
161
|
+
generateColorFromBranchName,
|
|
162
|
+
getColorPalette,
|
|
163
|
+
hexToRgb,
|
|
164
|
+
lightenColor,
|
|
165
|
+
rgbToHex,
|
|
166
|
+
saturateColor
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=color-QS5BFCNN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/color.ts"],"sourcesContent":["import { createHash } from 'crypto'\nimport logger from './logger'\n\n/**\n * RGB color representation\n */\nexport interface RgbColor {\n\tr: number\n\tg: number\n\tb: number\n}\n\n/**\n * Complete color data with RGB, hex, and palette index\n */\nexport interface ColorData {\n\trgb: RgbColor\n\thex: string\n\tindex: number\n}\n\n/**\n * Get the predefined color palette (40 subtle, professional colors)\n * Matches the terminal color palette from bash/new-branch-workflow.sh\n *\n * @returns Array of 40 RGB colors\n */\nexport function getColorPalette(): RgbColor[] {\n\treturn [\n\t\t// First 10 colors preserved for backward compatibility\n\t\t{ r: 220, g: 235, b: 248 }, // 0: Soft blue\n\t\t{ r: 248, g: 220, b: 235 }, // 1: Soft pink\n\t\t{ r: 220, g: 248, b: 235 }, // 2: Soft green\n\t\t{ r: 248, g: 240, b: 220 }, // 3: Soft cream\n\t\t{ r: 240, g: 220, b: 248 }, // 4: Soft lavender\n\t\t{ r: 220, g: 240, b: 248 }, // 5: Soft cyan\n\t\t{ r: 235, g: 235, b: 235 }, // 6: Soft grey\n\t\t{ r: 228, g: 238, b: 248 }, // 7: Soft ice blue\n\t\t{ r: 248, g: 228, b: 238 }, // 8: Soft rose\n\t\t{ r: 228, g: 248, b: 238 }, // 9: Soft mint\n\t\t// 30 new colors (indices 10-39)\n\t\t{ r: 235, g: 245, b: 250 }, // 10: Pale sky blue\n\t\t{ r: 250, g: 235, b: 245 }, // 11: Pale orchid\n\t\t{ r: 235, g: 250, b: 245 }, // 12: Pale seafoam\n\t\t{ r: 250, g: 245, b: 235 }, // 13: Pale peach\n\t\t{ r: 245, g: 235, b: 250 }, // 14: Pale periwinkle\n\t\t{ r: 235, g: 245, b: 235 }, // 15: Pale sage\n\t\t{ r: 245, g: 250, b: 235 }, // 16: Pale lemon\n\t\t{ r: 245, g: 235, b: 235 }, // 17: Pale blush\n\t\t{ r: 235, g: 235, b: 250 }, // 18: Pale lavender blue\n\t\t{ r: 250, g: 235, b: 235 }, // 19: Pale coral\n\t\t{ r: 235, g: 250, b: 250 }, // 20: Pale aqua\n\t\t{ r: 240, g: 248, b: 255 }, // 21: Alice blue\n\t\t{ r: 255, g: 240, b: 248 }, // 22: Lavender blush\n\t\t{ r: 240, g: 255, b: 248 }, // 23: Honeydew tint\n\t\t{ r: 255, g: 248, b: 240 }, // 24: Antique white\n\t\t{ r: 248, g: 240, b: 255 }, // 25: Magnolia\n\t\t{ r: 240, g: 248, b: 240 }, // 26: Mint cream tint\n\t\t{ r: 248, g: 255, b: 240 }, // 27: Ivory tint\n\t\t{ r: 248, g: 240, b: 240 }, // 28: Misty rose tint\n\t\t{ r: 240, g: 240, b: 255 }, // 29: Ghost white tint\n\t\t{ r: 255, g: 245, b: 238 }, // 30: Seashell\n\t\t{ r: 245, g: 255, b: 250 }, // 31: Azure mist\n\t\t{ r: 250, g: 245, b: 255 }, // 32: Lilac mist\n\t\t{ r: 255, g: 250, b: 245 }, // 33: Snow peach\n\t\t{ r: 238, g: 245, b: 255 }, // 34: Powder blue\n\t\t{ r: 255, g: 238, b: 245 }, // 35: Pink lace\n\t\t{ r: 245, g: 255, b: 238 }, // 36: Pale lime\n\t\t{ r: 238, g: 255, b: 245 }, // 37: Pale turquoise\n\t\t{ r: 245, g: 238, b: 255 }, // 38: Pale violet\n\t\t{ r: 255, g: 245, b: 255 }, // 39: Pale magenta\n\t]\n}\n\n/**\n * Convert RGB values to hex color format\n *\n * @param r - Red value (0-255)\n * @param g - Green value (0-255)\n * @param b - Blue value (0-255)\n * @returns Hex color string (e.g., \"#dcebf8\")\n * @throws Error if RGB values are out of range\n */\nexport function rgbToHex(r: number, g: number, b: number): string {\n\t// Validate RGB values\n\tif (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {\n\t\tthrow new Error('RGB values must be between 0 and 255')\n\t}\n\n\t// Convert to hex and pad with zeros\n\tconst rHex = r.toString(16).padStart(2, '0')\n\tconst gHex = g.toString(16).padStart(2, '0')\n\tconst bHex = b.toString(16).padStart(2, '0')\n\n\treturn `#${rHex}${gHex}${bHex}`\n}\n\n/**\n * Convert hex color format to RGB values\n *\n * @param hex - Hex color string (with or without # prefix)\n * @returns RGB color object\n * @throws Error if hex format is invalid\n */\nexport function hexToRgb(hex: string): RgbColor {\n\t// Remove # prefix if present\n\tconst cleanHex = hex.startsWith('#') ? hex.slice(1) : hex\n\n\t// Validate format (must be exactly 6 hex characters)\n\tif (cleanHex.length !== 6 || !/^[0-9a-fA-F]{6}$/.test(cleanHex)) {\n\t\tthrow new Error('Invalid hex color format. Expected format: #RRGGBB or RRGGBB')\n\t}\n\n\t// Parse hex values\n\tconst r = parseInt(cleanHex.slice(0, 2), 16)\n\tconst g = parseInt(cleanHex.slice(2, 4), 16)\n\tconst b = parseInt(cleanHex.slice(4, 6), 16)\n\n\treturn { r, g, b }\n}\n\n/**\n * Generate deterministic color from branch name using SHA256 hash\n * Matches the bash implementation in bash/new-branch-workflow.sh\n *\n * @param branchName - Branch name to generate color from\n * @returns ColorData with RGB, hex, and palette index\n */\nexport function generateColorFromBranchName(branchName: string): ColorData {\n\t// Generate SHA256 hash of branch name\n\tconst hash = createHash('sha256').update(branchName).digest('hex')\n\n\t// Take first 8 hex characters and convert to index (0-39)\n\t// Matches bash: local index=$(( 0x$hash % ${#colors[@]} ))\n\tconst hashPrefix = hash.slice(0, 8)\n\tconst palette = getColorPalette()\n\tconst hashAsInt = parseInt(hashPrefix, 16)\n\tconst index = hashAsInt % palette.length\n\tlogger.debug(`[generateColorFromBranchName] Branch name: ${branchName}, Hash: ${hash}, Hash prefix: ${hashPrefix}, Hash as int: ${hashAsInt}, Index: ${index}`)\n\n\t// Get color from palette\n\tconst rgb = palette[index]\n\n\t// This should never happen as index is always in range [0, palette.length)\n\tif (!rgb) {\n\t\tthrow new Error(`Invalid color index: ${index}`)\n\t}\n\n\t// Convert to hex format\n\tconst hex = rgbToHex(rgb.r, rgb.g, rgb.b)\n\n\treturn {\n\t\trgb,\n\t\thex,\n\t\tindex,\n\t}\n}\n\n/**\n * Lighten a color by a given amount\n * Useful for creating slightly lighter variants for hover states\n *\n * @param rgb - RGB color to lighten\n * @param amount - Amount to lighten (0-1, where 0.1 = 10% lighter)\n * @returns Lightened RGB color\n */\nexport function lightenColor(rgb: RgbColor, amount: number): RgbColor {\n\tconst clamp = (value: number): number => Math.min(255, Math.max(0, Math.round(value)))\n\n\treturn {\n\t\tr: clamp(rgb.r + (255 - rgb.r) * amount),\n\t\tg: clamp(rgb.g + (255 - rgb.g) * amount),\n\t\tb: clamp(rgb.b + (255 - rgb.b) * amount),\n\t}\n}\n\n/**\n * Saturate a color by pushing it away from grey towards its dominant hue\n * Makes subtle colors more vivid while maintaining their hue\n *\n * @param rgb - RGB color to saturate\n * @param amount - Amount to saturate (0-1, where 0.4 = 40% more saturated)\n * @returns Saturated RGB color\n */\nexport function saturateColor(rgb: RgbColor, amount: number): RgbColor {\n\tconst clamp = (value: number): number => Math.min(255, Math.max(0, Math.round(value)))\n\n\t// Calculate average (grey point)\n\tconst avg = (rgb.r + rgb.g + rgb.b) / 3\n\n\t// Push each channel away from grey\n\treturn {\n\t\tr: clamp(rgb.r + (rgb.r - avg) * amount),\n\t\tg: clamp(rgb.g + (rgb.g - avg) * amount),\n\t\tb: clamp(rgb.b + (rgb.b - avg) * amount),\n\t}\n}\n\n/**\n * Calculate appropriate foreground color (black or white) for a given background\n * Uses relative luminance formula from WCAG 2.0\n *\n * @param rgb - Background RGB color\n * @returns '#000000' for light backgrounds, '#ffffff' for dark backgrounds\n */\nexport function calculateForegroundColor(rgb: RgbColor): string {\n\t// Convert RGB to relative luminance (WCAG 2.0 formula)\n\tconst toLinear = (channel: number): number => {\n\t\tconst c = channel / 255\n\t\treturn c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)\n\t}\n\n\tconst r = toLinear(rgb.r)\n\tconst g = toLinear(rgb.g)\n\tconst b = toLinear(rgb.b)\n\n\tconst luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b\n\n\t// Use black text for light backgrounds (luminance > 0.5)\n\t// Use white text for dark backgrounds\n\treturn luminance > 0.5 ? '#000000' : '#ffffff'\n}\n"],"mappings":";;;;;;AAAA,SAAS,kBAAkB;AA2BpB,SAAS,kBAA8B;AAC7C,SAAO;AAAA;AAAA,IAEN,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA;AAAA,IAEzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,IACzB,EAAE,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI;AAAA;AAAA,EAC1B;AACD;AAWO,SAAS,SAAS,GAAW,GAAW,GAAmB;AAEjE,MAAI,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK;AAC7D,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACvD;AAGA,QAAM,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC3C,QAAM,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC3C,QAAM,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAE3C,SAAO,IAAI,IAAI,GAAG,IAAI,GAAG,IAAI;AAC9B;AASO,SAAS,SAAS,KAAuB;AAE/C,QAAM,WAAW,IAAI,WAAW,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI;AAGtD,MAAI,SAAS,WAAW,KAAK,CAAC,mBAAmB,KAAK,QAAQ,GAAG;AAChE,UAAM,IAAI,MAAM,8DAA8D;AAAA,EAC/E;AAGA,QAAM,IAAI,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;AAC3C,QAAM,IAAI,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;AAC3C,QAAM,IAAI,SAAS,SAAS,MAAM,GAAG,CAAC,GAAG,EAAE;AAE3C,SAAO,EAAE,GAAG,GAAG,EAAE;AAClB;AASO,SAAS,4BAA4B,YAA+B;AAE1E,QAAM,OAAO,WAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK;AAIjE,QAAM,aAAa,KAAK,MAAM,GAAG,CAAC;AAClC,QAAM,UAAU,gBAAgB;AAChC,QAAM,YAAY,SAAS,YAAY,EAAE;AACzC,QAAM,QAAQ,YAAY,QAAQ;AAClC,iBAAO,MAAM,8CAA8C,UAAU,WAAW,IAAI,kBAAkB,UAAU,kBAAkB,SAAS,YAAY,KAAK,EAAE;AAG9J,QAAM,MAAM,QAAQ,KAAK;AAGzB,MAAI,CAAC,KAAK;AACT,UAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;AAAA,EAChD;AAGA,QAAM,MAAM,SAAS,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAExC,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAUO,SAAS,aAAa,KAAe,QAA0B;AACrE,QAAM,QAAQ,CAAC,UAA0B,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC;AAErF,SAAO;AAAA,IACN,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM;AAAA,IACvC,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM;AAAA,IACvC,GAAG,MAAM,IAAI,KAAK,MAAM,IAAI,KAAK,MAAM;AAAA,EACxC;AACD;AAUO,SAAS,cAAc,KAAe,QAA0B;AACtE,QAAM,QAAQ,CAAC,UAA0B,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC;AAGrF,QAAM,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK;AAGtC,SAAO;AAAA,IACN,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM;AAAA,IACvC,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM;AAAA,IACvC,GAAG,MAAM,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM;AAAA,EACxC;AACD;AASO,SAAS,yBAAyB,KAAuB;AAE/D,QAAM,WAAW,CAAC,YAA4B;AAC7C,UAAM,IAAI,UAAU;AACpB,WAAO,KAAK,UAAU,IAAI,QAAQ,KAAK,KAAK,IAAI,SAAS,OAAO,GAAG;AAAA,EACpE;AAEA,QAAM,IAAI,SAAS,IAAI,CAAC;AACxB,QAAM,IAAI,SAAS,IAAI,CAAC;AACxB,QAAM,IAAI,SAAS,IAAI,CAAC;AAExB,QAAM,YAAY,SAAS,IAAI,SAAS,IAAI,SAAS;AAIrD,SAAO,YAAY,MAAM,YAAY;AACtC;","names":[]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-6SDFJ42P.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp/github-comment-server.ts
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
|
|
11
|
+
// src/utils/github.ts
|
|
12
|
+
import { execa } from "execa";
|
|
13
|
+
async function executeGhCommand(args, options) {
|
|
14
|
+
const result = await execa("gh", args, {
|
|
15
|
+
cwd: (options == null ? void 0 : options.cwd) ?? process.cwd(),
|
|
16
|
+
timeout: (options == null ? void 0 : options.timeout) ?? 3e4,
|
|
17
|
+
encoding: "utf8"
|
|
18
|
+
});
|
|
19
|
+
const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json";
|
|
20
|
+
const data = isJson ? JSON.parse(result.stdout) : result.stdout;
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
async function createIssueComment(issueNumber, body) {
|
|
24
|
+
logger.debug("Creating issue comment", { issueNumber });
|
|
25
|
+
return executeGhCommand([
|
|
26
|
+
"api",
|
|
27
|
+
`repos/:owner/:repo/issues/${issueNumber}/comments`,
|
|
28
|
+
"-f",
|
|
29
|
+
`body=${body}`,
|
|
30
|
+
"--jq",
|
|
31
|
+
"{id: .id, url: .html_url, created_at: .created_at}"
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
async function updateIssueComment(commentId, body) {
|
|
35
|
+
logger.debug("Updating issue comment", { commentId });
|
|
36
|
+
return executeGhCommand([
|
|
37
|
+
"api",
|
|
38
|
+
`repos/:owner/:repo/issues/comments/${commentId}`,
|
|
39
|
+
"-X",
|
|
40
|
+
"PATCH",
|
|
41
|
+
"-f",
|
|
42
|
+
`body=${body}`,
|
|
43
|
+
"--jq",
|
|
44
|
+
"{id: .id, url: .html_url, updated_at: .updated_at}"
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
async function createPRComment(prNumber, body) {
|
|
48
|
+
logger.debug("Creating PR comment", { prNumber });
|
|
49
|
+
return executeGhCommand([
|
|
50
|
+
"api",
|
|
51
|
+
`repos/:owner/:repo/issues/${prNumber}/comments`,
|
|
52
|
+
"-f",
|
|
53
|
+
`body=${body}`,
|
|
54
|
+
"--jq",
|
|
55
|
+
"{id: .id, url: .html_url, created_at: .created_at}"
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/mcp/github-comment-server.ts
|
|
60
|
+
function validateEnvironment() {
|
|
61
|
+
const required = ["REPO_OWNER", "REPO_NAME"];
|
|
62
|
+
const missing = required.filter((key) => !process.env[key]);
|
|
63
|
+
if (missing.length > 0) {
|
|
64
|
+
console.error(
|
|
65
|
+
`Missing required environment variables: ${missing.join(", ")}`
|
|
66
|
+
);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
var server = new McpServer({
|
|
71
|
+
name: "github-comment-broker",
|
|
72
|
+
version: "0.1.0"
|
|
73
|
+
});
|
|
74
|
+
server.registerTool(
|
|
75
|
+
"create_comment",
|
|
76
|
+
{
|
|
77
|
+
title: "Create GitHub Comment",
|
|
78
|
+
description: "Create a new comment on a GitHub issue or pull request. Use this to start tracking a workflow phase.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
number: z.number().describe("The issue or PR number"),
|
|
81
|
+
body: z.string().describe("The comment body (markdown supported)"),
|
|
82
|
+
type: z.enum(["issue", "pr"]).describe("Type of entity to comment on (issue or pr)")
|
|
83
|
+
},
|
|
84
|
+
outputSchema: {
|
|
85
|
+
id: z.number(),
|
|
86
|
+
url: z.string(),
|
|
87
|
+
created_at: z.string().optional()
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
async ({ number, body, type }) => {
|
|
91
|
+
console.error(`Creating ${type} comment on #${number}`);
|
|
92
|
+
try {
|
|
93
|
+
const result = type === "issue" ? await createIssueComment(number, body) : await createPRComment(number, body);
|
|
94
|
+
console.error(
|
|
95
|
+
`Comment created successfully: ${result.id} at ${result.url}`
|
|
96
|
+
);
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: JSON.stringify(result)
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
structuredContent: result
|
|
105
|
+
};
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
108
|
+
console.error(`Failed to create comment: ${errorMessage}`);
|
|
109
|
+
throw new Error(`Failed to create ${type} comment: ${errorMessage}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
server.registerTool(
|
|
114
|
+
"update_comment",
|
|
115
|
+
{
|
|
116
|
+
title: "Update GitHub Comment",
|
|
117
|
+
description: "Update an existing GitHub comment. Use this to update progress during a workflow phase.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
commentId: z.number().describe("The GitHub comment ID to update"),
|
|
120
|
+
body: z.string().describe("The updated comment body (markdown supported)")
|
|
121
|
+
},
|
|
122
|
+
outputSchema: {
|
|
123
|
+
id: z.number(),
|
|
124
|
+
url: z.string(),
|
|
125
|
+
updated_at: z.string().optional()
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
async ({ commentId, body }) => {
|
|
129
|
+
console.error(`Updating comment ${commentId}`);
|
|
130
|
+
try {
|
|
131
|
+
const result = await updateIssueComment(commentId, body);
|
|
132
|
+
console.error(
|
|
133
|
+
`Comment updated successfully: ${result.id} at ${result.url}`
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{
|
|
138
|
+
type: "text",
|
|
139
|
+
text: JSON.stringify(result)
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
structuredContent: result
|
|
143
|
+
};
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
146
|
+
console.error(`Failed to update comment: ${errorMessage}`);
|
|
147
|
+
throw new Error(`Failed to update comment: ${errorMessage}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
async function main() {
|
|
152
|
+
console.error("Starting GitHub Comment MCP Server...");
|
|
153
|
+
validateEnvironment();
|
|
154
|
+
console.error("Environment validated");
|
|
155
|
+
console.error(`Repository: ${process.env.REPO_OWNER}/${process.env.REPO_NAME}`);
|
|
156
|
+
console.error(`Event type: ${process.env.GITHUB_EVENT_NAME ?? "not specified"}`);
|
|
157
|
+
const transport = new StdioServerTransport();
|
|
158
|
+
await server.connect(transport);
|
|
159
|
+
console.error("GitHub Comment MCP Server running on stdio transport");
|
|
160
|
+
}
|
|
161
|
+
main().catch((error) => {
|
|
162
|
+
console.error("Fatal error starting MCP server:", error);
|
|
163
|
+
process.exit(1);
|
|
164
|
+
});
|
|
165
|
+
//# sourceMappingURL=github-comment-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/github-comment-server.ts","../../src/utils/github.ts"],"sourcesContent":["/**\n * GitHub Comment MCP Server\n *\n * A Model Context Protocol server that enables Claude to create and update\n * GitHub comments during issue/PR workflows. Uses gh CLI for all GitHub operations.\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport {\n\tcreateIssueComment,\n\tupdateIssueComment,\n\tcreatePRComment,\n} from '../utils/github.js'\nimport type {\n\tCreateCommentInput,\n\tUpdateCommentInput,\n} from './types.js'\n\n// Validate required environment variables\nfunction validateEnvironment(): void {\n\tconst required = ['REPO_OWNER', 'REPO_NAME']\n\tconst missing = required.filter((key) => !process.env[key])\n\n\tif (missing.length > 0) {\n\t\tconsole.error(\n\t\t\t`Missing required environment variables: ${missing.join(', ')}`\n\t\t)\n\t\tprocess.exit(1)\n\t}\n}\n\n// Initialize the MCP server\nconst server = new McpServer({\n\tname: 'github-comment-broker',\n\tversion: '0.1.0',\n})\n\n// Register create_comment tool\nserver.registerTool(\n\t'create_comment',\n\t{\n\t\ttitle: 'Create GitHub Comment',\n\t\tdescription:\n\t\t\t'Create a new comment on a GitHub issue or pull request. Use this to start tracking a workflow phase.',\n\t\tinputSchema: {\n\t\t\tnumber: z.number().describe('The issue or PR number'),\n\t\t\tbody: z.string().describe('The comment body (markdown supported)'),\n\t\t\ttype: z\n\t\t\t\t.enum(['issue', 'pr'])\n\t\t\t\t.describe('Type of entity to comment on (issue or pr)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tcreated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ number, body, type }: CreateCommentInput) => {\n\t\tconsole.error(`Creating ${type} comment on #${number}`)\n\n\t\ttry {\n\t\t\tconst result =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? await createIssueComment(number, body)\n\t\t\t\t\t: await createPRComment(number, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment created successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to create comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to create ${type} comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Register update_comment tool\nserver.registerTool(\n\t'update_comment',\n\t{\n\t\ttitle: 'Update GitHub Comment',\n\t\tdescription:\n\t\t\t'Update an existing GitHub comment. Use this to update progress during a workflow phase.',\n\t\tinputSchema: {\n\t\t\tcommentId: z.number().describe('The GitHub comment ID to update'),\n\t\t\tbody: z.string().describe('The updated comment body (markdown supported)'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.number(),\n\t\t\turl: z.string(),\n\t\t\tupdated_at: z.string().optional(),\n\t\t},\n\t},\n\tasync ({ commentId, body }: UpdateCommentInput) => {\n\t\tconsole.error(`Updating comment ${commentId}`)\n\n\t\ttry {\n\t\t\tconst result = await updateIssueComment(commentId, body)\n\n\t\t\tconsole.error(\n\t\t\t\t`Comment updated successfully: ${result.id} at ${result.url}`\n\t\t\t)\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text' as const,\n\t\t\t\t\t\ttext: JSON.stringify(result),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t\tstructuredContent: result as unknown as { [x: string]: unknown },\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error'\n\t\t\tconsole.error(`Failed to update comment: ${errorMessage}`)\n\t\t\tthrow new Error(`Failed to update comment: ${errorMessage}`)\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('Starting GitHub Comment MCP Server...')\n\n\t// Validate environment\n\tvalidateEnvironment()\n\tconsole.error('Environment validated')\n\tconsole.error(`Repository: ${process.env.REPO_OWNER}/${process.env.REPO_NAME}`)\n\tconsole.error(`Event type: ${process.env.GITHUB_EVENT_NAME ?? 'not specified'}`)\n\n\t// Connect stdio transport\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\n\tconsole.error('GitHub Comment MCP Server running on stdio transport')\n}\n\n// Run the server\nmain().catch((error) => {\n\tconsole.error('Fatal error starting MCP server:', error)\n\tprocess.exit(1)\n})\n","import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output\n\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\tconst userMatch = output.match(/Logged in to github\\.com as ([^\\s]+)/)\n\n\t\tconst username = userMatch?.[1]\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes: scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? [],\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber })\n\n\treturn executeGhCommand<GitHubPullRequest>([\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t])\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber })\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/${issueNumber}/comments`,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId })\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/comments/${commentId}`,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber })\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/${prNumber}/comments`,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":";;;;;;AAOA,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,SAAS;;;ACTlB,SAAS,aAAa;AAatB,eAAsB,iBACrB,MACA,SACa;AACb,QAAM,SAAS,MAAM,MAAM,MAAM,MAAM;AAAA,IACtC,MAAK,mCAAS,QAAO,QAAQ,IAAI;AAAA,IACjC,UAAS,mCAAS,YAAW;AAAA,IAC7B,UAAU;AAAA,EACX,CAAC;AAGD,QAAM,SACL,KAAK,SAAS,QAAQ,KACtB,KAAK,SAAS,MAAM,KACnB,KAAK,SAAS,UAAU,KAAK,KAAK,KAAK,QAAQ,UAAU,IAAI,CAAC,MAAM;AACtE,QAAM,OAAO,SAAS,KAAK,MAAM,OAAO,MAAM,IAAI,OAAO;AAEzD,SAAO;AACR;AAuTA,eAAsB,mBACrB,aACA,MAC2B;AAC3B,SAAO,MAAM,0BAA0B,EAAE,YAAY,CAAC;AAEtD,SAAO,iBAAkC;AAAA,IACxC;AAAA,IACA,6BAA6B,WAAW;AAAA,IACxC;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAQA,eAAsB,mBACrB,WACA,MAC2B;AAC3B,SAAO,MAAM,0BAA0B,EAAE,UAAU,CAAC;AAEpD,SAAO,iBAAkC;AAAA,IACxC;AAAA,IACA,sCAAsC,SAAS;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,EACD,CAAC;AACF;AASA,eAAsB,gBACrB,UACA,MAC2B;AAC3B,SAAO,MAAM,uBAAuB,EAAE,SAAS,CAAC;AAGhD,SAAO,iBAAkC;AAAA,IACxC;AAAA,IACA,6BAA6B,QAAQ;AAAA,IACrC;AAAA,IACA,QAAQ,IAAI;AAAA,IACZ;AAAA,IACA;AAAA,EACD,CAAC;AACF;;;AD/XA,SAAS,sBAA4B;AACpC,QAAM,WAAW,CAAC,cAAc,WAAW;AAC3C,QAAM,UAAU,SAAS,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC;AAE1D,MAAI,QAAQ,SAAS,GAAG;AACvB,YAAQ;AAAA,MACP,2CAA2C,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC9D;AACA,YAAQ,KAAK,CAAC;AAAA,EACf;AACD;AAGA,IAAM,SAAS,IAAI,UAAU;AAAA,EAC5B,MAAM;AAAA,EACN,SAAS;AACV,CAAC;AAGD,OAAO;AAAA,EACN;AAAA,EACA;AAAA,IACC,OAAO;AAAA,IACP,aACC;AAAA,IACD,aAAa;AAAA,MACZ,QAAQ,EAAE,OAAO,EAAE,SAAS,wBAAwB;AAAA,MACpD,MAAM,EAAE,OAAO,EAAE,SAAS,uCAAuC;AAAA,MACjE,MAAM,EACJ,KAAK,CAAC,SAAS,IAAI,CAAC,EACpB,SAAS,4CAA4C;AAAA,IACxD;AAAA,IACA,cAAc;AAAA,MACb,IAAI,EAAE,OAAO;AAAA,MACb,KAAK,EAAE,OAAO;AAAA,MACd,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,IACjC;AAAA,EACD;AAAA,EACA,OAAO,EAAE,QAAQ,MAAM,KAAK,MAA0B;AACrD,YAAQ,MAAM,YAAY,IAAI,gBAAgB,MAAM,EAAE;AAEtD,QAAI;AACH,YAAM,SACL,SAAS,UACN,MAAM,mBAAmB,QAAQ,IAAI,IACrC,MAAM,gBAAgB,QAAQ,IAAI;AAEtC,cAAQ;AAAA,QACP,iCAAiC,OAAO,EAAE,OAAO,OAAO,GAAG;AAAA,MAC5D;AAEA,aAAO;AAAA,QACN,SAAS;AAAA,UACR;AAAA,YACC,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,MAAM;AAAA,UAC5B;AAAA,QACD;AAAA,QACA,mBAAmB;AAAA,MACpB;AAAA,IACD,SAAS,OAAO;AACf,YAAM,eACL,iBAAiB,QAAQ,MAAM,UAAU;AAC1C,cAAQ,MAAM,6BAA6B,YAAY,EAAE;AACzD,YAAM,IAAI,MAAM,oBAAoB,IAAI,aAAa,YAAY,EAAE;AAAA,IACpE;AAAA,EACD;AACD;AAGA,OAAO;AAAA,EACN;AAAA,EACA;AAAA,IACC,OAAO;AAAA,IACP,aACC;AAAA,IACD,aAAa;AAAA,MACZ,WAAW,EAAE,OAAO,EAAE,SAAS,iCAAiC;AAAA,MAChE,MAAM,EAAE,OAAO,EAAE,SAAS,+CAA+C;AAAA,IAC1E;AAAA,IACA,cAAc;AAAA,MACb,IAAI,EAAE,OAAO;AAAA,MACb,KAAK,EAAE,OAAO;AAAA,MACd,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,IACjC;AAAA,EACD;AAAA,EACA,OAAO,EAAE,WAAW,KAAK,MAA0B;AAClD,YAAQ,MAAM,oBAAoB,SAAS,EAAE;AAE7C,QAAI;AACH,YAAM,SAAS,MAAM,mBAAmB,WAAW,IAAI;AAEvD,cAAQ;AAAA,QACP,iCAAiC,OAAO,EAAE,OAAO,OAAO,GAAG;AAAA,MAC5D;AAEA,aAAO;AAAA,QACN,SAAS;AAAA,UACR;AAAA,YACC,MAAM;AAAA,YACN,MAAM,KAAK,UAAU,MAAM;AAAA,UAC5B;AAAA,QACD;AAAA,QACA,mBAAmB;AAAA,MACpB;AAAA,IACD,SAAS,OAAO;AACf,YAAM,eACL,iBAAiB,QAAQ,MAAM,UAAU;AAC1C,cAAQ,MAAM,6BAA6B,YAAY,EAAE;AACzD,YAAM,IAAI,MAAM,6BAA6B,YAAY,EAAE;AAAA,IAC5D;AAAA,EACD;AACD;AAGA,eAAe,OAAsB;AACpC,UAAQ,MAAM,uCAAuC;AAGrD,sBAAoB;AACpB,UAAQ,MAAM,uBAAuB;AACrC,UAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,IAAI,QAAQ,IAAI,SAAS,EAAE;AAC9E,UAAQ,MAAM,eAAe,QAAQ,IAAI,qBAAqB,eAAe,EAAE;AAG/E,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAE9B,UAAQ,MAAM,sDAAsD;AACrE;AAGA,KAAK,EAAE,MAAM,CAAC,UAAU;AACvB,UAAQ,MAAM,oCAAoC,KAAK;AACvD,UAAQ,KAAK,CAAC;AACf,CAAC;","names":[]}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/terminal.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
function detectPlatform() {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
if (platform === "darwin") return "darwin";
|
|
9
|
+
if (platform === "linux") return "linux";
|
|
10
|
+
if (platform === "win32") return "win32";
|
|
11
|
+
return "unsupported";
|
|
12
|
+
}
|
|
13
|
+
async function detectITerm2() {
|
|
14
|
+
const platform = detectPlatform();
|
|
15
|
+
if (platform !== "darwin") return false;
|
|
16
|
+
return existsSync("/Applications/iTerm.app");
|
|
17
|
+
}
|
|
18
|
+
async function openTerminalWindow(options) {
|
|
19
|
+
const platform = detectPlatform();
|
|
20
|
+
if (platform !== "darwin") {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const applescript = buildAppleScript(options);
|
|
26
|
+
try {
|
|
27
|
+
await execa("osascript", ["-e", applescript]);
|
|
28
|
+
await execa("osascript", ["-e", 'tell application "Terminal" to activate']);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Failed to open terminal window: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function buildAppleScript(options) {
|
|
36
|
+
const {
|
|
37
|
+
workspacePath,
|
|
38
|
+
command,
|
|
39
|
+
backgroundColor,
|
|
40
|
+
port,
|
|
41
|
+
includeEnvSetup,
|
|
42
|
+
includePortExport
|
|
43
|
+
} = options;
|
|
44
|
+
const commands = [];
|
|
45
|
+
if (workspacePath) {
|
|
46
|
+
commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
|
|
47
|
+
}
|
|
48
|
+
if (includeEnvSetup) {
|
|
49
|
+
commands.push("source .env");
|
|
50
|
+
}
|
|
51
|
+
if (includePortExport && port !== void 0) {
|
|
52
|
+
commands.push(`export PORT=${port}`);
|
|
53
|
+
}
|
|
54
|
+
if (command) {
|
|
55
|
+
commands.push(command);
|
|
56
|
+
}
|
|
57
|
+
const fullCommand = commands.join(" && ");
|
|
58
|
+
const historyFreeCommand = ` ${fullCommand}`;
|
|
59
|
+
let script = `tell application "Terminal"
|
|
60
|
+
`;
|
|
61
|
+
script += ` set newTab to do script "${escapeForAppleScript(historyFreeCommand)}"
|
|
62
|
+
`;
|
|
63
|
+
if (backgroundColor) {
|
|
64
|
+
const { r, g, b } = backgroundColor;
|
|
65
|
+
script += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
script += `end tell`;
|
|
69
|
+
return script;
|
|
70
|
+
}
|
|
71
|
+
function escapePathForAppleScript(path) {
|
|
72
|
+
return path.replace(/'/g, "'\\''");
|
|
73
|
+
}
|
|
74
|
+
function escapeForAppleScript(command) {
|
|
75
|
+
return command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
76
|
+
}
|
|
77
|
+
function buildCommandSequence(options) {
|
|
78
|
+
const {
|
|
79
|
+
workspacePath,
|
|
80
|
+
command,
|
|
81
|
+
port,
|
|
82
|
+
includeEnvSetup,
|
|
83
|
+
includePortExport
|
|
84
|
+
} = options;
|
|
85
|
+
const commands = [];
|
|
86
|
+
if (workspacePath) {
|
|
87
|
+
commands.push(`cd '${escapePathForAppleScript(workspacePath)}'`);
|
|
88
|
+
}
|
|
89
|
+
if (includeEnvSetup) {
|
|
90
|
+
commands.push("source .env");
|
|
91
|
+
}
|
|
92
|
+
if (includePortExport && port !== void 0) {
|
|
93
|
+
commands.push(`export PORT=${port}`);
|
|
94
|
+
}
|
|
95
|
+
if (command) {
|
|
96
|
+
commands.push(command);
|
|
97
|
+
}
|
|
98
|
+
const fullCommand = commands.join(" && ");
|
|
99
|
+
return ` ${fullCommand}`;
|
|
100
|
+
}
|
|
101
|
+
function buildITerm2MultiTabScript(optionsArray) {
|
|
102
|
+
if (optionsArray.length < 2) {
|
|
103
|
+
throw new Error("buildITerm2MultiTabScript requires at least 2 terminal options");
|
|
104
|
+
}
|
|
105
|
+
let script = 'tell application id "com.googlecode.iterm2"\n';
|
|
106
|
+
script += " create window with default profile\n";
|
|
107
|
+
script += " set newWindow to current window\n";
|
|
108
|
+
const options1 = optionsArray[0];
|
|
109
|
+
if (!options1) {
|
|
110
|
+
throw new Error("First terminal option is undefined");
|
|
111
|
+
}
|
|
112
|
+
const command1 = buildCommandSequence(options1);
|
|
113
|
+
script += " set s1 to current session of newWindow\n\n";
|
|
114
|
+
if (options1.backgroundColor) {
|
|
115
|
+
const { r, g, b } = options1.backgroundColor;
|
|
116
|
+
script += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
script += ` tell s1 to write text "${escapeForAppleScript(command1)}"
|
|
120
|
+
|
|
121
|
+
`;
|
|
122
|
+
if (options1.title) {
|
|
123
|
+
script += ` set name of s1 to "${escapeForAppleScript(options1.title)}"
|
|
124
|
+
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
for (let i = 1; i < optionsArray.length; i++) {
|
|
128
|
+
const options = optionsArray[i];
|
|
129
|
+
if (!options) {
|
|
130
|
+
throw new Error(`Terminal option at index ${i} is undefined`);
|
|
131
|
+
}
|
|
132
|
+
const command = buildCommandSequence(options);
|
|
133
|
+
const sessionVar = `s${i + 1}`;
|
|
134
|
+
script += " tell newWindow\n";
|
|
135
|
+
script += ` set newTab${i} to (create tab with default profile)
|
|
136
|
+
`;
|
|
137
|
+
script += " end tell\n";
|
|
138
|
+
script += ` set ${sessionVar} to current session of newTab${i}
|
|
139
|
+
|
|
140
|
+
`;
|
|
141
|
+
if (options.backgroundColor) {
|
|
142
|
+
const { r, g, b } = options.backgroundColor;
|
|
143
|
+
script += ` set background color of ${sessionVar} to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
script += ` tell ${sessionVar} to write text "${escapeForAppleScript(command)}"
|
|
147
|
+
|
|
148
|
+
`;
|
|
149
|
+
if (options.title) {
|
|
150
|
+
script += ` set name of ${sessionVar} to "${escapeForAppleScript(options.title)}"
|
|
151
|
+
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
script += " activate\n";
|
|
156
|
+
script += "end tell";
|
|
157
|
+
return script;
|
|
158
|
+
}
|
|
159
|
+
async function openMultipleTerminalWindows(optionsArray) {
|
|
160
|
+
if (optionsArray.length < 2) {
|
|
161
|
+
throw new Error("openMultipleTerminalWindows requires at least 2 terminal options. Use openTerminalWindow for single terminal.");
|
|
162
|
+
}
|
|
163
|
+
const platform = detectPlatform();
|
|
164
|
+
if (platform !== "darwin") {
|
|
165
|
+
throw new Error(
|
|
166
|
+
`Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const hasITerm2 = await detectITerm2();
|
|
170
|
+
if (hasITerm2) {
|
|
171
|
+
const applescript = buildITerm2MultiTabScript(optionsArray);
|
|
172
|
+
try {
|
|
173
|
+
await execa("osascript", ["-e", applescript]);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Failed to open iTerm2 window: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
for (let i = 0; i < optionsArray.length; i++) {
|
|
181
|
+
const options = optionsArray[i];
|
|
182
|
+
if (!options) {
|
|
183
|
+
throw new Error(`Terminal option at index ${i} is undefined`);
|
|
184
|
+
}
|
|
185
|
+
await openTerminalWindow(options);
|
|
186
|
+
if (i < optionsArray.length - 1) {
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
async function openDualTerminalWindow(options1, options2) {
|
|
193
|
+
await openMultipleTerminalWindows([options1, options2]);
|
|
194
|
+
}
|
|
195
|
+
export {
|
|
196
|
+
detectITerm2,
|
|
197
|
+
detectPlatform,
|
|
198
|
+
openDualTerminalWindow,
|
|
199
|
+
openMultipleTerminalWindows,
|
|
200
|
+
openTerminalWindow
|
|
201
|
+
};
|
|
202
|
+
//# sourceMappingURL=terminal-SDCMDVD7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/terminal.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport type { Platform } from '../types/index.js'\n\nexport interface TerminalWindowOptions {\n\tworkspacePath?: string\n\tcommand?: string\n\tbackgroundColor?: { r: number; g: number; b: number }\n\tport?: number\n\tincludeEnvSetup?: boolean // source .env\n\tincludePortExport?: boolean // export PORT=<port>\n\ttitle?: string // Terminal tab title\n}\n\n/**\n * Detect current platform\n */\nexport function detectPlatform(): Platform {\n\tconst platform = process.platform\n\tif (platform === 'darwin') return 'darwin'\n\tif (platform === 'linux') return 'linux'\n\tif (platform === 'win32') return 'win32'\n\treturn 'unsupported'\n}\n\n/**\n * Detect if iTerm2 is installed on macOS\n * Returns false on non-macOS platforms\n */\nexport async function detectITerm2(): Promise<boolean> {\n\tconst platform = detectPlatform()\n\tif (platform !== 'darwin') return false\n\n\t// Check if iTerm.app exists at standard location\n\treturn existsSync('/Applications/iTerm.app')\n}\n\n/**\n * Open new terminal window with specified options\n * Currently supports macOS only\n */\nexport async function openTerminalWindow(\n\toptions: TerminalWindowOptions\n): Promise<void> {\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// macOS implementation using AppleScript\n\tconst applescript = buildAppleScript(options)\n\n\ttry {\n\t\tawait execa('osascript', ['-e', applescript])\n\n\t\t// Activate Terminal.app to bring windows to front\n\t\tawait execa('osascript', ['-e', 'tell application \"Terminal\" to activate'])\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to open terminal window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t)\n\t}\n}\n\n/**\n * Build AppleScript for macOS Terminal.app\n */\nfunction buildAppleScript(options: TerminalWindowOptions): string {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tbackgroundColor,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\t// Build command sequence\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source .env file\n\tif (includeEnvSetup) {\n\t\tcommands.push('source .env')\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\t// Most shells (bash/zsh) ignore commands starting with space when HISTCONTROL=ignorespace\n\tconst historyFreeCommand = ` ${fullCommand}`\n\n\t// Build AppleScript\n\tlet script = `tell application \"Terminal\"\\n`\n\tscript += ` set newTab to do script \"${escapeForAppleScript(historyFreeCommand)}\"\\n`\n\n\t// Apply background color if provided\n\tif (backgroundColor) {\n\t\tconst { r, g, b } = backgroundColor\n\t\t// Convert 8-bit RGB (0-255) to 16-bit RGB (0-65535)\n\t\tscript += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\tscript += `end tell`\n\n\treturn script\n}\n\n/**\n * Escape path for AppleScript string\n * Single quotes in path need special escaping\n */\nfunction escapePathForAppleScript(path: string): string {\n\t// Replace single quote with '\\''\n\treturn path.replace(/'/g, \"'\\\\''\")\n}\n\n/**\n * Escape command for AppleScript do script\n * Must handle double quotes and backslashes\n */\nfunction escapeForAppleScript(command: string): string {\n\treturn (\n\t\tcommand\n\t\t\t.replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n\t\t\t.replace(/\"/g, '\\\\\"') // Escape double quotes\n\t)\n}\n\n/**\n * Build command sequence for terminal\n */\nfunction buildCommandSequence(options: TerminalWindowOptions): string {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source .env file\n\tif (includeEnvSetup) {\n\t\tcommands.push('source .env')\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\treturn ` ${fullCommand}`\n}\n\n/**\n * Build iTerm2 AppleScript for multiple tabs (2+) in single window\n */\nfunction buildITerm2MultiTabScript(\n\toptionsArray: TerminalWindowOptions[]\n): string {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('buildITerm2MultiTabScript requires at least 2 terminal options')\n\t}\n\n\tlet script = 'tell application id \"com.googlecode.iterm2\"\\n'\n\tscript += ' create window with default profile\\n'\n\tscript += ' set newWindow to current window\\n'\n\n\t// First tab\n\tconst options1 = optionsArray[0]\n\tif (!options1) {\n\t\tthrow new Error('First terminal option is undefined')\n\t}\n\tconst command1 = buildCommandSequence(options1)\n\n\tscript += ' set s1 to current session of newWindow\\n\\n'\n\n\t// Set background color for first tab\n\tif (options1.backgroundColor) {\n\t\tconst { r, g, b } = options1.backgroundColor\n\t\tscript += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\t// Execute command in first tab\n\tscript += ` tell s1 to write text \"${escapeForAppleScript(command1)}\"\\n\\n`\n\n\t// Set tab title for first tab\n\tif (options1.title) {\n\t\tscript += ` set name of s1 to \"${escapeForAppleScript(options1.title)}\"\\n\\n`\n\t}\n\n\t// Subsequent tabs (2, 3, ...)\n\tfor (let i = 1; i < optionsArray.length; i++) {\n\t\tconst options = optionsArray[i]\n\t\tif (!options) {\n\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t}\n\t\tconst command = buildCommandSequence(options)\n\t\tconst sessionVar = `s${i + 1}`\n\n\t\t// Create tab\n\t\tscript += ' tell newWindow\\n'\n\t\tscript += ` set newTab${i} to (create tab with default profile)\\n`\n\t\tscript += ' end tell\\n'\n\t\tscript += ` set ${sessionVar} to current session of newTab${i}\\n\\n`\n\n\t\t// Set background color\n\t\tif (options.backgroundColor) {\n\t\t\tconst { r, g, b } = options.backgroundColor\n\t\t\tscript += ` set background color of ${sessionVar} to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t\t}\n\n\t\t// Execute command\n\t\tscript += ` tell ${sessionVar} to write text \"${escapeForAppleScript(command)}\"\\n\\n`\n\n\t\t// Set tab title\n\t\tif (options.title) {\n\t\t\tscript += ` set name of ${sessionVar} to \"${escapeForAppleScript(options.title)}\"\\n\\n`\n\t\t}\n\t}\n\n\t// Activate iTerm2\n\tscript += ' activate\\n'\n\tscript += 'end tell'\n\n\treturn script\n}\n\n/**\n * Open multiple terminal windows/tabs (2+) with specified options\n * If iTerm2 is available on macOS, creates single window with multiple tabs\n * Otherwise falls back to multiple separate Terminal.app windows\n */\nexport async function openMultipleTerminalWindows(\n\toptionsArray: TerminalWindowOptions[]\n): Promise<void> {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('openMultipleTerminalWindows requires at least 2 terminal options. Use openTerminalWindow for single terminal.')\n\t}\n\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// Detect if iTerm2 is available\n\tconst hasITerm2 = await detectITerm2()\n\n\tif (hasITerm2) {\n\t\t// Use iTerm2 with multiple tabs in single window\n\t\tconst applescript = buildITerm2MultiTabScript(optionsArray)\n\n\t\ttry {\n\t\t\tawait execa('osascript', ['-e', applescript])\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to open iTerm2 window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t} else {\n\t\t// Fall back to multiple Terminal.app windows\n\t\tfor (let i = 0; i < optionsArray.length; i++) {\n\t\t\tconst options = optionsArray[i]\n\t\t\tif (!options) {\n\t\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t\t}\n\t\t\tawait openTerminalWindow(options)\n\n\t\t\t// Brief pause between terminals (except after last one)\n\t\t\tif (i < optionsArray.length - 1) {\n\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 1000))\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Open dual terminal windows/tabs with specified options\n * If iTerm2 is available on macOS, creates single window with two tabs\n * Otherwise falls back to two separate Terminal.app windows\n */\nexport async function openDualTerminalWindow(\n\toptions1: TerminalWindowOptions,\n\toptions2: TerminalWindowOptions\n): Promise<void> {\n\t// Delegate to openMultipleTerminalWindows for consistency\n\tawait openMultipleTerminalWindows([options1, options2])\n}\n"],"mappings":"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","names":[]}
|