@iloom/cli 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +33 -0
- package/README.md +711 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js +13 -0
- package/dist/ClaudeContextManager-XOSXQ67R.js.map +1 -0
- package/dist/ClaudeService-YSZ6EXWP.js +12 -0
- package/dist/ClaudeService-YSZ6EXWP.js.map +1 -0
- package/dist/GitHubService-F7Z3XJOS.js +11 -0
- package/dist/GitHubService-F7Z3XJOS.js.map +1 -0
- package/dist/LoomLauncher-MODG2SEM.js +263 -0
- package/dist/LoomLauncher-MODG2SEM.js.map +1 -0
- package/dist/NeonProvider-PAGPUH7F.js +12 -0
- package/dist/NeonProvider-PAGPUH7F.js.map +1 -0
- package/dist/PromptTemplateManager-7FINLRDE.js +9 -0
- package/dist/PromptTemplateManager-7FINLRDE.js.map +1 -0
- package/dist/SettingsManager-VAZF26S2.js +19 -0
- package/dist/SettingsManager-VAZF26S2.js.map +1 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js +146 -0
- package/dist/SettingsMigrationManager-MTQIMI54.js.map +1 -0
- package/dist/add-issue-22JBNOML.js +54 -0
- package/dist/add-issue-22JBNOML.js.map +1 -0
- package/dist/agents/iloom-issue-analyze-and-plan.md +580 -0
- package/dist/agents/iloom-issue-analyzer.md +290 -0
- package/dist/agents/iloom-issue-complexity-evaluator.md +224 -0
- package/dist/agents/iloom-issue-enhancer.md +266 -0
- package/dist/agents/iloom-issue-implementer.md +262 -0
- package/dist/agents/iloom-issue-planner.md +358 -0
- package/dist/agents/iloom-issue-reviewer.md +63 -0
- package/dist/chunk-2ZPFJQ3B.js +63 -0
- package/dist/chunk-2ZPFJQ3B.js.map +1 -0
- package/dist/chunk-37DYYFVK.js +29 -0
- package/dist/chunk-37DYYFVK.js.map +1 -0
- package/dist/chunk-BLCTGFZN.js +121 -0
- package/dist/chunk-BLCTGFZN.js.map +1 -0
- package/dist/chunk-CP2NU2JC.js +545 -0
- package/dist/chunk-CP2NU2JC.js.map +1 -0
- package/dist/chunk-CWR2SANQ.js +39 -0
- package/dist/chunk-CWR2SANQ.js.map +1 -0
- package/dist/chunk-F3XBU2R7.js +110 -0
- package/dist/chunk-F3XBU2R7.js.map +1 -0
- package/dist/chunk-GEHQXLEI.js +130 -0
- package/dist/chunk-GEHQXLEI.js.map +1 -0
- package/dist/chunk-GYCR2LOU.js +143 -0
- package/dist/chunk-GYCR2LOU.js.map +1 -0
- package/dist/chunk-GZP4UGGM.js +48 -0
- package/dist/chunk-GZP4UGGM.js.map +1 -0
- package/dist/chunk-H4E4THUZ.js +55 -0
- package/dist/chunk-H4E4THUZ.js.map +1 -0
- package/dist/chunk-HPJJSYNS.js +644 -0
- package/dist/chunk-HPJJSYNS.js.map +1 -0
- package/dist/chunk-JBH2ZYYZ.js +220 -0
- package/dist/chunk-JBH2ZYYZ.js.map +1 -0
- package/dist/chunk-JNKJ7NJV.js +78 -0
- package/dist/chunk-JNKJ7NJV.js.map +1 -0
- package/dist/chunk-JQ7VOSTC.js +437 -0
- package/dist/chunk-JQ7VOSTC.js.map +1 -0
- package/dist/chunk-KQDEK2ZW.js +199 -0
- package/dist/chunk-KQDEK2ZW.js.map +1 -0
- package/dist/chunk-O2QWO64Z.js +179 -0
- package/dist/chunk-O2QWO64Z.js.map +1 -0
- package/dist/chunk-OC4H6HJD.js +248 -0
- package/dist/chunk-OC4H6HJD.js.map +1 -0
- package/dist/chunk-PR7FKQBG.js +120 -0
- package/dist/chunk-PR7FKQBG.js.map +1 -0
- package/dist/chunk-PXZBAC2M.js +250 -0
- package/dist/chunk-PXZBAC2M.js.map +1 -0
- package/dist/chunk-QEPVTTHD.js +383 -0
- package/dist/chunk-QEPVTTHD.js.map +1 -0
- package/dist/chunk-RSRO7564.js +203 -0
- package/dist/chunk-RSRO7564.js.map +1 -0
- package/dist/chunk-SJUQ2NDR.js +146 -0
- package/dist/chunk-SJUQ2NDR.js.map +1 -0
- package/dist/chunk-SPYPLHMK.js +177 -0
- package/dist/chunk-SPYPLHMK.js.map +1 -0
- package/dist/chunk-SSCQCCJ7.js +75 -0
- package/dist/chunk-SSCQCCJ7.js.map +1 -0
- package/dist/chunk-SSR5AVRJ.js +41 -0
- package/dist/chunk-SSR5AVRJ.js.map +1 -0
- package/dist/chunk-T7QPXANZ.js +315 -0
- package/dist/chunk-T7QPXANZ.js.map +1 -0
- package/dist/chunk-U3WU5OWO.js +203 -0
- package/dist/chunk-U3WU5OWO.js.map +1 -0
- package/dist/chunk-W3DQTW63.js +124 -0
- package/dist/chunk-W3DQTW63.js.map +1 -0
- package/dist/chunk-WKEWRSDB.js +151 -0
- package/dist/chunk-WKEWRSDB.js.map +1 -0
- package/dist/chunk-Y7SAGNUT.js +66 -0
- package/dist/chunk-Y7SAGNUT.js.map +1 -0
- package/dist/chunk-YETJNRQM.js +39 -0
- package/dist/chunk-YETJNRQM.js.map +1 -0
- package/dist/chunk-YYSKGAZT.js +384 -0
- package/dist/chunk-YYSKGAZT.js.map +1 -0
- package/dist/chunk-ZZZWQGTS.js +169 -0
- package/dist/chunk-ZZZWQGTS.js.map +1 -0
- package/dist/claude-7LUVDZZ4.js +17 -0
- package/dist/claude-7LUVDZZ4.js.map +1 -0
- package/dist/cleanup-3LUWPSM7.js +412 -0
- package/dist/cleanup-3LUWPSM7.js.map +1 -0
- package/dist/cli-overrides-XFZWY7CM.js +16 -0
- package/dist/cli-overrides-XFZWY7CM.js.map +1 -0
- package/dist/cli.js +603 -0
- package/dist/cli.js.map +1 -0
- package/dist/color-ZVALX37U.js +21 -0
- package/dist/color-ZVALX37U.js.map +1 -0
- package/dist/enhance-XJIQHVPD.js +166 -0
- package/dist/enhance-XJIQHVPD.js.map +1 -0
- package/dist/env-MDFL4ZXL.js +23 -0
- package/dist/env-MDFL4ZXL.js.map +1 -0
- package/dist/feedback-23CLXKFT.js +158 -0
- package/dist/feedback-23CLXKFT.js.map +1 -0
- package/dist/finish-CY4CIH6O.js +1608 -0
- package/dist/finish-CY4CIH6O.js.map +1 -0
- package/dist/git-LVRZ57GJ.js +43 -0
- package/dist/git-LVRZ57GJ.js.map +1 -0
- package/dist/ignite-WXEF2ID5.js +359 -0
- package/dist/ignite-WXEF2ID5.js.map +1 -0
- package/dist/index.d.ts +1341 -0
- package/dist/index.js +3058 -0
- package/dist/index.js.map +1 -0
- package/dist/init-RHACUR4E.js +123 -0
- package/dist/init-RHACUR4E.js.map +1 -0
- package/dist/installation-detector-VARGFFRZ.js +11 -0
- package/dist/installation-detector-VARGFFRZ.js.map +1 -0
- package/dist/logger-MKYH4UDV.js +12 -0
- package/dist/logger-MKYH4UDV.js.map +1 -0
- package/dist/mcp/chunk-6SDFJ42P.js +62 -0
- package/dist/mcp/chunk-6SDFJ42P.js.map +1 -0
- package/dist/mcp/claude-YHHHLSXH.js +249 -0
- package/dist/mcp/claude-YHHHLSXH.js.map +1 -0
- package/dist/mcp/color-QS5BFCNN.js +168 -0
- package/dist/mcp/color-QS5BFCNN.js.map +1 -0
- package/dist/mcp/github-comment-server.js +165 -0
- package/dist/mcp/github-comment-server.js.map +1 -0
- package/dist/mcp/terminal-SDCMDVD7.js +202 -0
- package/dist/mcp/terminal-SDCMDVD7.js.map +1 -0
- package/dist/open-X6BTENPV.js +278 -0
- package/dist/open-X6BTENPV.js.map +1 -0
- package/dist/prompt-ANTQWHUF.js +13 -0
- package/dist/prompt-ANTQWHUF.js.map +1 -0
- package/dist/prompts/issue-prompt.txt +230 -0
- package/dist/prompts/pr-prompt.txt +35 -0
- package/dist/prompts/regular-prompt.txt +14 -0
- package/dist/run-2JCPQAX3.js +278 -0
- package/dist/run-2JCPQAX3.js.map +1 -0
- package/dist/schema/settings.schema.json +221 -0
- package/dist/start-LWVRBJ6S.js +982 -0
- package/dist/start-LWVRBJ6S.js.map +1 -0
- package/dist/terminal-3D6TUAKJ.js +16 -0
- package/dist/terminal-3D6TUAKJ.js.map +1 -0
- package/dist/test-git-XPF4SZXJ.js +52 -0
- package/dist/test-git-XPF4SZXJ.js.map +1 -0
- package/dist/test-prefix-XGFXFAYN.js +68 -0
- package/dist/test-prefix-XGFXFAYN.js.map +1 -0
- package/dist/test-tabs-JRKY3QMM.js +69 -0
- package/dist/test-tabs-JRKY3QMM.js.map +1 -0
- package/dist/test-webserver-M2I3EV4J.js +62 -0
- package/dist/test-webserver-M2I3EV4J.js.map +1 -0
- package/dist/update-3ZT2XX2G.js +79 -0
- package/dist/update-3ZT2XX2G.js.map +1 -0
- package/dist/update-notifier-QSSEB5KC.js +11 -0
- package/dist/update-notifier-QSSEB5KC.js.map +1 -0
- package/package.json +113 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-GEHQXLEI.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/env.ts
|
|
7
|
+
import dotenvFlow from "dotenv-flow";
|
|
8
|
+
function parseEnvFile(content) {
|
|
9
|
+
const envMap = /* @__PURE__ */ new Map();
|
|
10
|
+
const lines = content.split("\n");
|
|
11
|
+
for (const line of lines) {
|
|
12
|
+
const trimmedLine = line.trim();
|
|
13
|
+
if (!trimmedLine || trimmedLine.startsWith("#")) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const cleanLine = trimmedLine.startsWith("export ") ? trimmedLine.substring(7) : trimmedLine;
|
|
17
|
+
const equalsIndex = cleanLine.indexOf("=");
|
|
18
|
+
if (equalsIndex === -1) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const key = cleanLine.substring(0, equalsIndex).trim();
|
|
22
|
+
let value = cleanLine.substring(equalsIndex + 1);
|
|
23
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
24
|
+
value = value.substring(1, value.length - 1);
|
|
25
|
+
value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
26
|
+
value = value.replace(/\\n/g, "\n");
|
|
27
|
+
}
|
|
28
|
+
if (key) {
|
|
29
|
+
envMap.set(key, value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return envMap;
|
|
33
|
+
}
|
|
34
|
+
function formatEnvLine(key, value) {
|
|
35
|
+
const escapedValue = value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
36
|
+
return `${key}="${escapedValue}"`;
|
|
37
|
+
}
|
|
38
|
+
function validateEnvVariable(key, _value) {
|
|
39
|
+
if (!key || key.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
valid: false,
|
|
42
|
+
error: "Environment variable key cannot be empty"
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!isValidEnvKey(key)) {
|
|
46
|
+
return {
|
|
47
|
+
valid: false,
|
|
48
|
+
error: `Invalid environment variable name: ${key}. Must start with a letter or underscore and contain only letters, numbers, and underscores.`
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return { valid: true };
|
|
52
|
+
}
|
|
53
|
+
function normalizeLineEndings(content) {
|
|
54
|
+
return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
55
|
+
}
|
|
56
|
+
function extractPort(envContent) {
|
|
57
|
+
const portValue = envContent.get("PORT");
|
|
58
|
+
if (!portValue) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const port = parseInt(portValue, 10);
|
|
62
|
+
if (isNaN(port)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return port;
|
|
66
|
+
}
|
|
67
|
+
function isValidEnvKey(key) {
|
|
68
|
+
if (!key || key.length === 0) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const validKeyRegex = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
72
|
+
return validKeyRegex.test(key);
|
|
73
|
+
}
|
|
74
|
+
function loadEnvIntoProcess(options) {
|
|
75
|
+
logger.debug("Loading environment variables with dotenv-flow", {
|
|
76
|
+
options: {
|
|
77
|
+
path: (options == null ? void 0 : options.path) ?? "current working directory",
|
|
78
|
+
nodeEnv: (options == null ? void 0 : options.nodeEnv) ?? "not specified",
|
|
79
|
+
defaultNodeEnv: (options == null ? void 0 : options.defaultNodeEnv) ?? "development (default)"
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const configOptions = {
|
|
83
|
+
silent: true
|
|
84
|
+
// Don't throw errors if .env files are missing
|
|
85
|
+
};
|
|
86
|
+
if ((options == null ? void 0 : options.path) !== void 0) {
|
|
87
|
+
configOptions.path = options.path;
|
|
88
|
+
logger.debug(`Using custom path: ${options.path}`);
|
|
89
|
+
}
|
|
90
|
+
if ((options == null ? void 0 : options.nodeEnv) !== void 0) {
|
|
91
|
+
configOptions.node_env = options.nodeEnv;
|
|
92
|
+
logger.debug(`Using NODE_ENV: ${options.nodeEnv}`);
|
|
93
|
+
}
|
|
94
|
+
if ((options == null ? void 0 : options.defaultNodeEnv) !== void 0) {
|
|
95
|
+
configOptions.default_node_env = options.defaultNodeEnv;
|
|
96
|
+
logger.debug(`Using default NODE_ENV: ${options.defaultNodeEnv}`);
|
|
97
|
+
} else {
|
|
98
|
+
configOptions.default_node_env = "development";
|
|
99
|
+
logger.debug("Using default NODE_ENV: development");
|
|
100
|
+
}
|
|
101
|
+
logger.debug("dotenv-flow config options:", configOptions);
|
|
102
|
+
const result = dotenvFlow.config(configOptions);
|
|
103
|
+
const returnValue = {};
|
|
104
|
+
if (result.parsed) {
|
|
105
|
+
returnValue.parsed = result.parsed;
|
|
106
|
+
const variableCount = Object.keys(result.parsed).length;
|
|
107
|
+
logger.debug(`Successfully loaded ${variableCount} environment variables`);
|
|
108
|
+
} else {
|
|
109
|
+
logger.debug("No environment variables were parsed");
|
|
110
|
+
}
|
|
111
|
+
if (result.error) {
|
|
112
|
+
returnValue.error = result.error;
|
|
113
|
+
logger.debug("dotenv-flow returned an error", {
|
|
114
|
+
error: result.error.message,
|
|
115
|
+
name: result.error.name
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
logger.debug("dotenv-flow completed without errors");
|
|
119
|
+
}
|
|
120
|
+
return returnValue;
|
|
121
|
+
}
|
|
122
|
+
function loadWorkspaceEnv(workspacePath) {
|
|
123
|
+
const nodeEnv = process.env.NODE_ENV ?? "development";
|
|
124
|
+
logger.debug("Loading workspace environment variables", {
|
|
125
|
+
workspacePath,
|
|
126
|
+
detectedNodeEnv: nodeEnv,
|
|
127
|
+
processNodeEnv: process.env.NODE_ENV ?? "not set"
|
|
128
|
+
});
|
|
129
|
+
return loadEnvIntoProcess({
|
|
130
|
+
path: workspacePath,
|
|
131
|
+
nodeEnv,
|
|
132
|
+
defaultNodeEnv: "development"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export {
|
|
137
|
+
parseEnvFile,
|
|
138
|
+
formatEnvLine,
|
|
139
|
+
validateEnvVariable,
|
|
140
|
+
normalizeLineEndings,
|
|
141
|
+
extractPort,
|
|
142
|
+
isValidEnvKey,
|
|
143
|
+
loadEnvIntoProcess,
|
|
144
|
+
loadWorkspaceEnv
|
|
145
|
+
};
|
|
146
|
+
//# sourceMappingURL=chunk-SJUQ2NDR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/env.ts"],"sourcesContent":["import dotenvFlow, { type DotenvFlowConfigOptions } from 'dotenv-flow'\nimport { logger } from './logger.js'\n\n/**\n * Parse .env file content into key-value map\n * Handles comments, empty lines, quoted/unquoted values, multiline values\n */\nexport function parseEnvFile(content: string): Map<string, string> {\n const envMap = new Map<string, string>()\n const lines = content.split('\\n')\n\n for (const line of lines) {\n const trimmedLine = line.trim()\n\n // Skip empty lines and comments\n if (!trimmedLine || trimmedLine.startsWith('#')) {\n continue\n }\n\n // Remove 'export ' prefix if present\n const cleanLine = trimmedLine.startsWith('export ')\n ? trimmedLine.substring(7)\n : trimmedLine\n\n // Find the first equals sign\n const equalsIndex = cleanLine.indexOf('=')\n if (equalsIndex === -1) {\n continue\n }\n\n const key = cleanLine.substring(0, equalsIndex).trim()\n let value = cleanLine.substring(equalsIndex + 1)\n\n // Handle quoted values\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.substring(1, value.length - 1)\n // Unescape quotes\n value = value.replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\")\n // Unescape newlines\n value = value.replace(/\\\\n/g, '\\n')\n }\n\n if (key) {\n envMap.set(key, value)\n }\n }\n\n return envMap\n}\n\n/**\n * Format environment variable as line for .env file\n * Always quotes values and escapes internal quotes\n */\nexport function formatEnvLine(key: string, value: string): string {\n // Escape quotes and newlines in the value\n const escapedValue = value\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, '\\\\n')\n .replace(/\\r/g, '\\\\r')\n\n return `${key}=\"${escapedValue}\"`\n}\n\n/**\n * Validate environment variable name and value\n */\nexport function validateEnvVariable(\n key: string,\n _value?: string\n): { valid: boolean; error?: string } {\n if (!key || key.length === 0) {\n return {\n valid: false,\n error: 'Environment variable key cannot be empty',\n }\n }\n\n if (!isValidEnvKey(key)) {\n return {\n valid: false,\n error: `Invalid environment variable name: ${key}. Must start with a letter or underscore and contain only letters, numbers, and underscores.`,\n }\n }\n\n // Values can be any string, including empty\n return { valid: true }\n}\n\n/**\n * Normalize line endings for cross-platform compatibility\n */\nexport function normalizeLineEndings(content: string): string {\n return content.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n')\n}\n\n/**\n * Extract port from .env file if present\n */\nexport function extractPort(envContent: Map<string, string>): number | null {\n const portValue = envContent.get('PORT')\n if (!portValue) {\n return null\n }\n\n const port = parseInt(portValue, 10)\n if (isNaN(port)) {\n return null\n }\n\n return port\n}\n\n/**\n * Check if environment variable key is valid\n */\nexport function isValidEnvKey(key: string): boolean {\n if (!key || key.length === 0) {\n return false\n }\n\n // Must start with letter or underscore, followed by letters, numbers, or underscores\n const validKeyRegex = /^[A-Za-z_][A-Za-z0-9_]*$/\n return validKeyRegex.test(key)\n}\n\n/**\n * Load environment variables using dotenv-flow\n * Supports environment-specific files (.env.development, .env.production, etc.)\n * and local overrides (.env.local, .env.development.local)\n */\nexport function loadEnvIntoProcess(options?: {\n path?: string\n nodeEnv?: string\n defaultNodeEnv?: string\n}): { parsed?: Record<string, string>; error?: Error } {\n logger.debug('Loading environment variables with dotenv-flow', {\n options: {\n path: options?.path ?? 'current working directory',\n nodeEnv: options?.nodeEnv ?? 'not specified',\n defaultNodeEnv: options?.defaultNodeEnv ?? 'development (default)'\n }\n })\n\n const configOptions: Partial<DotenvFlowConfigOptions> = {\n silent: true, // Don't throw errors if .env files are missing\n }\n\n // Only add defined values to avoid TypeScript strict type issues\n if (options?.path !== undefined) {\n configOptions.path = options.path\n logger.debug(`Using custom path: ${options.path}`)\n }\n if (options?.nodeEnv !== undefined) {\n configOptions.node_env = options.nodeEnv\n logger.debug(`Using NODE_ENV: ${options.nodeEnv}`)\n }\n if (options?.defaultNodeEnv !== undefined) {\n configOptions.default_node_env = options.defaultNodeEnv\n logger.debug(`Using default NODE_ENV: ${options.defaultNodeEnv}`)\n } else {\n configOptions.default_node_env = 'development'\n logger.debug('Using default NODE_ENV: development')\n }\n\n logger.debug('dotenv-flow config options:', configOptions)\n\n const result = dotenvFlow.config(configOptions)\n\n const returnValue: { parsed?: Record<string, string>; error?: Error } = {}\n\n if (result.parsed) {\n returnValue.parsed = result.parsed as Record<string, string>\n const variableCount = Object.keys(result.parsed).length\n logger.debug(`Successfully loaded ${variableCount} environment variables`)\n } else {\n logger.debug('No environment variables were parsed')\n }\n\n if (result.error) {\n returnValue.error = result.error\n logger.debug('dotenv-flow returned an error', {\n error: result.error.message,\n name: result.error.name\n })\n } else {\n logger.debug('dotenv-flow completed without errors')\n }\n\n return returnValue\n}\n\n/**\n * Load environment variables for a specific workspace\n * Automatically determines environment based on NODE_ENV or defaults to development\n */\nexport function loadWorkspaceEnv(workspacePath: string): {\n parsed?: Record<string, string>\n error?: Error\n} {\n const nodeEnv = process.env.NODE_ENV ?? 'development'\n\n logger.debug('Loading workspace environment variables', {\n workspacePath,\n detectedNodeEnv: nodeEnv,\n processNodeEnv: process.env.NODE_ENV ?? 'not set'\n })\n\n return loadEnvIntoProcess({\n path: workspacePath,\n nodeEnv: nodeEnv,\n defaultNodeEnv: 'development'\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/process/ProcessManager.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
import { setTimeout } from "timers/promises";
|
|
6
|
+
var ProcessManager = class {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.platform = this.detectPlatform();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detect current platform
|
|
12
|
+
*/
|
|
13
|
+
detectPlatform() {
|
|
14
|
+
switch (process.platform) {
|
|
15
|
+
case "darwin":
|
|
16
|
+
return "darwin";
|
|
17
|
+
case "linux":
|
|
18
|
+
return "linux";
|
|
19
|
+
case "win32":
|
|
20
|
+
return "win32";
|
|
21
|
+
default:
|
|
22
|
+
return "unsupported";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Detect if a dev server is running on the specified port
|
|
27
|
+
* Ports logic from merge-and-clean.sh lines 1107-1123
|
|
28
|
+
*/
|
|
29
|
+
async detectDevServer(port) {
|
|
30
|
+
if (this.platform === "unsupported") {
|
|
31
|
+
throw new Error("Process detection not supported on this platform");
|
|
32
|
+
}
|
|
33
|
+
if (this.platform === "win32") {
|
|
34
|
+
return await this.detectOnPortWindows(port);
|
|
35
|
+
} else {
|
|
36
|
+
return await this.detectOnPortUnix(port);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Unix/macOS implementation using lsof
|
|
41
|
+
* Ports bash lines 1107-1123
|
|
42
|
+
*/
|
|
43
|
+
async detectOnPortUnix(port) {
|
|
44
|
+
try {
|
|
45
|
+
const result = await execa("lsof", ["-i", `:${port}`, "-P"], {
|
|
46
|
+
reject: false
|
|
47
|
+
});
|
|
48
|
+
const lines = result.stdout.split("\n").filter((line) => line.includes("LISTEN"));
|
|
49
|
+
if (lines.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const firstLine = lines[0];
|
|
53
|
+
if (!firstLine) return null;
|
|
54
|
+
const parts = firstLine.split(/\s+/);
|
|
55
|
+
if (parts.length < 2) return null;
|
|
56
|
+
const processName = parts[0] ?? "";
|
|
57
|
+
const pid = parseInt(parts[1] ?? "", 10);
|
|
58
|
+
if (isNaN(pid)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const psResult = await execa("ps", ["-p", pid.toString(), "-o", "command="], {
|
|
62
|
+
reject: false
|
|
63
|
+
});
|
|
64
|
+
const fullCommand = psResult.stdout.trim();
|
|
65
|
+
const isDevServer = this.isDevServerProcess(processName, fullCommand);
|
|
66
|
+
return {
|
|
67
|
+
pid,
|
|
68
|
+
name: processName,
|
|
69
|
+
command: fullCommand,
|
|
70
|
+
port,
|
|
71
|
+
isDevServer
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Windows implementation using netstat and tasklist
|
|
79
|
+
*/
|
|
80
|
+
async detectOnPortWindows(port) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await execa("netstat", ["-ano"], { reject: false });
|
|
83
|
+
const lines = result.stdout.split("\n");
|
|
84
|
+
const portLine = lines.find(
|
|
85
|
+
(line) => line.includes(`:${port}`) && line.includes("LISTENING")
|
|
86
|
+
);
|
|
87
|
+
if (!portLine) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const parts = portLine.trim().split(/\s+/);
|
|
91
|
+
const lastPart = parts[parts.length - 1];
|
|
92
|
+
if (!lastPart) return null;
|
|
93
|
+
const pid = parseInt(lastPart, 10);
|
|
94
|
+
if (isNaN(pid)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const taskResult = await execa(
|
|
98
|
+
"tasklist",
|
|
99
|
+
["/FI", `PID eq ${pid}`, "/FO", "CSV"],
|
|
100
|
+
{
|
|
101
|
+
reject: false
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
const lines2 = taskResult.stdout.split("\n");
|
|
105
|
+
if (lines2.length < 2) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const secondLine = lines2[1];
|
|
109
|
+
if (!secondLine) return null;
|
|
110
|
+
const parts2 = secondLine.split(",");
|
|
111
|
+
const processName = (parts2[0] ?? "").replace(/"/g, "");
|
|
112
|
+
const fullCommand = processName;
|
|
113
|
+
const isDevServer = this.isDevServerProcess(processName, fullCommand);
|
|
114
|
+
return {
|
|
115
|
+
pid,
|
|
116
|
+
name: processName,
|
|
117
|
+
command: fullCommand,
|
|
118
|
+
port,
|
|
119
|
+
isDevServer
|
|
120
|
+
};
|
|
121
|
+
} catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Validate if process is a dev server
|
|
127
|
+
* Ports logic from merge-and-clean.sh lines 1121-1123
|
|
128
|
+
*/
|
|
129
|
+
isDevServerProcess(processName, command) {
|
|
130
|
+
const devServerNames = /^(node|npm|pnpm|yarn|next|next-server|vite|webpack|dev-server)$/i;
|
|
131
|
+
if (devServerNames.test(processName)) {
|
|
132
|
+
const devServerCommands2 = /(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i;
|
|
133
|
+
return devServerCommands2.test(command);
|
|
134
|
+
}
|
|
135
|
+
const devServerCommands = /(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i;
|
|
136
|
+
return devServerCommands.test(command);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Terminate a process by PID
|
|
140
|
+
* Ports logic from merge-and-clean.sh lines 1126-1139
|
|
141
|
+
*/
|
|
142
|
+
async terminateProcess(pid) {
|
|
143
|
+
try {
|
|
144
|
+
if (this.platform === "win32") {
|
|
145
|
+
await execa("taskkill", ["/PID", pid.toString(), "/F"], { reject: true });
|
|
146
|
+
} else {
|
|
147
|
+
process.kill(pid, "SIGKILL");
|
|
148
|
+
}
|
|
149
|
+
await setTimeout(1e3);
|
|
150
|
+
return true;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`Failed to terminate process ${pid}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Verify that a port is free
|
|
159
|
+
* Ports verification logic from merge-and-clean.sh lines 1135-1139
|
|
160
|
+
*/
|
|
161
|
+
async verifyPortFree(port) {
|
|
162
|
+
const processInfo = await this.detectDevServer(port);
|
|
163
|
+
return processInfo === null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Calculate dev server port from issue/PR number
|
|
167
|
+
* Ports logic from merge-and-clean.sh lines 1093-1098
|
|
168
|
+
*/
|
|
169
|
+
calculatePort(number) {
|
|
170
|
+
return 3e3 + number;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
ProcessManager
|
|
176
|
+
};
|
|
177
|
+
//# sourceMappingURL=chunk-SPYPLHMK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/process/ProcessManager.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { setTimeout } from 'timers/promises'\nimport type { ProcessInfo, Platform } from '../../types/process.js'\n\n/**\n * Manages process detection and termination across platforms\n * Ports dev server termination logic from bash/merge-and-clean.sh lines 1092-1148\n */\nexport class ProcessManager {\n\tprivate readonly platform: Platform\n\n\tconstructor() {\n\t\tthis.platform = this.detectPlatform()\n\t}\n\n\t/**\n\t * Detect current platform\n\t */\n\tprivate detectPlatform(): Platform {\n\t\tswitch (process.platform) {\n\t\t\tcase 'darwin':\n\t\t\t\treturn 'darwin'\n\t\t\tcase 'linux':\n\t\t\t\treturn 'linux'\n\t\t\tcase 'win32':\n\t\t\t\treturn 'win32'\n\t\t\tdefault:\n\t\t\t\treturn 'unsupported'\n\t\t}\n\t}\n\n\t/**\n\t * Detect if a dev server is running on the specified port\n\t * Ports logic from merge-and-clean.sh lines 1107-1123\n\t */\n\tasync detectDevServer(port: number): Promise<ProcessInfo | null> {\n\t\tif (this.platform === 'unsupported') {\n\t\t\tthrow new Error('Process detection not supported on this platform')\n\t\t}\n\n\t\t// Use platform-specific detection\n\t\tif (this.platform === 'win32') {\n\t\t\treturn await this.detectOnPortWindows(port)\n\t\t} else {\n\t\t\treturn await this.detectOnPortUnix(port)\n\t\t}\n\t}\n\n\t/**\n\t * Unix/macOS implementation using lsof\n\t * Ports bash lines 1107-1123\n\t */\n\tprivate async detectOnPortUnix(port: number): Promise<ProcessInfo | null> {\n\t\ttry {\n\t\t\t// Run lsof to find process listening on port (LISTEN only)\n\t\t\tconst result = await execa('lsof', ['-i', `:${port}`, '-P'], {\n\t\t\t\treject: false,\n\t\t\t})\n\n\t\t\t// Filter for LISTEN state only\n\t\t\tconst lines = result.stdout.split('\\n').filter(line => line.includes('LISTEN'))\n\n\t\t\tif (lines.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Parse first LISTEN line\n\t\t\tconst firstLine = lines[0]\n\t\t\tif (!firstLine) return null\n\n\t\t\tconst parts = firstLine.split(/\\s+/)\n\t\t\tif (parts.length < 2) return null\n\n\t\t\tconst processName = parts[0] ?? ''\n\t\t\tconst pid = parseInt(parts[1] ?? '', 10)\n\n\t\t\tif (isNaN(pid)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Get full command line using ps\n\t\t\tconst psResult = await execa('ps', ['-p', pid.toString(), '-o', 'command='], {\n\t\t\t\treject: false,\n\t\t\t})\n\t\t\tconst fullCommand = psResult.stdout.trim()\n\n\t\t\t// Validate if this is a dev server\n\t\t\tconst isDevServer = this.isDevServerProcess(processName, fullCommand)\n\n\t\t\treturn {\n\t\t\t\tpid,\n\t\t\t\tname: processName,\n\t\t\t\tcommand: fullCommand,\n\t\t\t\tport,\n\t\t\t\tisDevServer,\n\t\t\t}\n\t\t} catch {\n\t\t\t// If lsof fails, assume no process on port\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Windows implementation using netstat and tasklist\n\t */\n\tprivate async detectOnPortWindows(port: number): Promise<ProcessInfo | null> {\n\t\ttry {\n\t\t\t// Use netstat to find PID listening on port\n\t\t\tconst result = await execa('netstat', ['-ano'], { reject: false })\n\t\t\tconst lines = result.stdout.split('\\n')\n\n\t\t\t// Find line with our port and LISTENING state\n\t\t\tconst portLine = lines.find(\n\t\t\t\tline => line.includes(`:${port}`) && line.includes('LISTENING')\n\t\t\t)\n\n\t\t\tif (!portLine) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Extract PID (last column)\n\t\t\tconst parts = portLine.trim().split(/\\s+/)\n\t\t\tconst lastPart = parts[parts.length - 1]\n\t\t\tif (!lastPart) return null\n\n\t\t\tconst pid = parseInt(lastPart, 10)\n\n\t\t\tif (isNaN(pid)) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\t// Get process info using tasklist\n\t\t\tconst taskResult = await execa(\n\t\t\t\t'tasklist',\n\t\t\t\t['/FI', `PID eq ${pid}`, '/FO', 'CSV'],\n\t\t\t\t{\n\t\t\t\t\treject: false,\n\t\t\t\t}\n\t\t\t)\n\n\t\t\t// Parse CSV output\n\t\t\tconst lines2 = taskResult.stdout.split('\\n')\n\t\t\tif (lines2.length < 2) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst secondLine = lines2[1]\n\t\t\tif (!secondLine) return null\n\n\t\t\tconst parts2 = secondLine.split(',')\n\t\t\tconst processName = (parts2[0] ?? '').replace(/\"/g, '')\n\n\t\t\t// TODO: Get full command line on Windows (more complex)\n\t\t\tconst fullCommand = processName\n\n\t\t\tconst isDevServer = this.isDevServerProcess(processName, fullCommand)\n\n\t\t\treturn {\n\t\t\t\tpid,\n\t\t\t\tname: processName,\n\t\t\t\tcommand: fullCommand,\n\t\t\t\tport,\n\t\t\t\tisDevServer,\n\t\t\t}\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\n\t/**\n\t * Validate if process is a dev server\n\t * Ports logic from merge-and-clean.sh lines 1121-1123\n\t */\n\tprivate isDevServerProcess(processName: string, command: string): boolean {\n\t\t// Check process name patterns\n\t\tconst devServerNames = /^(node|npm|pnpm|yarn|next|next-server|vite|webpack|dev-server)$/i\n\t\tif (devServerNames.test(processName)) {\n\t\t\t// Additional validation via command line\n\t\t\tconst devServerCommands =\n\t\t\t\t/(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i\n\t\t\treturn devServerCommands.test(command)\n\t\t}\n\n\t\t// Check command line alone\n\t\tconst devServerCommands =\n\t\t\t/(next dev|next-server|npm.*dev|pnpm.*dev|yarn.*dev|vite|webpack.*serve|turbo.*dev|dev.*server)/i\n\t\treturn devServerCommands.test(command)\n\t}\n\n\t/**\n\t * Terminate a process by PID\n\t * Ports logic from merge-and-clean.sh lines 1126-1139\n\t */\n\tasync terminateProcess(pid: number): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.platform === 'win32') {\n\t\t\t\t// Windows: use taskkill\n\t\t\t\tawait execa('taskkill', ['/PID', pid.toString(), '/F'], { reject: true })\n\t\t\t} else {\n\t\t\t\t// Unix/macOS: use kill -9\n\t\t\t\tprocess.kill(pid, 'SIGKILL')\n\t\t\t}\n\n\t\t\t// Wait briefly for process to die\n\t\t\tawait setTimeout(1000)\n\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to terminate process ${pid}: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Verify that a port is free\n\t * Ports verification logic from merge-and-clean.sh lines 1135-1139\n\t */\n\tasync verifyPortFree(port: number): Promise<boolean> {\n\t\tconst processInfo = await this.detectDevServer(port)\n\t\treturn processInfo === null\n\t}\n\n\t/**\n\t * Calculate dev server port from issue/PR number\n\t * Ports logic from merge-and-clean.sh lines 1093-1098\n\t */\n\tcalculatePort(number: number): number {\n\t\treturn 3000 + number\n\t}\n}\n"],"mappings":"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","names":["devServerCommands"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
logger
|
|
4
|
+
} from "./chunk-GEHQXLEI.js";
|
|
5
|
+
|
|
6
|
+
// src/utils/installation-detector.ts
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
9
|
+
function detectInstallationMethod(scriptPath) {
|
|
10
|
+
logger.debug(`[installation-detector] Detecting installation method for: ${scriptPath}`);
|
|
11
|
+
if (process.env.OVERRIDE_INSTALLATION_METHOD) {
|
|
12
|
+
const overrideMethod = process.env.OVERRIDE_INSTALLATION_METHOD;
|
|
13
|
+
logger.info(`[installation-detector] Override detected, returning: ${overrideMethod}`);
|
|
14
|
+
return overrideMethod;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
try {
|
|
18
|
+
const stats = lstatSync(scriptPath);
|
|
19
|
+
if (stats.isSymbolicLink()) {
|
|
20
|
+
logger.debug(`[installation-detector] Script is a symlink`);
|
|
21
|
+
const realPath = realpathSync(scriptPath);
|
|
22
|
+
logger.debug(`[installation-detector] Symlink resolves to: ${realPath}`);
|
|
23
|
+
if (!realPath.includes("/node_modules/")) {
|
|
24
|
+
logger.debug(`[installation-detector] Symlink points outside node_modules, classification: linked`);
|
|
25
|
+
return "linked";
|
|
26
|
+
}
|
|
27
|
+
logger.debug(`[installation-detector] Symlink points to node_modules, treating as potential global install`);
|
|
28
|
+
scriptPath = realPath;
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
logger.debug(`[installation-detector] Unable to stat script file, continuing to other checks`);
|
|
32
|
+
}
|
|
33
|
+
if (scriptPath.includes("/dist/") || scriptPath.includes("\\dist\\")) {
|
|
34
|
+
logger.debug(`[installation-detector] Script is in dist/ directory, checking for local development setup`);
|
|
35
|
+
const distDir = dirname(scriptPath);
|
|
36
|
+
const projectRoot = dirname(distDir);
|
|
37
|
+
const srcDir = join(projectRoot, "src");
|
|
38
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
39
|
+
logger.debug(`[installation-detector] Looking for src/ at: ${srcDir}`);
|
|
40
|
+
logger.debug(`[installation-detector] Looking for package.json at: ${packageJsonPath}`);
|
|
41
|
+
if (existsSync(srcDir) && existsSync(packageJsonPath)) {
|
|
42
|
+
logger.debug(`[installation-detector] Found src/ and package.json, classification: local`);
|
|
43
|
+
return "local";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const globalPatterns = [
|
|
47
|
+
"/lib/node_modules/",
|
|
48
|
+
"/.nvm/versions/node/",
|
|
49
|
+
"/AppData/Roaming/npm/node_modules/",
|
|
50
|
+
"/.local/lib/node_modules/"
|
|
51
|
+
];
|
|
52
|
+
const normalizedPath = scriptPath.replace(/\\/g, "/");
|
|
53
|
+
logger.debug(`[installation-detector] Checking global patterns against: ${normalizedPath}`);
|
|
54
|
+
for (const pattern of globalPatterns) {
|
|
55
|
+
if (normalizedPath.includes(pattern)) {
|
|
56
|
+
logger.debug(`[installation-detector] Matched global pattern '${pattern}', classification: global`);
|
|
57
|
+
return "global";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
logger.debug(`[installation-detector] No patterns matched, classification: unknown`);
|
|
61
|
+
return "unknown";
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.debug(`[installation-detector] Error during detection: ${error}, classification: unknown`);
|
|
64
|
+
return "unknown";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function shouldShowUpdateNotification(method) {
|
|
68
|
+
return method === "global";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
detectInstallationMethod,
|
|
73
|
+
shouldShowUpdateNotification
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-SSCQCCJ7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/installation-detector.ts"],"sourcesContent":["import { dirname, join } from 'path'\nimport { existsSync, lstatSync, realpathSync } from 'fs'\nimport { logger } from './logger.js'\n\nexport type InstallationMethod = 'global' | 'local' | 'linked' | 'unknown'\n\n/**\n * Detect how iloom-cli is installed\n * - global: npm install -g (in global node_modules)\n * - local: Running from source directory (has src/ sibling to dist/)\n * - linked: npm link (symlinked executable)\n * - unknown: Cannot determine\n */\nexport function detectInstallationMethod(scriptPath: string): InstallationMethod {\n logger.debug(`[installation-detector] Detecting installation method for: ${scriptPath}`)\n\n if (process.env.OVERRIDE_INSTALLATION_METHOD) {\n const overrideMethod = process.env.OVERRIDE_INSTALLATION_METHOD as InstallationMethod\n logger.info(`[installation-detector] Override detected, returning: ${overrideMethod}`)\n return overrideMethod\n }\n\n try {\n // Check if the script is a symlink (npm link creates symlinks)\n try {\n const stats = lstatSync(scriptPath)\n if (stats.isSymbolicLink()) {\n logger.debug(`[installation-detector] Script is a symlink`)\n // Resolve symlink to check where it actually points\n const realPath = realpathSync(scriptPath)\n logger.debug(`[installation-detector] Symlink resolves to: ${realPath}`)\n // If the real path is in node_modules, it's a global install\n // Only return 'linked' if it points outside node_modules\n if (!realPath.includes('/node_modules/')) {\n logger.debug(`[installation-detector] Symlink points outside node_modules, classification: linked`)\n return 'linked'\n }\n logger.debug(`[installation-detector] Symlink points to node_modules, treating as potential global install`)\n // Otherwise, continue checking with the resolved path\n scriptPath = realPath\n }\n } catch {\n // If we can't stat it, continue to other checks\n logger.debug(`[installation-detector] Unable to stat script file, continuing to other checks`)\n }\n\n // Check if running from source directory\n // If the file is at dist/cli.js, check if src/ exists as a sibling\n if (scriptPath.includes('/dist/') || scriptPath.includes('\\\\dist\\\\')) {\n logger.debug(`[installation-detector] Script is in dist/ directory, checking for local development setup`)\n const distDir = dirname(scriptPath) // dist/\n const projectRoot = dirname(distDir) // project root\n const srcDir = join(projectRoot, 'src')\n const packageJsonPath = join(projectRoot, 'package.json')\n logger.debug(`[installation-detector] Looking for src/ at: ${srcDir}`)\n logger.debug(`[installation-detector] Looking for package.json at: ${packageJsonPath}`)\n\n // If src/ and package.json exist in parent, we're running from source\n if (existsSync(srcDir) && existsSync(packageJsonPath)) {\n logger.debug(`[installation-detector] Found src/ and package.json, classification: local`)\n return 'local'\n }\n }\n\n // Check if in global node_modules\n // Global installs are typically in:\n // - /usr/local/lib/node_modules/ (macOS/Linux)\n // - ~/.nvm/versions/node/*/lib/node_modules/ (NVM)\n // - C:\\Users\\*\\AppData\\Roaming\\npm\\node_modules\\ (Windows)\n // - /opt/homebrew/lib/node_modules (Homebrew on Apple Silicon)\n const globalPatterns = [\n '/lib/node_modules/',\n '/.nvm/versions/node/',\n '/AppData/Roaming/npm/node_modules/',\n '/.local/lib/node_modules/',\n ]\n\n const normalizedPath = scriptPath.replace(/\\\\/g, '/')\n logger.debug(`[installation-detector] Checking global patterns against: ${normalizedPath}`)\n for (const pattern of globalPatterns) {\n if (normalizedPath.includes(pattern)) {\n logger.debug(`[installation-detector] Matched global pattern '${pattern}', classification: global`)\n return 'global'\n }\n }\n\n logger.debug(`[installation-detector] No patterns matched, classification: unknown`)\n return 'unknown'\n } catch (error) {\n logger.debug(`[installation-detector] Error during detection: ${error}, classification: unknown`)\n return 'unknown'\n }\n}\n\n/**\n * Determine if update notifications should be shown\n * Returns true only for global installations\n */\nexport function shouldShowUpdateNotification(method: InstallationMethod): boolean {\n return method === 'global'\n}\n"],"mappings":";;;;;;AAAA,SAAS,SAAS,YAAY;AAC9B,SAAS,YAAY,WAAW,oBAAoB;AAY7C,SAAS,yBAAyB,YAAwC;AAC/E,SAAO,MAAM,8DAA8D,UAAU,EAAE;AAEvF,MAAI,QAAQ,IAAI,8BAA8B;AAC5C,UAAM,iBAAiB,QAAQ,IAAI;AACnC,WAAO,KAAK,yDAAyD,cAAc,EAAE;AACrF,WAAO;AAAA,EACT;AAEA,MAAI;AAEF,QAAI;AACF,YAAM,QAAQ,UAAU,UAAU;AAClC,UAAI,MAAM,eAAe,GAAG;AAC1B,eAAO,MAAM,6CAA6C;AAE1D,cAAM,WAAW,aAAa,UAAU;AACxC,eAAO,MAAM,gDAAgD,QAAQ,EAAE;AAGvE,YAAI,CAAC,SAAS,SAAS,gBAAgB,GAAG;AACxC,iBAAO,MAAM,qFAAqF;AAClG,iBAAO;AAAA,QACT;AACA,eAAO,MAAM,8FAA8F;AAE3G,qBAAa;AAAA,MACf;AAAA,IACF,QAAQ;AAEN,aAAO,MAAM,gFAAgF;AAAA,IAC/F;AAIA,QAAI,WAAW,SAAS,QAAQ,KAAK,WAAW,SAAS,UAAU,GAAG;AACpE,aAAO,MAAM,4FAA4F;AACzG,YAAM,UAAU,QAAQ,UAAU;AAClC,YAAM,cAAc,QAAQ,OAAO;AACnC,YAAM,SAAS,KAAK,aAAa,KAAK;AACtC,YAAM,kBAAkB,KAAK,aAAa,cAAc;AACxD,aAAO,MAAM,gDAAgD,MAAM,EAAE;AACrE,aAAO,MAAM,wDAAwD,eAAe,EAAE;AAGtF,UAAI,WAAW,MAAM,KAAK,WAAW,eAAe,GAAG;AACrD,eAAO,MAAM,4EAA4E;AACzF,eAAO;AAAA,MACT;AAAA,IACF;AAQA,UAAM,iBAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,iBAAiB,WAAW,QAAQ,OAAO,GAAG;AACpD,WAAO,MAAM,6DAA6D,cAAc,EAAE;AAC1F,eAAW,WAAW,gBAAgB;AACpC,UAAI,eAAe,SAAS,OAAO,GAAG;AACpC,eAAO,MAAM,mDAAmD,OAAO,2BAA2B;AAClG,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO,MAAM,sEAAsE;AACnF,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,MAAM,mDAAmD,KAAK,2BAA2B;AAChG,WAAO;AAAA,EACT;AACF;AAMO,SAAS,6BAA6B,QAAqC;AAChF,SAAO,WAAW;AACpB;","names":[]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getRepoInfo
|
|
4
|
+
} from "./chunk-KQDEK2ZW.js";
|
|
5
|
+
import {
|
|
6
|
+
logger
|
|
7
|
+
} from "./chunk-GEHQXLEI.js";
|
|
8
|
+
|
|
9
|
+
// src/utils/mcp.ts
|
|
10
|
+
import path from "path";
|
|
11
|
+
async function generateGitHubCommentMcpConfig(contextType) {
|
|
12
|
+
const repoInfo = await getRepoInfo();
|
|
13
|
+
const githubEventName = contextType === "issue" ? "issues" : contextType === "pr" ? "pull_request" : void 0;
|
|
14
|
+
const mcpServerConfig = {
|
|
15
|
+
mcpServers: {
|
|
16
|
+
github_comment: {
|
|
17
|
+
transport: "stdio",
|
|
18
|
+
command: "node",
|
|
19
|
+
args: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), "../dist/mcp/github-comment-server.js")],
|
|
20
|
+
env: {
|
|
21
|
+
REPO_OWNER: repoInfo.owner,
|
|
22
|
+
REPO_NAME: repoInfo.name,
|
|
23
|
+
GITHUB_API_URL: "https://api.github.com/",
|
|
24
|
+
...githubEventName && { GITHUB_EVENT_NAME: githubEventName }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
logger.debug("Generated MCP config for GitHub comment broker", {
|
|
30
|
+
repoOwner: repoInfo.owner,
|
|
31
|
+
repoName: repoInfo.name,
|
|
32
|
+
contextType: contextType ?? "auto-detect",
|
|
33
|
+
githubEventName: githubEventName ?? "auto-detect"
|
|
34
|
+
});
|
|
35
|
+
return [mcpServerConfig];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
generateGitHubCommentMcpConfig
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=chunk-SSR5AVRJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/mcp.ts"],"sourcesContent":["import path from 'path'\nimport { getRepoInfo } from './github.js'\nimport { logger } from './logger.js'\n\n/**\n * Generate MCP configuration for GitHub comment broker\n * Uses a single server that can handle both issues and pull requests\n * Returns array of MCP server config objects\n */\nexport async function generateGitHubCommentMcpConfig(contextType?: 'issue' | 'pr'): Promise<Record<string, unknown>[]> {\n\t// Get repository information\n\tconst repoInfo = await getRepoInfo()\n\n\t// Map logical types to GitHub's webhook event names (handle GitHub's naming quirk here)\n\tconst githubEventName = contextType === 'issue' ? 'issues' : contextType === 'pr' ? 'pull_request' : undefined\n\n\t// Generate single MCP server config\n\tconst mcpServerConfig = {\n\t\tmcpServers: {\n\t\t\tgithub_comment: {\n\t\t\t\ttransport: 'stdio',\n\t\t\t\tcommand: 'node',\n\t\t\t\targs: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), '../dist/mcp/github-comment-server.js')],\n\t\t\t\tenv: {\n\t\t\t\t\tREPO_OWNER: repoInfo.owner,\n\t\t\t\t\tREPO_NAME: repoInfo.name,\n\t\t\t\t\tGITHUB_API_URL: 'https://api.github.com/',\n\t\t\t\t\t...(githubEventName && { GITHUB_EVENT_NAME: githubEventName }),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlogger.debug('Generated MCP config for GitHub comment broker', {\n\t\trepoOwner: repoInfo.owner,\n\t\trepoName: repoInfo.name,\n\t\tcontextType: contextType ?? 'auto-detect',\n\t\tgithubEventName: githubEventName ?? 'auto-detect'\n\t})\n\n\treturn [mcpServerConfig]\n}"],"mappings":";;;;;;;;;AAAA,OAAO,UAAU;AASjB,eAAsB,+BAA+B,aAAkE;AAEtH,QAAM,WAAW,MAAM,YAAY;AAGnC,QAAM,kBAAkB,gBAAgB,UAAU,WAAW,gBAAgB,OAAO,iBAAiB;AAGrG,QAAM,kBAAkB;AAAA,IACvB,YAAY;AAAA,MACX,gBAAgB;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,MAAM,CAAC,KAAK,KAAK,KAAK,QAAQ,IAAI,WAAW,IAAI,YAAY,GAAG,EAAE,QAAQ,GAAG,sCAAsC,CAAC;AAAA,QACpH,KAAK;AAAA,UACJ,YAAY,SAAS;AAAA,UACrB,WAAW,SAAS;AAAA,UACpB,gBAAgB;AAAA,UAChB,GAAI,mBAAmB,EAAE,mBAAmB,gBAAgB;AAAA,QAC7D;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO,MAAM,kDAAkD;AAAA,IAC9D,WAAW,SAAS;AAAA,IACpB,UAAU,SAAS;AAAA,IACnB,aAAa,eAAe;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACrC,CAAC;AAED,SAAO,CAAC,eAAe;AACxB;","names":[]}
|