@iloom/cli 0.1.17 → 0.1.19
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 +51 -6
- package/dist/ClaudeContextManager-JKR4WGNU.js +13 -0
- package/dist/ClaudeService-55DQGB7T.js +12 -0
- package/dist/{GitHubService-F7Z3XJOS.js → GitHubService-LWP4GKGH.js} +3 -3
- package/dist/{LoomLauncher-MODG2SEM.js → LoomLauncher-UMMLPIZO.js} +7 -7
- package/dist/{PromptTemplateManager-7FINLRDE.js → PromptTemplateManager-WII75TKH.js} +2 -2
- package/dist/README.md +755 -0
- package/dist/{SettingsManager-VAZF26S2.js → SettingsManager-SKLUVE3K.js} +6 -2
- package/dist/{add-issue-22JBNOML.js → add-issue-X56V3XPB.js} +23 -8
- package/dist/add-issue-X56V3XPB.js.map +1 -0
- package/dist/{chunk-Y7SAGNUT.js → chunk-DEPYQRRB.js} +2 -2
- package/dist/{chunk-WKEWRSDB.js → chunk-ELFT36PV.js} +3 -3
- 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-QEPVTTHD.js → chunk-IO4WFTL2.js} +17 -11
- package/dist/chunk-IO4WFTL2.js.map +1 -0
- package/dist/{chunk-KQDEK2ZW.js → chunk-JXQXSC45.js} +41 -9
- package/dist/chunk-JXQXSC45.js.map +1 -0
- package/dist/{chunk-JQ7VOSTC.js → chunk-KOCQAD2E.js} +3 -3
- 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-PVAVNJKS.js +188 -0
- package/dist/chunk-PVAVNJKS.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-W3DQTW63.js → chunk-USVVV3FP.js} +4 -4
- package/dist/{chunk-SSR5AVRJ.js → chunk-VCMMAFXQ.js} +21 -8
- package/dist/chunk-VCMMAFXQ.js.map +1 -0
- 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/{chunk-T7QPXANZ.js → chunk-ZWXJBSUW.js} +17 -17
- package/dist/chunk-ZWXJBSUW.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 +107 -49
- package/dist/cli.js.map +1 -1
- package/dist/contribute-3MQJ3XAQ.js +256 -0
- package/dist/contribute-3MQJ3XAQ.js.map +1 -0
- package/dist/{enhance-XJIQHVPD.js → enhance-VGWUX474.js} +18 -8
- package/dist/enhance-VGWUX474.js.map +1 -0
- package/dist/{feedback-23CLXKFT.js → feedback-ZOUCCHN4.js} +8 -8
- package/dist/{finish-3CQZIULO.js → finish-QJSK6Z7J.js} +36 -313
- package/dist/finish-QJSK6Z7J.js.map +1 -0
- package/dist/{git-LVRZ57GJ.js → git-OUYMVYJX.js} +2 -2
- package/dist/{ignite-WXEF2ID5.js → ignite-HICLZEYU.js} +124 -9
- package/dist/ignite-HICLZEYU.js.map +1 -0
- package/dist/index.d.ts +794 -712
- package/dist/index.js +169 -36
- package/dist/index.js.map +1 -1
- package/dist/init-UMKNHNV5.js +339 -0
- package/dist/init-UMKNHNV5.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 +748 -0
- package/dist/prompts/issue-prompt.txt +141 -9
- 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-CT2ZEFP2.js} +54 -53
- package/dist/{start-LWVRBJ6S.js.map → start-CT2ZEFP2.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-DN3FSNKY.js → update-4TDDUR5K.js} +10 -4
- package/dist/{update-DN3FSNKY.js.map → update-4TDDUR5K.js.map} +1 -1
- package/package.json +3 -2
- 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 +0 -110
- 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-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-3CQZIULO.js.map +0 -1
- package/dist/ignite-WXEF2ID5.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-JKR4WGNU.js.map} +0 -0
- /package/dist/{ClaudeService-YSZ6EXWP.js.map → ClaudeService-55DQGB7T.js.map} +0 -0
- /package/dist/{GitHubService-F7Z3XJOS.js.map → GitHubService-LWP4GKGH.js.map} +0 -0
- /package/dist/{LoomLauncher-MODG2SEM.js.map → LoomLauncher-UMMLPIZO.js.map} +0 -0
- /package/dist/{NeonProvider-PAGPUH7F.js.map → PromptTemplateManager-WII75TKH.js.map} +0 -0
- /package/dist/{PromptTemplateManager-7FINLRDE.js.map → SettingsManager-SKLUVE3K.js.map} +0 -0
- /package/dist/{chunk-Y7SAGNUT.js.map → chunk-DEPYQRRB.js.map} +0 -0
- /package/dist/{chunk-WKEWRSDB.js.map → chunk-ELFT36PV.js.map} +0 -0
- /package/dist/{chunk-JQ7VOSTC.js.map → chunk-KOCQAD2E.js.map} +0 -0
- /package/dist/{chunk-W3DQTW63.js.map → chunk-USVVV3FP.js.map} +0 -0
- /package/dist/{feedback-23CLXKFT.js.map → feedback-ZOUCCHN4.js.map} +0 -0
- /package/dist/{SettingsManager-VAZF26S2.js.map → git-OUYMVYJX.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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkGhAuth,
|
|
4
|
+
executeGhCommand
|
|
5
|
+
} from "./chunk-JXQXSC45.js";
|
|
6
|
+
import {
|
|
7
|
+
executeGitCommand
|
|
8
|
+
} from "./chunk-KOCQAD2E.js";
|
|
9
|
+
import {
|
|
10
|
+
promptInput
|
|
11
|
+
} from "./chunk-JNKJ7NJV.js";
|
|
12
|
+
import {
|
|
13
|
+
logger
|
|
14
|
+
} from "./chunk-GEHQXLEI.js";
|
|
15
|
+
|
|
16
|
+
// src/commands/contribute.ts
|
|
17
|
+
import { existsSync, accessSync, constants } from "fs";
|
|
18
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import chalk from "chalk";
|
|
21
|
+
var ILOOM_REPO = "iloom-ai/iloom-cli";
|
|
22
|
+
var UPSTREAM_URL = "https://github.com/iloom-ai/iloom-cli.git";
|
|
23
|
+
var MAX_PATH_LENGTH = 255;
|
|
24
|
+
var RESERVED_NAMES = [
|
|
25
|
+
"CON",
|
|
26
|
+
"PRN",
|
|
27
|
+
"AUX",
|
|
28
|
+
"NUL",
|
|
29
|
+
"COM1",
|
|
30
|
+
"COM2",
|
|
31
|
+
"COM3",
|
|
32
|
+
"COM4",
|
|
33
|
+
"COM5",
|
|
34
|
+
"COM6",
|
|
35
|
+
"COM7",
|
|
36
|
+
"COM8",
|
|
37
|
+
"COM9",
|
|
38
|
+
"LPT1",
|
|
39
|
+
"LPT2",
|
|
40
|
+
"LPT3",
|
|
41
|
+
"LPT4",
|
|
42
|
+
"LPT5",
|
|
43
|
+
"LPT6",
|
|
44
|
+
"LPT7",
|
|
45
|
+
"LPT8",
|
|
46
|
+
"LPT9"
|
|
47
|
+
];
|
|
48
|
+
var INVALID_CHARS_PATTERN = /[<>:"|?*\x00-\x1f]/;
|
|
49
|
+
function validateDirectoryName(directoryName) {
|
|
50
|
+
if (!directoryName || directoryName.trim() === "") {
|
|
51
|
+
return { isValid: false, error: "Directory name cannot be empty" };
|
|
52
|
+
}
|
|
53
|
+
const trimmed = directoryName.trim();
|
|
54
|
+
const baseName = path.basename(trimmed);
|
|
55
|
+
if (INVALID_CHARS_PATTERN.test(baseName)) {
|
|
56
|
+
return { isValid: false, error: 'Directory name contains invalid characters (<>:"|?*)' };
|
|
57
|
+
}
|
|
58
|
+
if (RESERVED_NAMES.includes(baseName.toUpperCase())) {
|
|
59
|
+
return { isValid: false, error: `"${baseName}" is a reserved name and cannot be used` };
|
|
60
|
+
}
|
|
61
|
+
if (baseName.startsWith(".") && baseName === ".") {
|
|
62
|
+
return { isValid: false, error: "Directory name cannot be just a dot" };
|
|
63
|
+
}
|
|
64
|
+
if (baseName.endsWith(".") || baseName.endsWith(" ")) {
|
|
65
|
+
return { isValid: false, error: "Directory name cannot end with a dot or space" };
|
|
66
|
+
}
|
|
67
|
+
return { isValid: true };
|
|
68
|
+
}
|
|
69
|
+
function validateDirectoryPath(directoryPath) {
|
|
70
|
+
const nameValidation = validateDirectoryName(directoryPath);
|
|
71
|
+
if (!nameValidation.isValid) {
|
|
72
|
+
return nameValidation;
|
|
73
|
+
}
|
|
74
|
+
const trimmed = directoryPath.trim();
|
|
75
|
+
const absolutePath = path.resolve(trimmed);
|
|
76
|
+
if (absolutePath.length > MAX_PATH_LENGTH) {
|
|
77
|
+
return {
|
|
78
|
+
isValid: false,
|
|
79
|
+
error: `Path is too long (${absolutePath.length} characters). Maximum is ${MAX_PATH_LENGTH} characters.`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(absolutePath)) {
|
|
83
|
+
return { isValid: false, error: `Directory already exists: ${trimmed}` };
|
|
84
|
+
}
|
|
85
|
+
const parentDir = path.dirname(absolutePath);
|
|
86
|
+
if (!existsSync(parentDir)) {
|
|
87
|
+
return { isValid: false, error: `Parent directory does not exist: ${parentDir}` };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
accessSync(parentDir, constants.W_OK);
|
|
91
|
+
} catch {
|
|
92
|
+
return { isValid: false, error: `Parent directory is not writable: ${parentDir}` };
|
|
93
|
+
}
|
|
94
|
+
return { isValid: true };
|
|
95
|
+
}
|
|
96
|
+
var ContributeCommand = class {
|
|
97
|
+
constructor(_initCommand) {
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Main entry point for the contribute command
|
|
101
|
+
* Automates fork creation, cloning, and upstream configuration
|
|
102
|
+
*/
|
|
103
|
+
async execute() {
|
|
104
|
+
logger.info(chalk.bold("Setting up iloom contributor environment..."));
|
|
105
|
+
const username = await this.getAuthenticatedUsername();
|
|
106
|
+
logger.success(`Authenticated as ${chalk.cyan(username)}`);
|
|
107
|
+
const hasFork = await this.forkExists(username);
|
|
108
|
+
if (!hasFork) {
|
|
109
|
+
logger.info("Creating fork of iloom-ai/iloom-cli...");
|
|
110
|
+
await this.createFork();
|
|
111
|
+
logger.success("Fork created successfully");
|
|
112
|
+
} else {
|
|
113
|
+
logger.info("Using existing fork");
|
|
114
|
+
}
|
|
115
|
+
const directory = await this.promptForDirectory();
|
|
116
|
+
if (!directory) {
|
|
117
|
+
logger.info("Setup cancelled by user");
|
|
118
|
+
process.exit(0);
|
|
119
|
+
}
|
|
120
|
+
const absolutePath = path.resolve(directory);
|
|
121
|
+
logger.info(`Cloning repository to ${directory}...`);
|
|
122
|
+
await this.cloneRepository(username, directory);
|
|
123
|
+
logger.success("Repository cloned successfully");
|
|
124
|
+
await this.addUpstreamRemote(absolutePath);
|
|
125
|
+
logger.info("Configuring iloom settings...");
|
|
126
|
+
await this.configureSettings(absolutePath);
|
|
127
|
+
logger.success("Settings configured");
|
|
128
|
+
logger.success(chalk.bold.green("\nContributor environment setup complete!"));
|
|
129
|
+
logger.info(`
|
|
130
|
+
Next steps:`);
|
|
131
|
+
logger.info(` 1. cd ${directory}`);
|
|
132
|
+
logger.info(` 2. pnpm install`);
|
|
133
|
+
logger.info(` 3. iloom start <issue_number>`);
|
|
134
|
+
logger.info(`
|
|
135
|
+
Happy contributing!`);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Get authenticated GitHub username
|
|
139
|
+
* @throws Error if not authenticated
|
|
140
|
+
*/
|
|
141
|
+
async getAuthenticatedUsername() {
|
|
142
|
+
const authStatus = await checkGhAuth();
|
|
143
|
+
if (!authStatus.hasAuth) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
"GitHub CLI is not authenticated. Please run: gh auth login"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!authStatus.username) {
|
|
149
|
+
try {
|
|
150
|
+
const user = await executeGhCommand(["api", "user", "--json", "login"]);
|
|
151
|
+
return user.login;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
+
throw new Error(`Unable to determine GitHub username: ${message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return authStatus.username;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Check if user already has a fork of iloom-cli
|
|
161
|
+
*/
|
|
162
|
+
async forkExists(username) {
|
|
163
|
+
try {
|
|
164
|
+
await executeGhCommand(["api", `repos/${username}/iloom-cli`]);
|
|
165
|
+
return true;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error instanceof Error && error.message.includes("Not Found")) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Create a fork of iloom-cli without cloning
|
|
175
|
+
*/
|
|
176
|
+
async createFork() {
|
|
177
|
+
await executeGhCommand(["repo", "fork", ILOOM_REPO, "--clone=false"]);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Clone the repository using simplified gh CLI approach
|
|
181
|
+
*/
|
|
182
|
+
async cloneRepository(username, directory) {
|
|
183
|
+
const repoIdentifier = `${username}/iloom-cli`;
|
|
184
|
+
await executeGhCommand(["repo", "clone", repoIdentifier, directory]);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Add upstream remote if it doesn't already exist
|
|
188
|
+
*/
|
|
189
|
+
async addUpstreamRemote(directory) {
|
|
190
|
+
try {
|
|
191
|
+
await executeGitCommand(["remote", "get-url", "upstream"], { cwd: directory });
|
|
192
|
+
logger.info("Upstream remote already configured");
|
|
193
|
+
} catch {
|
|
194
|
+
logger.info("Adding upstream remote...");
|
|
195
|
+
await executeGitCommand(
|
|
196
|
+
["remote", "add", "upstream", UPSTREAM_URL],
|
|
197
|
+
{ cwd: directory }
|
|
198
|
+
);
|
|
199
|
+
logger.success("Upstream remote configured");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Prompt for directory with validation and retry loop
|
|
204
|
+
* @returns The validated directory path, or null if user cancels
|
|
205
|
+
*/
|
|
206
|
+
async promptForDirectory() {
|
|
207
|
+
const maxRetries = 3;
|
|
208
|
+
let attempts = 0;
|
|
209
|
+
while (attempts < maxRetries) {
|
|
210
|
+
const directory = await promptInput(
|
|
211
|
+
"Where should the repository be cloned?",
|
|
212
|
+
"./iloom-cli"
|
|
213
|
+
);
|
|
214
|
+
if (!directory || directory.trim() === "") {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const trimmed = directory.trim();
|
|
218
|
+
const validation = validateDirectoryPath(trimmed);
|
|
219
|
+
if (validation.isValid) {
|
|
220
|
+
return trimmed;
|
|
221
|
+
}
|
|
222
|
+
attempts++;
|
|
223
|
+
if (attempts < maxRetries) {
|
|
224
|
+
logger.error(`${validation.error}`);
|
|
225
|
+
logger.info(`Please try again (${maxRetries - attempts} attempts remaining)`);
|
|
226
|
+
} else {
|
|
227
|
+
logger.error(`${validation.error}`);
|
|
228
|
+
logger.error("Maximum retry attempts reached");
|
|
229
|
+
throw new Error(`Invalid directory after ${maxRetries} attempts: ${validation.error}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Configure .iloom/settings.json with upstream remote
|
|
236
|
+
*/
|
|
237
|
+
async configureSettings(directory) {
|
|
238
|
+
const iloomDir = path.join(directory, ".iloom");
|
|
239
|
+
const settingsPath = path.join(iloomDir, "settings.local.json");
|
|
240
|
+
await mkdir(iloomDir, { recursive: true });
|
|
241
|
+
const settings = {
|
|
242
|
+
issueManagement: {
|
|
243
|
+
github: {
|
|
244
|
+
remote: "upstream"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
export {
|
|
252
|
+
ContributeCommand,
|
|
253
|
+
validateDirectoryName,
|
|
254
|
+
validateDirectoryPath
|
|
255
|
+
};
|
|
256
|
+
//# sourceMappingURL=contribute-3MQJ3XAQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/contribute.ts"],"sourcesContent":["import { logger } from '../utils/logger.js'\nimport { checkGhAuth, executeGhCommand } from '../utils/github.js'\nimport { executeGitCommand } from '../utils/git.js'\nimport { promptInput } from '../utils/prompt.js'\nimport { existsSync, accessSync, constants } from 'fs'\nimport { writeFile, mkdir } from 'fs/promises'\nimport path from 'path'\nimport { InitCommand } from './init.js'\nimport chalk from 'chalk'\n\nconst ILOOM_REPO = 'iloom-ai/iloom-cli'\nconst UPSTREAM_URL = 'https://github.com/iloom-ai/iloom-cli.git'\n\n// Maximum path length for most file systems\nconst MAX_PATH_LENGTH = 255\n\n// Reserved names on Windows (also avoid on all platforms for portability)\nconst RESERVED_NAMES = [\n\t'CON', 'PRN', 'AUX', 'NUL',\n\t'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',\n\t'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',\n]\n\n// Invalid characters for directory names (cross-platform)\n// eslint-disable-next-line no-control-regex\nconst INVALID_CHARS_PATTERN = /[<>:\"|?*\\x00-\\x1f]/\n\n\n/**\n * Validation result for directory input\n */\ninterface DirectoryValidationResult {\n\tisValid: boolean\n\terror?: string\n}\n\n/**\n * Validate directory name format\n * @param directoryName - The directory name (not full path)\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryName(directoryName: string): DirectoryValidationResult {\n\t// Check for empty or whitespace-only\n\tif (!directoryName || directoryName.trim() === '') {\n\t\treturn { isValid: false, error: 'Directory name cannot be empty' }\n\t}\n\n\tconst trimmed = directoryName.trim()\n\tconst baseName = path.basename(trimmed)\n\n\t// Check for invalid characters\n\tif (INVALID_CHARS_PATTERN.test(baseName)) {\n\t\treturn { isValid: false, error: 'Directory name contains invalid characters (<>:\"|?*)' }\n\t}\n\n\t// Check for reserved names (case-insensitive)\n\tif (RESERVED_NAMES.includes(baseName.toUpperCase())) {\n\t\treturn { isValid: false, error: `\"${baseName}\" is a reserved name and cannot be used` }\n\t}\n\n\t// Check for names that start/end with dots or spaces (problematic on some systems)\n\tif (baseName.startsWith('.') && baseName === '.') {\n\t\treturn { isValid: false, error: 'Directory name cannot be just a dot' }\n\t}\n\tif (baseName.endsWith('.') || baseName.endsWith(' ')) {\n\t\treturn { isValid: false, error: 'Directory name cannot end with a dot or space' }\n\t}\n\n\treturn { isValid: true }\n}\n\n/**\n * Validate full directory path\n * @param directoryPath - The full directory path\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryPath(directoryPath: string): DirectoryValidationResult {\n\t// First validate the directory name component\n\tconst nameValidation = validateDirectoryName(directoryPath)\n\tif (!nameValidation.isValid) {\n\t\treturn nameValidation\n\t}\n\n\tconst trimmed = directoryPath.trim()\n\tconst absolutePath = path.resolve(trimmed)\n\n\t// Check path length\n\tif (absolutePath.length > MAX_PATH_LENGTH) {\n\t\treturn {\n\t\t\tisValid: false,\n\t\t\terror: `Path is too long (${absolutePath.length} characters). Maximum is ${MAX_PATH_LENGTH} characters.`\n\t\t}\n\t}\n\n\t// Check if directory already exists\n\tif (existsSync(absolutePath)) {\n\t\treturn { isValid: false, error: `Directory already exists: ${trimmed}` }\n\t}\n\n\t// Check if parent directory exists\n\tconst parentDir = path.dirname(absolutePath)\n\tif (!existsSync(parentDir)) {\n\t\treturn { isValid: false, error: `Parent directory does not exist: ${parentDir}` }\n\t}\n\n\t// Check if parent directory is writable\n\ttry {\n\t\taccessSync(parentDir, constants.W_OK)\n\t} catch {\n\t\treturn { isValid: false, error: `Parent directory is not writable: ${parentDir}` }\n\t}\n\n\treturn { isValid: true }\n}\n\n\n/**\n * ContributeCommand - Set up local development environment for contributing to iloom\n * Implements issue #220: streamlined contributor onboarding workflow\n */\nexport class ContributeCommand {\n\tconstructor(_initCommand?: InitCommand) {}\n\n\t/**\n\t * Main entry point for the contribute command\n\t * Automates fork creation, cloning, and upstream configuration\n\t */\n\tpublic async execute(): Promise<void> {\n\t\tlogger.info(chalk.bold('Setting up iloom contributor environment...'))\n\n\t\t// Step 1: Verify gh CLI authenticated\n\t\tconst username = await this.getAuthenticatedUsername()\n\t\tlogger.success(`Authenticated as ${chalk.cyan(username)}`)\n\n\t\t// Step 2: Check for existing fork\n\t\tconst hasFork = await this.forkExists(username)\n\n\t\t// Step 3: Create fork if needed\n\t\tif (!hasFork) {\n\t\t\tlogger.info('Creating fork of iloom-ai/iloom-cli...')\n\t\t\tawait this.createFork()\n\t\t\tlogger.success('Fork created successfully')\n\t\t} else {\n\t\t\tlogger.info('Using existing fork')\n\t\t}\n\n\t\t// Step 4: Prompt for directory with validation and retry loop\n\t\tconst directory = await this.promptForDirectory()\n\n\t\t// Handle cancelled input\n\t\tif (!directory) {\n\t\t\tlogger.info('Setup cancelled by user')\n\t\t\tprocess.exit(0)\n\t\t}\n\n\t\tconst absolutePath = path.resolve(directory)\n\n\t\t// Step 5: Clone repository (gh CLI handles SSH/HTTPS automatically based on git config)\n\t\tlogger.info(`Cloning repository to ${directory}...`)\n\t\tawait this.cloneRepository(username, directory)\n\t\tlogger.success('Repository cloned successfully')\n\n\t\t// Step 6: Add upstream remote if it doesn't exist\n\t\tawait this.addUpstreamRemote(absolutePath)\n\n\t\t// Step 7: Configure settings\n\t\tlogger.info('Configuring iloom settings...')\n\t\tawait this.configureSettings(absolutePath)\n\t\tlogger.success('Settings configured')\n\n\t\tlogger.success(chalk.bold.green('\\nContributor environment setup complete!'))\n\t\tlogger.info(`\\nNext steps:`)\n\t\tlogger.info(` 1. cd ${directory}`)\n\t\tlogger.info(` 2. pnpm install`)\n\t\tlogger.info(` 3. iloom start <issue_number>`)\n\t\tlogger.info(`\\nHappy contributing!`)\n\t}\n\n\t/**\n\t * Get authenticated GitHub username\n\t * @throws Error if not authenticated\n\t */\n\tprivate async getAuthenticatedUsername(): Promise<string> {\n\t\tconst authStatus = await checkGhAuth()\n\n\t\tif (!authStatus.hasAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t'GitHub CLI is not authenticated. Please run: gh auth login'\n\t\t\t)\n\t\t}\n\n\t\tif (!authStatus.username) {\n\t\t\t// Try to fetch username from gh api if not in auth status\n\t\t\ttry {\n\t\t\t\tconst user = await executeGhCommand<{ login: string }>(['api', 'user', '--json', 'login'])\n\t\t\t\treturn user.login\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error)\n\t\t\t\tthrow new Error(`Unable to determine GitHub username: ${message}`)\n\t\t\t}\n\t\t}\n\n\t\treturn authStatus.username\n\t}\n\n\t/**\n\t * Check if user already has a fork of iloom-cli\n\t */\n\tprivate async forkExists(username: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait executeGhCommand(['api', `repos/${username}/iloom-cli`])\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\t// 404 means no fork exists\n\t\t\tif (error instanceof Error && error.message.includes('Not Found')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Create a fork of iloom-cli without cloning\n\t */\n\tprivate async createFork(): Promise<void> {\n\t\tawait executeGhCommand(['repo', 'fork', ILOOM_REPO, '--clone=false'])\n\t}\n\n\n\t/**\n\t * Clone the repository using simplified gh CLI approach\n\t */\n\tprivate async cloneRepository(\n\t\tusername: string,\n\t\tdirectory: string\n\t): Promise<void> {\n\t\tconst repoIdentifier = `${username}/iloom-cli`\n\t\t// Always use gh repo clone - it handles SSH/HTTPS based on user's git config\n\t\tawait executeGhCommand(['repo', 'clone', repoIdentifier, directory])\n\t}\n\n\t/**\n\t * Add upstream remote if it doesn't already exist\n\t */\n\tprivate async addUpstreamRemote(directory: string): Promise<void> {\n\t\ttry {\n\t\t\t// Check if upstream remote exists\n\t\t\tawait executeGitCommand(['remote', 'get-url', 'upstream'], { cwd: directory })\n\t\t\tlogger.info('Upstream remote already configured')\n\t\t} catch {\n\t\t\t// Upstream doesn't exist, add it\n\t\t\tlogger.info('Adding upstream remote...')\n\t\t\tawait executeGitCommand(\n\t\t\t\t['remote', 'add', 'upstream', UPSTREAM_URL],\n\t\t\t\t{ cwd: directory }\n\t\t\t)\n\t\t\tlogger.success('Upstream remote configured')\n\t\t}\n\t}\n\n\t/**\n\t * Prompt for directory with validation and retry loop\n\t * @returns The validated directory path, or null if user cancels\n\t */\n\tprivate async promptForDirectory(): Promise<string | null> {\n\t\tconst maxRetries = 3\n\t\tlet attempts = 0\n\n\t\twhile (attempts < maxRetries) {\n\t\t\tconst directory = await promptInput(\n\t\t\t\t'Where should the repository be cloned?',\n\t\t\t\t'./iloom-cli'\n\t\t\t)\n\n\t\t\t// Handle empty input (user cancelled by entering empty string after exhausting default)\n\t\t\tif (!directory || directory.trim() === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst trimmed = directory.trim()\n\n\t\t\t// Validate the directory path\n\t\t\tconst validation = validateDirectoryPath(trimmed)\n\t\t\tif (validation.isValid) {\n\t\t\t\treturn trimmed\n\t\t\t}\n\n\t\t\t// Show error and increment attempts\n\t\t\tattempts++\n\t\t\tif (attempts < maxRetries) {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.info(`Please try again (${maxRetries - attempts} attempts remaining)`)\n\t\t\t} else {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.error('Maximum retry attempts reached')\n\t\t\t\tthrow new Error(`Invalid directory after ${maxRetries} attempts: ${validation.error}`)\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n\n\n\t/**\n\t * Configure .iloom/settings.json with upstream remote\n\t */\n\tprivate async configureSettings(directory: string): Promise<void> {\n\t\tconst iloomDir = path.join(directory, '.iloom')\n\t\tconst settingsPath = path.join(iloomDir, 'settings.local.json')\n\n\t\t// Create .iloom directory\n\t\tawait mkdir(iloomDir, { recursive: true })\n\n\t\t// Create settings.json with upstream remote configuration\n\t\tconst settings = {\n\t\t\tissueManagement: {\n\t\t\t\tgithub: {\n\t\t\t\t\tremote: 'upstream',\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tawait writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\\n')\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generateGitHubCommentMcpConfig
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-VCMMAFXQ.js";
|
|
5
5
|
import {
|
|
6
6
|
AgentManager
|
|
7
7
|
} from "./chunk-OC4H6HJD.js";
|
|
8
8
|
import {
|
|
9
9
|
openBrowser
|
|
10
10
|
} from "./chunk-YETJNRQM.js";
|
|
11
|
+
import {
|
|
12
|
+
getConfiguredRepoFromSettings,
|
|
13
|
+
hasMultipleRemotes
|
|
14
|
+
} from "./chunk-FXV24OYZ.js";
|
|
11
15
|
import {
|
|
12
16
|
GitHubService
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-ZWXJBSUW.js";
|
|
18
|
+
import "./chunk-JXQXSC45.js";
|
|
15
19
|
import {
|
|
16
20
|
launchClaude
|
|
17
21
|
} from "./chunk-PXZBAC2M.js";
|
|
18
22
|
import {
|
|
19
23
|
SettingsManager
|
|
20
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
21
25
|
import {
|
|
22
26
|
waitForKeypress
|
|
23
27
|
} from "./chunk-JNKJ7NJV.js";
|
|
@@ -44,19 +48,25 @@ var EnhanceCommand = class {
|
|
|
44
48
|
async execute(input) {
|
|
45
49
|
const { issueNumber, options } = input;
|
|
46
50
|
const { author } = options;
|
|
51
|
+
const settings = await this.settingsManager.loadSettings();
|
|
52
|
+
let repo;
|
|
53
|
+
const multipleRemotes = await hasMultipleRemotes();
|
|
54
|
+
if (multipleRemotes) {
|
|
55
|
+
repo = await getConfiguredRepoFromSettings(settings);
|
|
56
|
+
logger.info(`Using GitHub repository: ${repo}`);
|
|
57
|
+
}
|
|
47
58
|
this.validateIssueNumber(issueNumber);
|
|
48
59
|
logger.info(`Fetching issue #${issueNumber}...`);
|
|
49
|
-
const issue = await this.gitHubService.fetchIssue(issueNumber);
|
|
60
|
+
const issue = await this.gitHubService.fetchIssue(issueNumber, repo);
|
|
50
61
|
logger.debug("Issue fetched successfully", { number: issue.number, title: issue.title });
|
|
51
62
|
logger.debug("Loading agent configurations...");
|
|
52
|
-
const settings = await this.settingsManager.loadSettings();
|
|
53
63
|
const loadedAgents = await this.agentManager.loadAgents(settings);
|
|
54
64
|
const agents = this.agentManager.formatForCli(loadedAgents);
|
|
55
65
|
let mcpConfig;
|
|
56
66
|
let allowedTools;
|
|
57
67
|
let disallowedTools;
|
|
58
68
|
try {
|
|
59
|
-
mcpConfig = await generateGitHubCommentMcpConfig("issue");
|
|
69
|
+
mcpConfig = await generateGitHubCommentMcpConfig("issue", repo);
|
|
60
70
|
logger.debug("Generated MCP configuration for GitHub comment broker");
|
|
61
71
|
allowedTools = [
|
|
62
72
|
"mcp__github_comment__create_comment",
|
|
@@ -163,4 +173,4 @@ IMPORTANT: When you create your analysis comment, tag @${author} in the "Questio
|
|
|
163
173
|
export {
|
|
164
174
|
EnhanceCommand
|
|
165
175
|
};
|
|
166
|
-
//# sourceMappingURL=enhance-
|
|
176
|
+
//# sourceMappingURL=enhance-VGWUX474.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/enhance.ts"],"sourcesContent":["import type { GitHubService } from '../lib/GitHubService.js'\nimport type { AgentManager } from '../lib/AgentManager.js'\nimport type { SettingsManager } from '../lib/SettingsManager.js'\nimport { launchClaude } from '../utils/claude.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { waitForKeypress } from '../utils/prompt.js'\nimport { logger } from '../utils/logger.js'\nimport { generateGitHubCommentMcpConfig } from '../utils/mcp.js'\nimport { GitHubService as DefaultGitHubService } from '../lib/GitHubService.js'\nimport { AgentManager as DefaultAgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager as DefaultSettingsManager } from '../lib/SettingsManager.js'\nimport { getConfiguredRepoFromSettings, hasMultipleRemotes } from '../utils/remote.js'\n\nexport interface EnhanceCommandInput {\n\tissueNumber: number\n\toptions: EnhanceOptions\n}\n\nexport interface EnhanceOptions {\n\tnoBrowser?: boolean // Skip browser opening prompt\n\tauthor?: string // GitHub username of issue author for tagging\n}\n\n/**\n * Command to enhance existing GitHub issues with AI assistance.\n * Applies the issue enhancer agent to an existing issue, respecting idempotency checks.\n */\nexport class EnhanceCommand {\n\tprivate gitHubService: GitHubService\n\tprivate agentManager: AgentManager\n\tprivate settingsManager: SettingsManager\n\n\tconstructor(\n\t\tgitHubService?: GitHubService,\n\t\tagentManager?: AgentManager,\n\t\tsettingsManager?: SettingsManager\n\t) {\n\t\t// Use provided services or create defaults\n\t\tthis.gitHubService = gitHubService ?? new DefaultGitHubService()\n\t\tthis.agentManager = agentManager ?? new DefaultAgentManager()\n\t\tthis.settingsManager = settingsManager ?? new DefaultSettingsManager()\n\t}\n\n\t/**\n\t * Execute the enhance command workflow:\n\t * 1. Validate issue number\n\t * 2. Fetch issue to verify it exists\n\t * 3. Load agent configurations\n\t * 4. Invoke Claude CLI with enhancer agent\n\t * 5. Parse response to determine outcome\n\t * 6. Handle browser interaction based on outcome\n\t */\n\tpublic async execute(input: EnhanceCommandInput): Promise<void> {\n\t\tconst { issueNumber, options } = input\n\t\tconst { author } = options\n\n\t\t// Step 0: Load settings and get configured repo for GitHub operations\n\t\tconst settings = await this.settingsManager.loadSettings()\n\t\tlet repo: string | undefined\n\n\t\tconst multipleRemotes = await hasMultipleRemotes()\n\t\tif (multipleRemotes) {\n\t\t\trepo = await getConfiguredRepoFromSettings(settings)\n\t\t\tlogger.info(`Using GitHub repository: ${repo}`)\n\t\t}\n\n\t\t// Step 1: Validate issue number\n\t\tthis.validateIssueNumber(issueNumber)\n\n\t\t// Step 2: Fetch issue to verify it exists\n\t\tlogger.info(`Fetching issue #${issueNumber}...`)\n\t\tconst issue = await this.gitHubService.fetchIssue(issueNumber, repo)\n\t\tlogger.debug('Issue fetched successfully', { number: issue.number, title: issue.title })\n\n\t\t// Step 3: Load agent configurations\n\t\tlogger.debug('Loading agent configurations...')\n\t\tconst loadedAgents = await this.agentManager.loadAgents(settings)\n\t\tconst agents = this.agentManager.formatForCli(loadedAgents)\n\n\t\t// Step 3.5: Generate MCP config and tool filtering for GitHub comment broker\n\t\tlet mcpConfig: Record<string, unknown>[] | undefined\n\t\tlet allowedTools: string[] | undefined\n\t\tlet disallowedTools: string[] | undefined\n\n\t\ttry {\n\t\t\tmcpConfig = await generateGitHubCommentMcpConfig('issue', repo)\n\t\t\tlogger.debug('Generated MCP configuration for GitHub comment broker')\n\n\t\t\t// Configure tool filtering for issue workflows\n\t\t\tallowedTools = [\n\t\t\t\t'mcp__github_comment__create_comment',\n\t\t\t\t'mcp__github_comment__update_comment',\n\t\t\t]\n\t\t\tdisallowedTools = ['Bash(gh api:*)']\n\n\t\t\tlogger.debug('Configured tool filtering for issue workflow', { allowedTools, disallowedTools })\n\t\t} catch (error) {\n\t\t\t// Log warning but continue without MCP\n\t\t\tlogger.warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t}\n\n\t\t// Step 4: Invoke Claude CLI with enhancer agent\n\t\tlogger.info('Invoking enhancer agent. This may take a moment...')\n\t\tconst prompt = this.constructPrompt(issueNumber, author)\n\t\tconst response = await launchClaude(prompt, {\n\t\t\theadless: true,\n\t\t\tmodel: 'sonnet',\n\t\t\tagents,\n\t\t\t...(mcpConfig && { mcpConfig }),\n\t\t\t...(allowedTools && { allowedTools }),\n\t\t\t...(disallowedTools && { disallowedTools }),\n\t\t})\n\n\t\t// Step 5: Parse response to determine outcome\n\t\tconst result = this.parseEnhancerResponse(response)\n\n\t\t// Step 6: Handle browser interaction based on outcome\n\t\tif (!result.enhanced) {\n\t\t\tlogger.success('Issue already has thorough description. No enhancement needed.')\n\t\t\treturn\n\t\t}\n\n\t\tlogger.success(`Issue #${issueNumber} enhanced successfully!`)\n\t\tlogger.info(`Enhanced specification available at: ${result.url}`)\n\n\t\t// Prompt to open browser (unless --no-browser flag is set)\n\t\tif (!options.noBrowser && result.url) {\n\t\t\tawait this.promptAndOpenBrowser(result.url)\n\t\t}\n\t}\n\n\t/**\n\t * Validate that issue number is a valid positive integer\n\t */\n\tprivate validateIssueNumber(issueNumber: number): void {\n\t\tif (issueNumber === undefined || issueNumber === null) {\n\t\t\tthrow new Error('Issue number is required')\n\t\t}\n\n\t\tif (typeof issueNumber !== 'number' || Number.isNaN(issueNumber) || issueNumber <= 0 || !Number.isInteger(issueNumber)) {\n\t\t\tthrow new Error('Issue number must be a valid positive integer')\n\t\t}\n\t}\n\n\t/**\n\t * Construct the prompt for the orchestrating Claude instance.\n\t * This prompt is very clear about expected output format to ensure reliable parsing.\n\t */\n\tprivate constructPrompt(issueNumber: number, author?: string): string {\n\t\tconst authorInstruction = author\n\t\t\t? `\\nIMPORTANT: When you create your analysis comment, tag @${author} in the \"Questions for Reporter\" section if you have questions.\\n`\n\t\t\t: ''\n\n\t\treturn `Execute @agent-iloom-issue-enhancer ${issueNumber}${authorInstruction}\n\n## OUTPUT REQUIREMENTS\n* If the issue was not enhanced, return ONLY: \"No enhancement needed\"\n* If the issue WAS enhanced, return ONLY: <FULL URL OF THE COMMENT INCLUDING COMMENT ID>\n* If you encounter permission/authentication/access errors, return ONLY: \"Permission denied: <specific error description>\"\n* IMPORTANT: Return ONLY one of the above - DO NOT include commentary such as \"I created a comment at <URL>\" or \"I examined the issue and found no enhancement was necessary\"\n* CONTEXT: Your output is going to be parsed programmatically, so adherence to the output requirements is CRITICAL.`\n\t}\n\n\t/**\n\t * Parse the response from the enhancer agent.\n\t * Returns either { enhanced: false } or { enhanced: true, url: \"...\" }\n\t * Throws specific errors for permission issues.\n\t */\n\tprivate parseEnhancerResponse(response: string | void): { enhanced: boolean; url?: string } {\n\t\t// Handle empty or void response\n\t\tif (!response || typeof response !== 'string') {\n\t\t\tthrow new Error('No response from enhancer agent')\n\t\t}\n\n\t\tconst trimmed = response.trim()\n\t\n\t\tlogger.debug(`RESPONSE FROM ENHANCER AGENT: '${trimmed}'`)\n\n\t\t// Check for permission denied errors (case-insensitive)\n\t\tif (trimmed.toLowerCase().startsWith('permission denied:')) {\n\t\t\tconst errorMessage = trimmed.substring('permission denied:'.length).trim()\n\t\t\tthrow new Error(`Permission denied: ${errorMessage}`)\n\t\t}\n\n\t\t// Check for \"No enhancement needed\" (case-insensitive)\n\t\tif (trimmed.toLowerCase().includes('no enhancement needed')) {\n\t\t\treturn { enhanced: false }\n\t\t}\n\n\t\t// Check if response looks like a GitHub comment URL\n\t\tconst urlPattern = /https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/\\d+#issuecomment-\\d+/\n\t\tconst match = trimmed.match(urlPattern)\n\n\t\tif (match) {\n\t\t\treturn { enhanced: true, url: match[0] }\n\t\t}\n\n\t\t// Unexpected response format\n\t\tthrow new Error(`Unexpected response from enhancer agent: ${trimmed}`)\n\t}\n\n\t/**\n\t * Prompt user and open browser to view enhanced issue.\n\t * Matches the pattern from the issue specification.\n\t */\n\tprivate async promptAndOpenBrowser(commentUrl: string): Promise<void> {\n\t\ttry {\n\t\t\t// Prompt user with custom message\n\t\t\tconst key = await waitForKeypress(\n\t\t\t\t'Press q to quit or any other key to view the enhanced issue in a web browser...'\n\t\t\t)\n\n\t\t\t// Check if user pressed 'q' to quit\n\t\t\tif (key.toLowerCase() === 'q') {\n\t\t\t\tlogger.info('Skipping browser opening')\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Open browser with comment URL\n\t\t\tawait openBrowser(commentUrl)\n\t\t} catch (error) {\n\t\t\t// Browser opening failures should not be fatal\n\t\t\tlogger.warn(`Failed to open browser: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t}\n\t}\n\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ProjectCapabilityDetector
|
|
4
|
+
} from "./chunk-CWR2SANQ.js";
|
|
5
|
+
import "./chunk-2ZPFJQ3B.js";
|
|
2
6
|
import {
|
|
3
7
|
IssueEnhancementService
|
|
4
8
|
} from "./chunk-PR7FKQBG.js";
|
|
@@ -6,20 +10,16 @@ import {
|
|
|
6
10
|
AgentManager
|
|
7
11
|
} from "./chunk-OC4H6HJD.js";
|
|
8
12
|
import "./chunk-YETJNRQM.js";
|
|
9
|
-
import {
|
|
10
|
-
ProjectCapabilityDetector
|
|
11
|
-
} from "./chunk-CWR2SANQ.js";
|
|
12
|
-
import "./chunk-2ZPFJQ3B.js";
|
|
13
13
|
import {
|
|
14
14
|
GitHubService
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-ZWXJBSUW.js";
|
|
16
|
+
import "./chunk-JXQXSC45.js";
|
|
17
17
|
import {
|
|
18
18
|
getClaudeVersion
|
|
19
19
|
} from "./chunk-PXZBAC2M.js";
|
|
20
20
|
import {
|
|
21
21
|
SettingsManager
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
23
23
|
import "./chunk-JNKJ7NJV.js";
|
|
24
24
|
import {
|
|
25
25
|
logger
|
|
@@ -155,4 +155,4 @@ ${description}`;
|
|
|
155
155
|
export {
|
|
156
156
|
FeedbackCommand
|
|
157
157
|
};
|
|
158
|
-
//# sourceMappingURL=feedback-
|
|
158
|
+
//# sourceMappingURL=feedback-ZOUCCHN4.js.map
|