@iloom/cli 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -7
- package/dist/ClaudeContextManager-LD3VB6EM.js +13 -0
- package/dist/ClaudeService-CFFI7DD5.js +12 -0
- package/dist/{GitHubService-F7Z3XJOS.js → GitHubService-SH4H6VS5.js} +3 -3
- package/dist/{LoomLauncher-MODG2SEM.js → LoomLauncher-FB2MV2ZI.js} +7 -7
- package/dist/{PromptTemplateManager-7FINLRDE.js → PromptTemplateManager-WM5GIPEF.js} +2 -2
- package/dist/{SettingsManager-VAZF26S2.js → SettingsManager-SKLUVE3K.js} +6 -2
- package/dist/{add-issue-22JBNOML.js → add-issue-L5HX6LEV.js} +23 -8
- package/dist/add-issue-L5HX6LEV.js.map +1 -0
- package/dist/{chunk-U3WU5OWO.js → chunk-4XIDC3NF.js} +2 -2
- package/dist/chunk-4XIDC3NF.js.map +1 -0
- package/dist/{chunk-SSR5AVRJ.js → chunk-6OTVPRXH.js} +21 -8
- package/dist/chunk-6OTVPRXH.js.map +1 -0
- package/dist/{chunk-KQDEK2ZW.js → chunk-DGEKUT7Q.js} +9 -5
- package/dist/chunk-DGEKUT7Q.js.map +1 -0
- package/dist/chunk-FXV24OYZ.js +83 -0
- package/dist/chunk-FXV24OYZ.js.map +1 -0
- package/dist/{chunk-HPJJSYNS.js → chunk-H5LDRGVK.js} +6 -8
- package/dist/{chunk-HPJJSYNS.js.map → chunk-H5LDRGVK.js.map} +1 -1
- package/dist/{chunk-WKEWRSDB.js → chunk-HURVAQRK.js} +3 -3
- package/dist/{chunk-T7QPXANZ.js → chunk-IIPTBZQW.js} +17 -17
- package/dist/chunk-IIPTBZQW.js.map +1 -0
- package/dist/{chunk-QEPVTTHD.js → chunk-IO4WFTL2.js} +17 -11
- package/dist/chunk-IO4WFTL2.js.map +1 -0
- package/dist/{chunk-JQ7VOSTC.js → chunk-KOCQAD2E.js} +3 -3
- package/dist/{chunk-F3XBU2R7.js → chunk-L4QGC27H.js} +68 -2
- package/dist/chunk-L4QGC27H.js.map +1 -0
- package/dist/{chunk-YYSKGAZT.js → chunk-LAPY6NAE.js} +17 -8
- package/dist/chunk-LAPY6NAE.js.map +1 -0
- package/dist/{chunk-O2QWO64Z.js → chunk-PV3GAXQO.js} +56 -3
- package/dist/chunk-PV3GAXQO.js.map +1 -0
- package/dist/{chunk-CP2NU2JC.js → chunk-Q2KYPAH2.js} +7 -7
- package/dist/{chunk-CP2NU2JC.js.map → chunk-Q2KYPAH2.js.map} +1 -1
- package/dist/{chunk-Y7SAGNUT.js → chunk-SLIMABFA.js} +2 -2
- package/dist/{chunk-W3DQTW63.js → chunk-USVVV3FP.js} +4 -4
- package/dist/chunk-VVH3ANF2.js +307 -0
- package/dist/chunk-VVH3ANF2.js.map +1 -0
- package/dist/{chunk-JBH2ZYYZ.js → chunk-VYQLLHZ7.js} +22 -3
- package/dist/chunk-VYQLLHZ7.js.map +1 -0
- package/dist/{chunk-SJUQ2NDR.js → chunk-ZMNQBJUI.js} +24 -19
- package/dist/chunk-ZMNQBJUI.js.map +1 -0
- package/dist/{cleanup-3LUWPSM7.js → cleanup-ZHROIBSQ.js} +12 -16
- package/dist/cleanup-ZHROIBSQ.js.map +1 -0
- package/dist/cli.js +97 -49
- package/dist/cli.js.map +1 -1
- package/dist/{enhance-XJIQHVPD.js → enhance-VVMAKMVZ.js} +18 -8
- package/dist/enhance-VVMAKMVZ.js.map +1 -0
- package/dist/{feedback-23CLXKFT.js → feedback-AKHD7QIM.js} +8 -8
- package/dist/{finish-CY4CIH6O.js → finish-WGPISUEH.js} +60 -313
- package/dist/finish-WGPISUEH.js.map +1 -0
- package/dist/{git-LVRZ57GJ.js → git-OUYMVYJX.js} +2 -2
- package/dist/{ignite-WXEF2ID5.js → ignite-JEN3K3OT.js} +7 -7
- package/dist/index.d.ts +791 -712
- package/dist/index.js +126 -32
- package/dist/index.js.map +1 -1
- package/dist/init-EVUT4ZQJ.js +339 -0
- package/dist/init-EVUT4ZQJ.js.map +1 -0
- package/dist/mcp/github-comment-server.js +12 -9
- package/dist/mcp/github-comment-server.js.map +1 -1
- package/dist/neon-helpers-ZVIRPKCI.js +10 -0
- package/dist/{open-X6BTENPV.js → open-ETZUFSE4.js} +15 -17
- package/dist/{open-X6BTENPV.js.map → open-ETZUFSE4.js.map} +1 -1
- package/dist/prompts/init-prompt.txt +746 -0
- package/dist/prompts/issue-prompt.txt +48 -5
- package/dist/prompts/pr-prompt.txt +46 -1
- package/dist/prompts/regular-prompt.txt +22 -0
- package/dist/rebase-KBWFDZCN.js +95 -0
- package/dist/rebase-KBWFDZCN.js.map +1 -0
- package/dist/remote-GJEZWRCC.js +14 -0
- package/dist/{run-2JCPQAX3.js → run-4SVQ3WEU.js} +15 -17
- package/dist/{run-2JCPQAX3.js.map → run-4SVQ3WEU.js.map} +1 -1
- package/dist/schema/settings.schema.json +51 -1
- package/dist/{start-LWVRBJ6S.js → start-2NEZU7SE.js} +54 -53
- package/dist/{start-LWVRBJ6S.js.map → start-2NEZU7SE.js.map} +1 -1
- package/dist/{test-git-XPF4SZXJ.js → test-git-MKZATGZN.js} +3 -3
- package/dist/{test-prefix-XGFXFAYN.js → test-prefix-ZNLWDI3K.js} +3 -3
- package/dist/{update-3ZT2XX2G.js → update-4TDDUR5K.js} +11 -5
- package/dist/{update-3ZT2XX2G.js.map → update-4TDDUR5K.js.map} +1 -1
- package/dist/{update-notifier-QSSEB5KC.js → update-notifier-QEX3CJHA.js} +2 -2
- package/package.json +1 -1
- package/dist/ClaudeContextManager-XOSXQ67R.js +0 -13
- package/dist/ClaudeService-YSZ6EXWP.js +0 -12
- package/dist/NeonProvider-PAGPUH7F.js +0 -12
- package/dist/add-issue-22JBNOML.js.map +0 -1
- package/dist/chunk-37DYYFVK.js +0 -29
- package/dist/chunk-37DYYFVK.js.map +0 -1
- package/dist/chunk-F3XBU2R7.js.map +0 -1
- package/dist/chunk-JBH2ZYYZ.js.map +0 -1
- package/dist/chunk-KQDEK2ZW.js.map +0 -1
- package/dist/chunk-O2QWO64Z.js.map +0 -1
- package/dist/chunk-QEPVTTHD.js.map +0 -1
- package/dist/chunk-SJUQ2NDR.js.map +0 -1
- package/dist/chunk-SSR5AVRJ.js.map +0 -1
- package/dist/chunk-T7QPXANZ.js.map +0 -1
- package/dist/chunk-U3WU5OWO.js.map +0 -1
- package/dist/chunk-YYSKGAZT.js.map +0 -1
- package/dist/cleanup-3LUWPSM7.js.map +0 -1
- package/dist/enhance-XJIQHVPD.js.map +0 -1
- package/dist/env-MDFL4ZXL.js +0 -23
- package/dist/finish-CY4CIH6O.js.map +0 -1
- package/dist/init-RHACUR4E.js +0 -123
- package/dist/init-RHACUR4E.js.map +0 -1
- /package/dist/{ClaudeContextManager-XOSXQ67R.js.map → ClaudeContextManager-LD3VB6EM.js.map} +0 -0
- /package/dist/{ClaudeService-YSZ6EXWP.js.map → ClaudeService-CFFI7DD5.js.map} +0 -0
- /package/dist/{GitHubService-F7Z3XJOS.js.map → GitHubService-SH4H6VS5.js.map} +0 -0
- /package/dist/{LoomLauncher-MODG2SEM.js.map → LoomLauncher-FB2MV2ZI.js.map} +0 -0
- /package/dist/{NeonProvider-PAGPUH7F.js.map → PromptTemplateManager-WM5GIPEF.js.map} +0 -0
- /package/dist/{PromptTemplateManager-7FINLRDE.js.map → SettingsManager-SKLUVE3K.js.map} +0 -0
- /package/dist/{chunk-WKEWRSDB.js.map → chunk-HURVAQRK.js.map} +0 -0
- /package/dist/{chunk-JQ7VOSTC.js.map → chunk-KOCQAD2E.js.map} +0 -0
- /package/dist/{chunk-Y7SAGNUT.js.map → chunk-SLIMABFA.js.map} +0 -0
- /package/dist/{chunk-W3DQTW63.js.map → chunk-USVVV3FP.js.map} +0 -0
- /package/dist/{feedback-23CLXKFT.js.map → feedback-AKHD7QIM.js.map} +0 -0
- /package/dist/{SettingsManager-VAZF26S2.js.map → git-OUYMVYJX.js.map} +0 -0
- /package/dist/{ignite-WXEF2ID5.js.map → ignite-JEN3K3OT.js.map} +0 -0
- /package/dist/{env-MDFL4ZXL.js.map → neon-helpers-ZVIRPKCI.js.map} +0 -0
- /package/dist/{git-LVRZ57GJ.js.map → remote-GJEZWRCC.js.map} +0 -0
- /package/dist/{test-git-XPF4SZXJ.js.map → test-git-MKZATGZN.js.map} +0 -0
- /package/dist/{test-prefix-XGFXFAYN.js.map → test-prefix-ZNLWDI3K.js.map} +0 -0
- /package/dist/{update-notifier-QSSEB5KC.js.map → update-notifier-QEX3CJHA.js.map} +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectClaudeCli,
|
|
4
|
+
launchClaude
|
|
5
|
+
} from "./chunk-PXZBAC2M.js";
|
|
6
|
+
import {
|
|
7
|
+
PromptTemplateManager
|
|
8
|
+
} from "./chunk-L4QGC27H.js";
|
|
9
|
+
import {
|
|
10
|
+
ShellCompletion
|
|
11
|
+
} from "./chunk-PV3GAXQO.js";
|
|
12
|
+
import "./chunk-IO4WFTL2.js";
|
|
13
|
+
import "./chunk-KOCQAD2E.js";
|
|
14
|
+
import {
|
|
15
|
+
logger
|
|
16
|
+
} from "./chunk-GEHQXLEI.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/init.ts
|
|
19
|
+
import chalk from "chalk";
|
|
20
|
+
import { mkdir, writeFile, readFile } from "fs/promises";
|
|
21
|
+
import { existsSync } from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
var InitCommand = class {
|
|
25
|
+
constructor(shellCompletion, templateManager) {
|
|
26
|
+
this.shellCompletion = shellCompletion ?? new ShellCompletion();
|
|
27
|
+
this.templateManager = templateManager ?? new PromptTemplateManager();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Main entry point for the init command
|
|
31
|
+
* Prompts user for autocomplete setup and displays instructions
|
|
32
|
+
* @param customInitialMessage Optional custom initial message to send to Claude (defaults to "Help me configure iloom settings.")
|
|
33
|
+
*/
|
|
34
|
+
async execute(customInitialMessage) {
|
|
35
|
+
try {
|
|
36
|
+
logger.debug("InitCommand.execute() starting", {
|
|
37
|
+
cwd: process.cwd(),
|
|
38
|
+
nodeVersion: process.version,
|
|
39
|
+
hasCustomInitialMessage: !!customInitialMessage
|
|
40
|
+
});
|
|
41
|
+
logger.info(chalk.bold("Welcome to iloom setup"));
|
|
42
|
+
logger.info(chalk.bold("Verifying current setup..."));
|
|
43
|
+
await this.setupProjectConfiguration();
|
|
44
|
+
await this.launchGuidedInit(customInitialMessage);
|
|
45
|
+
logger.info(chalk.green("Setup complete! Enjoy using iloom CLI."));
|
|
46
|
+
} catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
48
|
+
logger.error(`Initialization failed: ${message}`);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Setup project configuration files
|
|
54
|
+
* Creates settings.local.json and updates .gitignore
|
|
55
|
+
*/
|
|
56
|
+
async setupProjectConfiguration() {
|
|
57
|
+
logger.debug("setupProjectConfiguration() starting");
|
|
58
|
+
try {
|
|
59
|
+
logger.debug("Loading SettingsMigrationManager for legacy migration");
|
|
60
|
+
const { SettingsMigrationManager } = await import("./SettingsMigrationManager-MTQIMI54.js");
|
|
61
|
+
const migrationManager = new SettingsMigrationManager();
|
|
62
|
+
logger.debug("Running settings migration check");
|
|
63
|
+
await migrationManager.migrateSettingsIfNeeded();
|
|
64
|
+
logger.debug("Settings migration check completed");
|
|
65
|
+
} catch (error) {
|
|
66
|
+
logger.warn(`Settings migration failed: ${error instanceof Error ? error.message : "Unknown"}`);
|
|
67
|
+
logger.debug("Settings migration error details", { error });
|
|
68
|
+
}
|
|
69
|
+
const iloomDir = path.join(process.cwd(), ".iloom");
|
|
70
|
+
logger.debug("Creating .iloom directory", { iloomDir });
|
|
71
|
+
await mkdir(iloomDir, { recursive: true });
|
|
72
|
+
logger.debug(".iloom directory created/verified");
|
|
73
|
+
const settingsLocalPath = path.join(iloomDir, "settings.local.json");
|
|
74
|
+
logger.debug("Checking for existing settings.local.json", { settingsLocalPath });
|
|
75
|
+
if (!existsSync(settingsLocalPath)) {
|
|
76
|
+
logger.debug("Creating settings.local.json file");
|
|
77
|
+
await writeFile(settingsLocalPath, "{}\n", "utf-8");
|
|
78
|
+
logger.info("Created .iloom/settings.local.json");
|
|
79
|
+
logger.debug("settings.local.json file created successfully");
|
|
80
|
+
} else {
|
|
81
|
+
logger.debug("settings.local.json file already exists, skipping");
|
|
82
|
+
}
|
|
83
|
+
logger.debug("Starting .gitignore update");
|
|
84
|
+
await this.updateGitignore();
|
|
85
|
+
logger.debug("setupProjectConfiguration() completed");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Add settings.local.json to .gitignore if not already present
|
|
89
|
+
*/
|
|
90
|
+
async updateGitignore() {
|
|
91
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
92
|
+
const entryToAdd = ".iloom/settings.local.json";
|
|
93
|
+
logger.debug("updateGitignore() starting", {
|
|
94
|
+
gitignorePath,
|
|
95
|
+
entryToAdd
|
|
96
|
+
});
|
|
97
|
+
let content = "";
|
|
98
|
+
if (existsSync(gitignorePath)) {
|
|
99
|
+
logger.debug(".gitignore file exists, reading content");
|
|
100
|
+
content = await readFile(gitignorePath, "utf-8");
|
|
101
|
+
logger.debug("Read .gitignore content", {
|
|
102
|
+
contentLength: content.length,
|
|
103
|
+
lineCount: content.split("\n").length
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
logger.debug(".gitignore file does not exist, will create new one");
|
|
107
|
+
}
|
|
108
|
+
const lines = content.split("\n");
|
|
109
|
+
const entryExists = lines.some((line) => line.trim() === entryToAdd);
|
|
110
|
+
logger.debug("Checking if entry already exists", {
|
|
111
|
+
entryExists,
|
|
112
|
+
totalLines: lines.length
|
|
113
|
+
});
|
|
114
|
+
if (entryExists) {
|
|
115
|
+
logger.debug("Entry already exists, skipping .gitignore update");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const commentLine = "\n# Added by iloom CLI";
|
|
119
|
+
const separator = content.endsWith("\n") || content === "" ? "" : "\n";
|
|
120
|
+
const newContent = content + separator + commentLine + "\n" + entryToAdd + "\n";
|
|
121
|
+
logger.debug("Writing updated .gitignore", {
|
|
122
|
+
originalLength: content.length,
|
|
123
|
+
newLength: newContent.length,
|
|
124
|
+
addedLines: 3
|
|
125
|
+
// comment + entry + newline
|
|
126
|
+
});
|
|
127
|
+
await writeFile(gitignorePath, newContent, "utf-8");
|
|
128
|
+
logger.info("Added .iloom/settings.local.json to .gitignore");
|
|
129
|
+
logger.debug(".gitignore update completed successfully");
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Launch interactive Claude-guided configuration
|
|
133
|
+
* @param customInitialMessage Optional custom initial message to send to Claude
|
|
134
|
+
*/
|
|
135
|
+
async launchGuidedInit(customInitialMessage) {
|
|
136
|
+
logger.debug("launchGuidedInit() starting", { hasCustomInitialMessage: !!customInitialMessage });
|
|
137
|
+
logger.info(chalk.bold("Starting interactive Claude-guided configuration..."));
|
|
138
|
+
logger.debug("Checking Claude CLI availability");
|
|
139
|
+
const claudeAvailable = await detectClaudeCli();
|
|
140
|
+
logger.debug("Claude CLI availability check result", { claudeAvailable });
|
|
141
|
+
if (!claudeAvailable) {
|
|
142
|
+
logger.warn("Claude Code not detected. Skipping guided configuration.");
|
|
143
|
+
logger.info("iloom won't be able to help you much without Claude Code, so please install it: npm install -g @anthropic-ai/claude-code");
|
|
144
|
+
logger.debug("Exiting launchGuidedInit() due to missing Claude CLI");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
149
|
+
const __dirname = path.dirname(__filename);
|
|
150
|
+
let schemaPath = path.join(__dirname, "schema", "settings.schema.json");
|
|
151
|
+
logger.debug("Loading settings schema", {
|
|
152
|
+
__filename,
|
|
153
|
+
__dirname,
|
|
154
|
+
schemaPath,
|
|
155
|
+
schemaExists: existsSync(schemaPath)
|
|
156
|
+
});
|
|
157
|
+
let schemaContent = "";
|
|
158
|
+
if (existsSync(schemaPath)) {
|
|
159
|
+
logger.debug("Reading schema file");
|
|
160
|
+
schemaContent = await readFile(schemaPath, "utf-8");
|
|
161
|
+
logger.debug("Schema file loaded", {
|
|
162
|
+
contentLength: schemaContent.length,
|
|
163
|
+
isValidJson: (() => {
|
|
164
|
+
try {
|
|
165
|
+
JSON.parse(schemaContent);
|
|
166
|
+
return true;
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
})()
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
logger.warn("Schema file not found - Claude will work without schema validation");
|
|
174
|
+
logger.debug("Schema file not found at expected path", { schemaPath });
|
|
175
|
+
}
|
|
176
|
+
const settingsLocalPath = path.join(process.cwd(), ".iloom", "settings.local.json");
|
|
177
|
+
const settingsCommittedPath = path.join(process.cwd(), ".iloom", "settings.json");
|
|
178
|
+
let settingsJson = "";
|
|
179
|
+
let settingsLocalJson = "";
|
|
180
|
+
logger.debug("Checking for settings files", {
|
|
181
|
+
settingsLocalPath,
|
|
182
|
+
settingsCommittedPath,
|
|
183
|
+
localExists: existsSync(settingsLocalPath),
|
|
184
|
+
committedExists: existsSync(settingsCommittedPath)
|
|
185
|
+
});
|
|
186
|
+
if (existsSync(settingsCommittedPath)) {
|
|
187
|
+
logger.debug("Reading settings.json");
|
|
188
|
+
const content = await readFile(settingsCommittedPath, "utf-8");
|
|
189
|
+
const trimmed = content.trim();
|
|
190
|
+
if (trimmed !== "{}" && trimmed !== "") {
|
|
191
|
+
settingsJson = content;
|
|
192
|
+
logger.debug("settings.json loaded", {
|
|
193
|
+
contentLength: content.length,
|
|
194
|
+
isValidJson: (() => {
|
|
195
|
+
try {
|
|
196
|
+
JSON.parse(content);
|
|
197
|
+
return true;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
})()
|
|
202
|
+
});
|
|
203
|
+
} else {
|
|
204
|
+
logger.debug("settings.json is empty, skipping");
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
logger.debug("settings.json does not exist");
|
|
208
|
+
}
|
|
209
|
+
if (existsSync(settingsLocalPath)) {
|
|
210
|
+
logger.debug("Reading settings.local.json");
|
|
211
|
+
const content = await readFile(settingsLocalPath, "utf-8");
|
|
212
|
+
const trimmed = content.trim();
|
|
213
|
+
if (trimmed !== "{}" && trimmed !== "") {
|
|
214
|
+
settingsLocalJson = content;
|
|
215
|
+
logger.debug("settings.local.json loaded", {
|
|
216
|
+
contentLength: content.length,
|
|
217
|
+
isValidJson: (() => {
|
|
218
|
+
try {
|
|
219
|
+
JSON.parse(content);
|
|
220
|
+
return true;
|
|
221
|
+
} catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
})()
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
logger.debug("settings.local.json is empty, skipping");
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
logger.debug("settings.local.json does not exist");
|
|
231
|
+
}
|
|
232
|
+
logger.debug("Settings files summary", {
|
|
233
|
+
hasSettingsJson: !!settingsJson,
|
|
234
|
+
hasSettingsLocalJson: !!settingsLocalJson,
|
|
235
|
+
settingsJsonLength: settingsJson.length,
|
|
236
|
+
settingsLocalJsonLength: settingsLocalJson.length
|
|
237
|
+
});
|
|
238
|
+
logger.debug("Detecting user shell for autocomplete setup");
|
|
239
|
+
const shell = this.shellCompletion.detectShell();
|
|
240
|
+
logger.debug("Shell detection result", { shell });
|
|
241
|
+
let shellConfigPath = "";
|
|
242
|
+
let shellConfigContent = "";
|
|
243
|
+
if (shell !== "unknown") {
|
|
244
|
+
logger.debug("Reading shell config file");
|
|
245
|
+
const shellConfig = await this.shellCompletion.readShellConfig(shell);
|
|
246
|
+
if (shellConfig) {
|
|
247
|
+
shellConfigPath = shellConfig.path;
|
|
248
|
+
shellConfigContent = shellConfig.content;
|
|
249
|
+
logger.debug("Shell config loaded", {
|
|
250
|
+
path: shellConfigPath,
|
|
251
|
+
contentLength: shellConfigContent.length,
|
|
252
|
+
configExists: existsSync(shellConfigPath)
|
|
253
|
+
});
|
|
254
|
+
} else {
|
|
255
|
+
logger.debug("Could not read shell config");
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
logger.debug("Unknown shell detected, skipping config read");
|
|
259
|
+
}
|
|
260
|
+
logger.debug("Detecting git remotes for GitHub configuration");
|
|
261
|
+
const { parseGitRemotes } = await import("./remote-GJEZWRCC.js");
|
|
262
|
+
const remotes = await parseGitRemotes();
|
|
263
|
+
logger.debug("Git remotes detected", { count: remotes.length, remotes });
|
|
264
|
+
let remotesInfo = "";
|
|
265
|
+
let multipleRemotes = "";
|
|
266
|
+
let singleRemote = "";
|
|
267
|
+
let singleRemoteName = "";
|
|
268
|
+
let singleRemoteUrl = "";
|
|
269
|
+
let noRemotes = "";
|
|
270
|
+
if (remotes.length === 0) {
|
|
271
|
+
noRemotes = "true";
|
|
272
|
+
remotesInfo = "No git remotes detected in this repository.";
|
|
273
|
+
} else if (remotes.length === 1 && remotes[0]) {
|
|
274
|
+
singleRemote = "true";
|
|
275
|
+
singleRemoteName = remotes[0].name;
|
|
276
|
+
singleRemoteUrl = remotes[0].url;
|
|
277
|
+
remotesInfo = `Detected Remote:
|
|
278
|
+
- **${remotes[0].name}**: ${remotes[0].url} (${remotes[0].owner}/${remotes[0].repo})`;
|
|
279
|
+
} else {
|
|
280
|
+
multipleRemotes = "true";
|
|
281
|
+
remotesInfo = `Detected Remotes (${remotes.length}):
|
|
282
|
+
` + remotes.map((r) => `- **${r.name}**: ${r.url} (${r.owner}/${r.repo})`).join("\n");
|
|
283
|
+
}
|
|
284
|
+
const variables = {
|
|
285
|
+
SETTINGS_SCHEMA: schemaContent,
|
|
286
|
+
SETTINGS_JSON: settingsJson,
|
|
287
|
+
SETTINGS_LOCAL_JSON: settingsLocalJson,
|
|
288
|
+
SHELL_TYPE: shell,
|
|
289
|
+
SHELL_CONFIG_PATH: shellConfigPath,
|
|
290
|
+
SHELL_CONFIG_CONTENT: shellConfigContent,
|
|
291
|
+
REMOTES_INFO: remotesInfo,
|
|
292
|
+
MULTIPLE_REMOTES: multipleRemotes,
|
|
293
|
+
SINGLE_REMOTE: singleRemote,
|
|
294
|
+
SINGLE_REMOTE_NAME: singleRemoteName,
|
|
295
|
+
SINGLE_REMOTE_URL: singleRemoteUrl,
|
|
296
|
+
NO_REMOTES: noRemotes
|
|
297
|
+
};
|
|
298
|
+
logger.debug("Building template variables", {
|
|
299
|
+
variableKeys: Object.keys(variables),
|
|
300
|
+
schemaContentLength: schemaContent.length,
|
|
301
|
+
settingsJsonLength: settingsJson.length,
|
|
302
|
+
settingsLocalJsonLength: settingsLocalJson.length
|
|
303
|
+
});
|
|
304
|
+
logger.debug("Loading init prompt template");
|
|
305
|
+
const prompt = await this.templateManager.getPrompt("init", variables);
|
|
306
|
+
logger.debug("Init prompt loaded", {
|
|
307
|
+
promptLength: prompt.length,
|
|
308
|
+
containsSchema: prompt.includes("SETTINGS_SCHEMA"),
|
|
309
|
+
containsExistingSettings: prompt.includes("EXISTING_SETTINGS")
|
|
310
|
+
});
|
|
311
|
+
const claudeOptions = {
|
|
312
|
+
headless: false,
|
|
313
|
+
appendSystemPrompt: prompt,
|
|
314
|
+
addDir: process.cwd()
|
|
315
|
+
};
|
|
316
|
+
logger.debug("Launching Claude with options", {
|
|
317
|
+
optionKeys: Object.keys(claudeOptions),
|
|
318
|
+
headless: claudeOptions.headless,
|
|
319
|
+
hasSystemPrompt: !!claudeOptions.appendSystemPrompt,
|
|
320
|
+
addDir: claudeOptions.addDir,
|
|
321
|
+
promptLength: prompt.length,
|
|
322
|
+
hasCustomInitialMessage: !!customInitialMessage
|
|
323
|
+
});
|
|
324
|
+
const initialMessage = customInitialMessage ?? "Help me configure iloom settings.";
|
|
325
|
+
await launchClaude(initialMessage, claudeOptions);
|
|
326
|
+
logger.debug("Claude session completed");
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
329
|
+
logger.warn(`Guided configuration failed: ${message}`);
|
|
330
|
+
logger.debug("launchGuidedInit() error details", { error });
|
|
331
|
+
logger.info("You can manually edit .iloom/settings.json to configure iloom.");
|
|
332
|
+
}
|
|
333
|
+
logger.debug("launchGuidedInit() completed");
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
export {
|
|
337
|
+
InitCommand
|
|
338
|
+
};
|
|
339
|
+
//# sourceMappingURL=init-EVUT4ZQJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/init.ts"],"sourcesContent":["import { logger } from '../utils/logger.js'\nimport { ShellCompletion } from '../lib/ShellCompletion.js'\nimport chalk from 'chalk'\nimport { mkdir, writeFile, readFile } from 'fs/promises'\nimport { existsSync } from 'fs'\nimport path from 'path'\nimport { detectClaudeCli, launchClaude } from '../utils/claude.js'\nimport { PromptTemplateManager } from '../lib/PromptTemplateManager.js'\nimport { fileURLToPath } from 'url'\n\n/**\n * Initialize iloom configuration and setup shell autocomplete\n * Implements the `il init` command requested in issue #94\n */\nexport class InitCommand {\n private readonly shellCompletion: ShellCompletion\n private readonly templateManager: PromptTemplateManager\n\n constructor(shellCompletion?: ShellCompletion, templateManager?: PromptTemplateManager) {\n this.shellCompletion = shellCompletion ?? new ShellCompletion()\n this.templateManager = templateManager ?? new PromptTemplateManager()\n }\n\n /**\n * Main entry point for the init command\n * Prompts user for autocomplete setup and displays instructions\n * @param customInitialMessage Optional custom initial message to send to Claude (defaults to \"Help me configure iloom settings.\")\n */\n public async execute(customInitialMessage?: string): Promise<void> {\n try {\n logger.debug('InitCommand.execute() starting', {\n cwd: process.cwd(),\n nodeVersion: process.version,\n hasCustomInitialMessage: !!customInitialMessage\n })\n\n logger.info(chalk.bold('Welcome to iloom setup'))\n\n // Setup project configuration\n logger.info(chalk.bold('Verifying current setup...'))\n\n await this.setupProjectConfiguration()\n\n // Launch guided Claude configuration if available\n await this.launchGuidedInit(customInitialMessage)\n\n logger.info(chalk.green('Setup complete! Enjoy using iloom CLI.'))\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n logger.error(`Initialization failed: ${message}`)\n throw error\n }\n }\n\n /**\n * Setup project configuration files\n * Creates settings.local.json and updates .gitignore\n */\n private async setupProjectConfiguration(): Promise<void> {\n logger.debug('setupProjectConfiguration() starting')\n\n // Migrate legacy .hatchbox settings to .iloom (BEFORE creating new files)\n try {\n logger.debug('Loading SettingsMigrationManager for legacy migration')\n const { SettingsMigrationManager } = await import('../lib/SettingsMigrationManager.js')\n const migrationManager = new SettingsMigrationManager()\n logger.debug('Running settings migration check')\n await migrationManager.migrateSettingsIfNeeded()\n logger.debug('Settings migration check completed')\n } catch (error) {\n // Log warning but don't fail\n logger.warn(`Settings migration failed: ${error instanceof Error ? error.message : 'Unknown'}`)\n logger.debug('Settings migration error details', { error })\n }\n\n // Ensure .iloom directory exists\n const iloomDir = path.join(process.cwd(), '.iloom')\n logger.debug('Creating .iloom directory', { iloomDir })\n await mkdir(iloomDir, { recursive: true })\n logger.debug('.iloom directory created/verified')\n\n // Create settings.local.json if it doesn't exist\n const settingsLocalPath = path.join(iloomDir, 'settings.local.json')\n logger.debug('Checking for existing settings.local.json', { settingsLocalPath })\n\n if (!existsSync(settingsLocalPath)) {\n logger.debug('Creating settings.local.json file')\n await writeFile(settingsLocalPath, '{}\\n', 'utf-8')\n logger.info('Created .iloom/settings.local.json')\n logger.debug('settings.local.json file created successfully')\n } else {\n logger.debug('settings.local.json file already exists, skipping')\n }\n\n // Update .gitignore\n logger.debug('Starting .gitignore update')\n await this.updateGitignore()\n logger.debug('setupProjectConfiguration() completed')\n }\n\n /**\n * Add settings.local.json to .gitignore if not already present\n */\n private async updateGitignore(): Promise<void> {\n const gitignorePath = path.join(process.cwd(), '.gitignore')\n const entryToAdd = '.iloom/settings.local.json'\n\n logger.debug('updateGitignore() starting', {\n gitignorePath,\n entryToAdd\n })\n\n // Read existing .gitignore or create empty\n let content = ''\n if (existsSync(gitignorePath)) {\n logger.debug('.gitignore file exists, reading content')\n content = await readFile(gitignorePath, 'utf-8')\n logger.debug('Read .gitignore content', {\n contentLength: content.length,\n lineCount: content.split('\\n').length\n })\n } else {\n logger.debug('.gitignore file does not exist, will create new one')\n }\n\n // Check if entry already exists\n const lines = content.split('\\n')\n const entryExists = lines.some(line => line.trim() === entryToAdd)\n logger.debug('Checking if entry already exists', {\n entryExists,\n totalLines: lines.length\n })\n\n if (entryExists) {\n logger.debug('Entry already exists, skipping .gitignore update')\n return\n }\n\n // Add entry with comment\n const commentLine = '\\n# Added by iloom CLI'\n const separator = content.endsWith('\\n') || content === '' ? '' : '\\n'\n const newContent = content + separator + commentLine + '\\n' + entryToAdd + '\\n'\n\n logger.debug('Writing updated .gitignore', {\n originalLength: content.length,\n newLength: newContent.length,\n addedLines: 3 // comment + entry + newline\n })\n\n await writeFile(gitignorePath, newContent, 'utf-8')\n logger.info('Added .iloom/settings.local.json to .gitignore')\n logger.debug('.gitignore update completed successfully')\n }\n\n /**\n * Launch interactive Claude-guided configuration\n * @param customInitialMessage Optional custom initial message to send to Claude\n */\n private async launchGuidedInit(customInitialMessage?: string): Promise<void> {\n logger.debug('launchGuidedInit() starting', { hasCustomInitialMessage: !!customInitialMessage })\n logger.info(chalk.bold('Starting interactive Claude-guided configuration...'))\n\n // Check if Claude CLI is available\n logger.debug('Checking Claude CLI availability')\n const claudeAvailable = await detectClaudeCli()\n logger.debug('Claude CLI availability check result', { claudeAvailable })\n\n if (!claudeAvailable) {\n logger.warn('Claude Code not detected. Skipping guided configuration.')\n logger.info('iloom won\\'t be able to help you much without Claude Code, so please install it: npm install -g @anthropic-ai/claude-code')\n logger.debug('Exiting launchGuidedInit() due to missing Claude CLI')\n return\n }\n\n try {\n // Load schema from dist/schema/settings.schema.json\n // Use similar approach to PromptTemplateManager for path resolution\n const __filename = fileURLToPath(import.meta.url)\n const __dirname = path.dirname(__filename)\n\n // Walk up to find the schema directory (in case of chunked files)\n let schemaPath = path.join(__dirname, 'schema', 'settings.schema.json')\n\n logger.debug('Loading settings schema', {\n __filename,\n __dirname,\n schemaPath,\n schemaExists: existsSync(schemaPath)\n })\n\n let schemaContent = ''\n if (existsSync(schemaPath)) {\n logger.debug('Reading schema file')\n schemaContent = await readFile(schemaPath, 'utf-8')\n logger.debug('Schema file loaded', {\n contentLength: schemaContent.length,\n isValidJson: ((): boolean => {\n try {\n JSON.parse(schemaContent)\n return true\n } catch {\n return false\n }\n })()\n })\n } else {\n logger.warn('Schema file not found - Claude will work without schema validation')\n logger.debug('Schema file not found at expected path', { schemaPath })\n }\n\n // Check for existing settings - read BOTH files if they exist\n const settingsLocalPath = path.join(process.cwd(), '.iloom', 'settings.local.json')\n const settingsCommittedPath = path.join(process.cwd(), '.iloom', 'settings.json')\n\n let settingsJson = ''\n let settingsLocalJson = ''\n\n logger.debug('Checking for settings files', {\n settingsLocalPath,\n settingsCommittedPath,\n localExists: existsSync(settingsLocalPath),\n committedExists: existsSync(settingsCommittedPath)\n })\n\n // Read settings.json if it exists\n if (existsSync(settingsCommittedPath)) {\n logger.debug('Reading settings.json')\n const content = await readFile(settingsCommittedPath, 'utf-8')\n const trimmed = content.trim()\n if (trimmed !== '{}' && trimmed !== '') {\n settingsJson = content\n logger.debug('settings.json loaded', {\n contentLength: content.length,\n isValidJson: ((): boolean => {\n try {\n JSON.parse(content)\n return true\n } catch {\n return false\n }\n })()\n })\n } else {\n logger.debug('settings.json is empty, skipping')\n }\n } else {\n logger.debug('settings.json does not exist')\n }\n\n // Read settings.local.json if it exists\n if (existsSync(settingsLocalPath)) {\n logger.debug('Reading settings.local.json')\n const content = await readFile(settingsLocalPath, 'utf-8')\n const trimmed = content.trim()\n if (trimmed !== '{}' && trimmed !== '') {\n settingsLocalJson = content\n logger.debug('settings.local.json loaded', {\n contentLength: content.length,\n isValidJson: ((): boolean => {\n try {\n JSON.parse(content)\n return true\n } catch {\n return false\n }\n })()\n })\n } else {\n logger.debug('settings.local.json is empty, skipping')\n }\n } else {\n logger.debug('settings.local.json does not exist')\n }\n\n // Log summary\n logger.debug('Settings files summary', {\n hasSettingsJson: !!settingsJson,\n hasSettingsLocalJson: !!settingsLocalJson,\n settingsJsonLength: settingsJson.length,\n settingsLocalJsonLength: settingsLocalJson.length\n })\n\n // Detect shell and read config\n logger.debug('Detecting user shell for autocomplete setup')\n const shell = this.shellCompletion.detectShell()\n logger.debug('Shell detection result', { shell })\n\n let shellConfigPath = ''\n let shellConfigContent = ''\n\n if (shell !== 'unknown') {\n logger.debug('Reading shell config file')\n const shellConfig = await this.shellCompletion.readShellConfig(shell)\n if (shellConfig) {\n shellConfigPath = shellConfig.path\n shellConfigContent = shellConfig.content\n logger.debug('Shell config loaded', {\n path: shellConfigPath,\n contentLength: shellConfigContent.length,\n configExists: existsSync(shellConfigPath)\n })\n } else {\n logger.debug('Could not read shell config')\n }\n } else {\n logger.debug('Unknown shell detected, skipping config read')\n }\n\n // Detect git remotes for GitHub configuration\n logger.debug('Detecting git remotes for GitHub configuration')\n const { parseGitRemotes } = await import('../utils/remote.js')\n const remotes = await parseGitRemotes()\n logger.debug('Git remotes detected', { count: remotes.length, remotes })\n\n let remotesInfo = ''\n let multipleRemotes = ''\n let singleRemote = ''\n let singleRemoteName = ''\n let singleRemoteUrl = ''\n let noRemotes = ''\n\n if (remotes.length === 0) {\n noRemotes = 'true'\n remotesInfo = 'No git remotes detected in this repository.'\n } else if (remotes.length === 1 && remotes[0]) {\n singleRemote = 'true'\n singleRemoteName = remotes[0].name\n singleRemoteUrl = remotes[0].url\n remotesInfo = `Detected Remote:\\n- **${remotes[0].name}**: ${remotes[0].url} (${remotes[0].owner}/${remotes[0].repo})`\n } else {\n multipleRemotes = 'true'\n remotesInfo = `Detected Remotes (${remotes.length}):\\n` +\n remotes.map(r => `- **${r.name}**: ${r.url} (${r.owner}/${r.repo})`).join('\\n')\n }\n\n // Build template variables\n const variables = {\n SETTINGS_SCHEMA: schemaContent,\n SETTINGS_JSON: settingsJson,\n SETTINGS_LOCAL_JSON: settingsLocalJson,\n SHELL_TYPE: shell,\n SHELL_CONFIG_PATH: shellConfigPath,\n SHELL_CONFIG_CONTENT: shellConfigContent,\n REMOTES_INFO: remotesInfo,\n MULTIPLE_REMOTES: multipleRemotes,\n SINGLE_REMOTE: singleRemote,\n SINGLE_REMOTE_NAME: singleRemoteName,\n SINGLE_REMOTE_URL: singleRemoteUrl,\n NO_REMOTES: noRemotes\n }\n\n logger.debug('Building template variables', {\n variableKeys: Object.keys(variables),\n schemaContentLength: schemaContent.length,\n settingsJsonLength: settingsJson.length,\n settingsLocalJsonLength: settingsLocalJson.length\n })\n\n // Get init prompt\n logger.debug('Loading init prompt template')\n const prompt = await this.templateManager.getPrompt('init', variables)\n\n logger.debug('Init prompt loaded', {\n promptLength: prompt.length,\n containsSchema: prompt.includes('SETTINGS_SCHEMA'),\n containsExistingSettings: prompt.includes('EXISTING_SETTINGS')\n })\n\n const claudeOptions = {\n headless: false,\n appendSystemPrompt: prompt,\n addDir: process.cwd(),\n }\n\n logger.debug('Launching Claude with options', {\n optionKeys: Object.keys(claudeOptions),\n headless: claudeOptions.headless,\n hasSystemPrompt: !!claudeOptions.appendSystemPrompt,\n addDir: claudeOptions.addDir,\n promptLength: prompt.length,\n hasCustomInitialMessage: !!customInitialMessage\n })\n\n // Launch Claude in interactive mode with custom initial message if provided\n const initialMessage = customInitialMessage ?? 'Help me configure iloom settings.'\n await launchClaude(initialMessage, claudeOptions)\n logger.debug('Claude session completed')\n\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error'\n logger.warn(`Guided configuration failed: ${message}`)\n logger.debug('launchGuidedInit() error details', { error })\n logger.info('You can manually edit .iloom/settings.json to configure iloom.')\n }\n\n logger.debug('launchGuidedInit() completed')\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -20,22 +20,24 @@ async function executeGhCommand(args, options) {
|
|
|
20
20
|
const data = isJson ? JSON.parse(result.stdout) : result.stdout;
|
|
21
21
|
return data;
|
|
22
22
|
}
|
|
23
|
-
async function createIssueComment(issueNumber, body) {
|
|
24
|
-
logger.debug("Creating issue comment", { issueNumber });
|
|
23
|
+
async function createIssueComment(issueNumber, body, repo) {
|
|
24
|
+
logger.debug("Creating issue comment", { issueNumber, repo });
|
|
25
|
+
const apiPath = repo ? `repos/${repo}/issues/${issueNumber}/comments` : `repos/:owner/:repo/issues/${issueNumber}/comments`;
|
|
25
26
|
return executeGhCommand([
|
|
26
27
|
"api",
|
|
27
|
-
|
|
28
|
+
apiPath,
|
|
28
29
|
"-f",
|
|
29
30
|
`body=${body}`,
|
|
30
31
|
"--jq",
|
|
31
32
|
"{id: .id, url: .html_url, created_at: .created_at}"
|
|
32
33
|
]);
|
|
33
34
|
}
|
|
34
|
-
async function updateIssueComment(commentId, body) {
|
|
35
|
-
logger.debug("Updating issue comment", { commentId });
|
|
35
|
+
async function updateIssueComment(commentId, body, repo) {
|
|
36
|
+
logger.debug("Updating issue comment", { commentId, repo });
|
|
37
|
+
const apiPath = repo ? `repos/${repo}/issues/comments/${commentId}` : `repos/:owner/:repo/issues/comments/${commentId}`;
|
|
36
38
|
return executeGhCommand([
|
|
37
39
|
"api",
|
|
38
|
-
|
|
40
|
+
apiPath,
|
|
39
41
|
"-X",
|
|
40
42
|
"PATCH",
|
|
41
43
|
"-f",
|
|
@@ -44,11 +46,12 @@ async function updateIssueComment(commentId, body) {
|
|
|
44
46
|
"{id: .id, url: .html_url, updated_at: .updated_at}"
|
|
45
47
|
]);
|
|
46
48
|
}
|
|
47
|
-
async function createPRComment(prNumber, body) {
|
|
48
|
-
logger.debug("Creating PR comment", { prNumber });
|
|
49
|
+
async function createPRComment(prNumber, body, repo) {
|
|
50
|
+
logger.debug("Creating PR comment", { prNumber, repo });
|
|
51
|
+
const apiPath = repo ? `repos/${repo}/issues/${prNumber}/comments` : `repos/:owner/:repo/issues/${prNumber}/comments`;
|
|
49
52
|
return executeGhCommand([
|
|
50
53
|
"api",
|
|
51
|
-
|
|
54
|
+
apiPath,
|
|
52
55
|
"-f",
|
|
53
56
|
`body=${body}`,
|
|
54
57
|
"--jq",
|
|
@@ -1 +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":"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","names":[]}
|
|
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\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\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\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\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 * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\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 * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\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 * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\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":"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","names":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createNeonProviderFromSettings
|
|
4
|
+
} from "./chunk-LAPY6NAE.js";
|
|
5
|
+
import "./chunk-JNKJ7NJV.js";
|
|
6
|
+
import "./chunk-GEHQXLEI.js";
|
|
7
|
+
export {
|
|
8
|
+
createNeonProviderFromSettings
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=neon-helpers-ZVIRPKCI.js.map
|
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DevServerManager
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-GZP4UGGM.js";
|
|
6
|
-
import {
|
|
7
|
-
extractSettingsOverrides
|
|
8
|
-
} from "./chunk-GYCR2LOU.js";
|
|
9
|
-
import {
|
|
10
|
-
openBrowser
|
|
11
|
-
} from "./chunk-YETJNRQM.js";
|
|
4
|
+
} from "./chunk-USVVV3FP.js";
|
|
12
5
|
import {
|
|
13
6
|
IdentifierParser
|
|
14
7
|
} from "./chunk-H4E4THUZ.js";
|
|
15
8
|
import "./chunk-SPYPLHMK.js";
|
|
9
|
+
import "./chunk-GZP4UGGM.js";
|
|
16
10
|
import {
|
|
17
|
-
calculatePortForBranch
|
|
18
|
-
|
|
11
|
+
calculatePortForBranch,
|
|
12
|
+
extractPort,
|
|
13
|
+
parseEnvFile
|
|
14
|
+
} from "./chunk-ZMNQBJUI.js";
|
|
19
15
|
import "./chunk-BLCTGFZN.js";
|
|
16
|
+
import {
|
|
17
|
+
extractSettingsOverrides
|
|
18
|
+
} from "./chunk-GYCR2LOU.js";
|
|
20
19
|
import {
|
|
21
20
|
ProjectCapabilityDetector
|
|
22
21
|
} from "./chunk-CWR2SANQ.js";
|
|
23
22
|
import "./chunk-2ZPFJQ3B.js";
|
|
24
23
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "./chunk-SJUQ2NDR.js";
|
|
24
|
+
openBrowser
|
|
25
|
+
} from "./chunk-YETJNRQM.js";
|
|
28
26
|
import {
|
|
29
27
|
GitWorktreeManager
|
|
30
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-IO4WFTL2.js";
|
|
31
29
|
import {
|
|
32
30
|
SettingsManager
|
|
33
|
-
} from "./chunk-
|
|
34
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
32
|
+
import "./chunk-KOCQAD2E.js";
|
|
35
33
|
import {
|
|
36
34
|
logger
|
|
37
35
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -275,4 +273,4 @@ Make sure the project is built (run 'il start' first)`
|
|
|
275
273
|
export {
|
|
276
274
|
OpenCommand
|
|
277
275
|
};
|
|
278
|
-
//# sourceMappingURL=open-
|
|
276
|
+
//# sourceMappingURL=open-ETZUFSE4.js.map
|