@iloom/cli 0.1.18 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -6
- package/dist/ClaudeContextManager-LVCYRM6Q.js +13 -0
- package/dist/ClaudeService-WVTWB3DK.js +12 -0
- package/dist/{GitHubService-SH4H6VS5.js → GitHubService-7E2S5NNZ.js} +3 -3
- package/dist/{LoomLauncher-FB2MV2ZI.js → LoomLauncher-CTSWJL35.js} +7 -7
- package/dist/{PromptTemplateManager-WM5GIPEF.js → PromptTemplateManager-WII75TKH.js} +2 -2
- package/dist/README.md +771 -0
- package/dist/{SettingsManager-SKLUVE3K.js → SettingsManager-XOYCLH3D.js} +2 -2
- package/dist/{add-issue-L5HX6LEV.js → add-issue-OBI325W7.js} +7 -7
- package/dist/{chunk-SLIMABFA.js → chunk-2PLUQT6J.js} +2 -2
- package/dist/{chunk-VVH3ANF2.js → chunk-4IV6W4U5.js} +4 -4
- package/dist/{chunk-PV3GAXQO.js → chunk-6LEQW46Y.js} +2 -2
- package/dist/{chunk-6OTVPRXH.js → chunk-CVLAZRNB.js} +2 -2
- package/dist/{chunk-FXV24OYZ.js → chunk-DJUGYNQE.js} +9 -2
- package/dist/{chunk-FXV24OYZ.js.map → chunk-DJUGYNQE.js.map} +1 -1
- package/dist/{chunk-KOCQAD2E.js → chunk-HBVFXN7R.js} +3 -3
- package/dist/{chunk-HURVAQRK.js → chunk-LHP6ROUM.js} +4 -4
- package/dist/{chunk-PXZBAC2M.js → chunk-MFU53H6J.js} +2 -2
- package/dist/{chunk-L4QGC27H.js → chunk-PVAVNJKS.js} +13 -1
- package/dist/chunk-PVAVNJKS.js.map +1 -0
- package/dist/{chunk-PR7FKQBG.js → chunk-RF2YI2XJ.js} +2 -2
- package/dist/{chunk-DGEKUT7Q.js → chunk-SWCRXDZC.js} +34 -6
- package/dist/chunk-SWCRXDZC.js.map +1 -0
- package/dist/{chunk-Q2KYPAH2.js → chunk-SYOSCMIT.js} +6 -6
- package/dist/{chunk-VYQLLHZ7.js → chunk-T3KEIB4D.js} +6 -2
- package/dist/{chunk-VYQLLHZ7.js.map → chunk-T3KEIB4D.js.map} +1 -1
- package/dist/{chunk-IIPTBZQW.js → chunk-TS6DL67T.js} +2 -2
- package/dist/{chunk-IO4WFTL2.js → chunk-VETG35MF.js} +2 -2
- package/dist/{chunk-RSRO7564.js → chunk-ZE74H5BR.js} +28 -3
- package/dist/chunk-ZE74H5BR.js.map +1 -0
- package/dist/{claude-7LUVDZZ4.js → claude-ZIWDG4XG.js} +2 -2
- package/dist/{cleanup-ZHROIBSQ.js → cleanup-FEIVZSIV.js} +5 -5
- package/dist/cli.js +41 -31
- package/dist/cli.js.map +1 -1
- package/dist/contribute-EMZKCAC6.js +259 -0
- package/dist/contribute-EMZKCAC6.js.map +1 -0
- package/dist/{enhance-VVMAKMVZ.js → enhance-MNA4ZGXW.js} +7 -7
- package/dist/{feedback-AKHD7QIM.js → feedback-LFNMQBAZ.js} +6 -6
- package/dist/{finish-WGPISUEH.js → finish-TX5CJICB.js} +411 -17
- package/dist/finish-TX5CJICB.js.map +1 -0
- package/dist/{git-OUYMVYJX.js → git-WC6HZLOT.js} +2 -2
- package/dist/{ignite-JEN3K3OT.js → ignite-MQWVJEAB.js} +125 -10
- package/dist/ignite-MQWVJEAB.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +75 -7
- package/dist/index.js.map +1 -1
- package/dist/{init-EVUT4ZQJ.js → init-GJDYN2IK.js} +7 -7
- package/dist/mcp/{claude-YHHHLSXH.js → claude-NDFOCQQQ.js} +2 -2
- package/dist/mcp/github-comment-server.js.map +1 -1
- package/dist/mcp/{terminal-SDCMDVD7.js → terminal-OMNRFWB3.js} +28 -3
- package/dist/mcp/terminal-OMNRFWB3.js.map +1 -0
- package/dist/{open-ETZUFSE4.js → open-NXSN7XOC.js} +4 -4
- package/dist/prompts/init-prompt.txt +32 -1
- package/dist/prompts/issue-prompt.txt +141 -9
- package/dist/{rebase-KBWFDZCN.js → rebase-DUNFOJVS.js} +6 -6
- package/dist/{remote-GJEZWRCC.js → remote-ZCXJVVNW.js} +4 -2
- package/dist/{run-4SVQ3WEU.js → run-O7ZK7CKA.js} +4 -4
- package/dist/schema/settings.schema.json +18 -0
- package/dist/{start-2NEZU7SE.js → start-73I5W7WW.js} +16 -16
- package/dist/{terminal-3D6TUAKJ.js → terminal-BIRBZ4AZ.js} +2 -2
- package/dist/{test-git-MKZATGZN.js → test-git-T76HOTIA.js} +3 -3
- package/dist/{test-prefix-ZNLWDI3K.js → test-prefix-6HJUVQMH.js} +3 -3
- package/dist/{test-tabs-JRKY3QMM.js → test-tabs-RXDBZ6J7.js} +2 -2
- package/package.json +3 -2
- package/dist/ClaudeContextManager-LD3VB6EM.js +0 -13
- package/dist/ClaudeService-CFFI7DD5.js +0 -12
- package/dist/chunk-DGEKUT7Q.js.map +0 -1
- package/dist/chunk-L4QGC27H.js.map +0 -1
- package/dist/chunk-RSRO7564.js.map +0 -1
- package/dist/finish-WGPISUEH.js.map +0 -1
- package/dist/ignite-JEN3K3OT.js.map +0 -1
- package/dist/mcp/terminal-SDCMDVD7.js.map +0 -1
- /package/dist/{ClaudeContextManager-LD3VB6EM.js.map → ClaudeContextManager-LVCYRM6Q.js.map} +0 -0
- /package/dist/{ClaudeService-CFFI7DD5.js.map → ClaudeService-WVTWB3DK.js.map} +0 -0
- /package/dist/{GitHubService-SH4H6VS5.js.map → GitHubService-7E2S5NNZ.js.map} +0 -0
- /package/dist/{LoomLauncher-FB2MV2ZI.js.map → LoomLauncher-CTSWJL35.js.map} +0 -0
- /package/dist/{PromptTemplateManager-WM5GIPEF.js.map → PromptTemplateManager-WII75TKH.js.map} +0 -0
- /package/dist/{SettingsManager-SKLUVE3K.js.map → SettingsManager-XOYCLH3D.js.map} +0 -0
- /package/dist/{add-issue-L5HX6LEV.js.map → add-issue-OBI325W7.js.map} +0 -0
- /package/dist/{chunk-SLIMABFA.js.map → chunk-2PLUQT6J.js.map} +0 -0
- /package/dist/{chunk-VVH3ANF2.js.map → chunk-4IV6W4U5.js.map} +0 -0
- /package/dist/{chunk-PV3GAXQO.js.map → chunk-6LEQW46Y.js.map} +0 -0
- /package/dist/{chunk-6OTVPRXH.js.map → chunk-CVLAZRNB.js.map} +0 -0
- /package/dist/{chunk-KOCQAD2E.js.map → chunk-HBVFXN7R.js.map} +0 -0
- /package/dist/{chunk-HURVAQRK.js.map → chunk-LHP6ROUM.js.map} +0 -0
- /package/dist/{chunk-PXZBAC2M.js.map → chunk-MFU53H6J.js.map} +0 -0
- /package/dist/{chunk-PR7FKQBG.js.map → chunk-RF2YI2XJ.js.map} +0 -0
- /package/dist/{chunk-Q2KYPAH2.js.map → chunk-SYOSCMIT.js.map} +0 -0
- /package/dist/{chunk-IIPTBZQW.js.map → chunk-TS6DL67T.js.map} +0 -0
- /package/dist/{chunk-IO4WFTL2.js.map → chunk-VETG35MF.js.map} +0 -0
- /package/dist/{claude-7LUVDZZ4.js.map → claude-ZIWDG4XG.js.map} +0 -0
- /package/dist/{cleanup-ZHROIBSQ.js.map → cleanup-FEIVZSIV.js.map} +0 -0
- /package/dist/{enhance-VVMAKMVZ.js.map → enhance-MNA4ZGXW.js.map} +0 -0
- /package/dist/{feedback-AKHD7QIM.js.map → feedback-LFNMQBAZ.js.map} +0 -0
- /package/dist/{git-OUYMVYJX.js.map → git-WC6HZLOT.js.map} +0 -0
- /package/dist/{init-EVUT4ZQJ.js.map → init-GJDYN2IK.js.map} +0 -0
- /package/dist/mcp/{claude-YHHHLSXH.js.map → claude-NDFOCQQQ.js.map} +0 -0
- /package/dist/{open-ETZUFSE4.js.map → open-NXSN7XOC.js.map} +0 -0
- /package/dist/{rebase-KBWFDZCN.js.map → rebase-DUNFOJVS.js.map} +0 -0
- /package/dist/{remote-GJEZWRCC.js.map → remote-ZCXJVVNW.js.map} +0 -0
- /package/dist/{run-4SVQ3WEU.js.map → run-O7ZK7CKA.js.map} +0 -0
- /package/dist/{start-2NEZU7SE.js.map → start-73I5W7WW.js.map} +0 -0
- /package/dist/{terminal-3D6TUAKJ.js.map → terminal-BIRBZ4AZ.js.map} +0 -0
- /package/dist/{test-git-MKZATGZN.js.map → test-git-T76HOTIA.js.map} +0 -0
- /package/dist/{test-prefix-ZNLWDI3K.js.map → test-prefix-6HJUVQMH.js.map} +0 -0
- /package/dist/{test-tabs-JRKY3QMM.js.map → test-tabs-RXDBZ6J7.js.map} +0 -0
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import {
|
|
3
3
|
detectClaudeCli,
|
|
4
4
|
launchClaude
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MFU53H6J.js";
|
|
6
6
|
import {
|
|
7
7
|
PromptTemplateManager
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PVAVNJKS.js";
|
|
9
9
|
import {
|
|
10
10
|
ShellCompletion
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-6LEQW46Y.js";
|
|
12
|
+
import "./chunk-VETG35MF.js";
|
|
13
|
+
import "./chunk-HBVFXN7R.js";
|
|
14
14
|
import {
|
|
15
15
|
logger
|
|
16
16
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -258,7 +258,7 @@ var InitCommand = class {
|
|
|
258
258
|
logger.debug("Unknown shell detected, skipping config read");
|
|
259
259
|
}
|
|
260
260
|
logger.debug("Detecting git remotes for GitHub configuration");
|
|
261
|
-
const { parseGitRemotes } = await import("./remote-
|
|
261
|
+
const { parseGitRemotes } = await import("./remote-ZCXJVVNW.js");
|
|
262
262
|
const remotes = await parseGitRemotes();
|
|
263
263
|
logger.debug("Git remotes detected", { count: remotes.length, remotes });
|
|
264
264
|
let remotesInfo = "";
|
|
@@ -336,4 +336,4 @@ var InitCommand = class {
|
|
|
336
336
|
export {
|
|
337
337
|
InitCommand
|
|
338
338
|
};
|
|
339
|
-
//# sourceMappingURL=init-
|
|
339
|
+
//# sourceMappingURL=init-GJDYN2IK.js.map
|
|
@@ -161,7 +161,7 @@ async function launchClaudeInNewTerminalWindow(_prompt, options) {
|
|
|
161
161
|
if (!workspacePath) {
|
|
162
162
|
throw new Error("workspacePath is required for terminal window launch");
|
|
163
163
|
}
|
|
164
|
-
const { openTerminalWindow } = await import("./terminal-
|
|
164
|
+
const { openTerminalWindow } = await import("./terminal-OMNRFWB3.js");
|
|
165
165
|
const executable = executablePath ?? "iloom";
|
|
166
166
|
let launchCommand = `${executable} spin`;
|
|
167
167
|
if (oneShot !== "default") {
|
|
@@ -246,4 +246,4 @@ export {
|
|
|
246
246
|
launchClaude,
|
|
247
247
|
launchClaudeInNewTerminalWindow
|
|
248
248
|
};
|
|
249
|
-
//# sourceMappingURL=claude-
|
|
249
|
+
//# sourceMappingURL=claude-NDFOCQQQ.js.map
|
|
@@ -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\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":[]}
|
|
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 - handle both old and new formats\n\t\t// Old format: \"Logged in to github.com as username\"\n\t\t// New format: \"✓ Logged in to github.com account username (keyring)\"\n\n\t\t// Split output into lines to find the active account\n\t\tconst lines = output.split('\\n')\n\t\tlet username: string | undefined\n\t\tlet scopes: string[] = []\n\n\t\t// Find the active account (look for \"Active account: true\" or first account if none marked)\n\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\tconst line = lines[i]\n\n\t\t\t// Match new format: \"✓ Logged in to github.com account username\"\n\t\t\tconst newFormatMatch = line?.match(/Logged in to github\\.com account ([^\\s(]+)/)\n\t\t\tif (newFormatMatch) {\n\t\t\t\tconst accountName = newFormatMatch[1]\n\n\t\t\t\t// Check if this is the active account\n\t\t\t\tconst nextFewLines = lines.slice(i + 1, i + 5).join('\\n')\n\t\t\t\tconst isActive = nextFewLines.includes('Active account: true')\n\n\t\t\t\t// If this is the active account, or we haven't found one yet and there's no \"Active account\" marker\n\t\t\t\tif (isActive || (!username && !output.includes('Active account:'))) {\n\t\t\t\t\tusername = accountName\n\n\t\t\t\t\t// Find scopes for this account\n\t\t\t\t\tconst scopeMatch = nextFewLines.match(/Token scopes: (.+)/)\n\t\t\t\t\tif (scopeMatch?.[1]) {\n\t\t\t\t\t\tscopes = scopeMatch[1].split(', ').map(scope => scope.replace(/^'|'$/g, ''))\n\t\t\t\t\t}\n\n\t\t\t\t\t// If this is the active account, we're done\n\t\t\t\t\tif (isActive) break\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fallback: match old format\n\t\t\tif (!username) {\n\t\t\t\tconst oldFormatMatch = line?.match(/Logged in to github\\.com as ([^\\s]+)/)\n\t\t\t\tif (oldFormatMatch) {\n\t\t\t\t\tusername = oldFormatMatch[1]\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// If scopes not yet extracted, try the old \"Token scopes\" format\n\t\tif (scopes.length === 0) {\n\t\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\t\tscopes = scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? []\n\t\t}\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes,\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":[]}
|
|
@@ -22,10 +22,13 @@ async function openTerminalWindow(options) {
|
|
|
22
22
|
`Terminal window launching not yet supported on ${platform}. Currently only macOS is supported.`
|
|
23
23
|
);
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const hasITerm2 = await detectITerm2();
|
|
26
|
+
const applescript = hasITerm2 ? buildITerm2SingleTabScript(options) : buildAppleScript(options);
|
|
26
27
|
try {
|
|
27
28
|
await execa("osascript", ["-e", applescript]);
|
|
28
|
-
|
|
29
|
+
if (!hasITerm2) {
|
|
30
|
+
await execa("osascript", ["-e", 'tell application "Terminal" to activate']);
|
|
31
|
+
}
|
|
29
32
|
} catch (error) {
|
|
30
33
|
throw new Error(
|
|
31
34
|
`Failed to open terminal window: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
@@ -74,6 +77,28 @@ function escapePathForAppleScript(path) {
|
|
|
74
77
|
function escapeForAppleScript(command) {
|
|
75
78
|
return command.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
76
79
|
}
|
|
80
|
+
function buildITerm2SingleTabScript(options) {
|
|
81
|
+
const command = buildCommandSequence(options);
|
|
82
|
+
let script = 'tell application id "com.googlecode.iterm2"\n';
|
|
83
|
+
script += " create window with default profile\n";
|
|
84
|
+
script += " set s1 to current session of current window\n\n";
|
|
85
|
+
if (options.backgroundColor) {
|
|
86
|
+
const { r, g, b } = options.backgroundColor;
|
|
87
|
+
script += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
script += ` tell s1 to write text "${escapeForAppleScript(command)}"
|
|
91
|
+
|
|
92
|
+
`;
|
|
93
|
+
if (options.title) {
|
|
94
|
+
script += ` set name of s1 to "${escapeForAppleScript(options.title)}"
|
|
95
|
+
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
script += " activate\n";
|
|
99
|
+
script += "end tell";
|
|
100
|
+
return script;
|
|
101
|
+
}
|
|
77
102
|
function buildCommandSequence(options) {
|
|
78
103
|
const {
|
|
79
104
|
workspacePath,
|
|
@@ -199,4 +224,4 @@ export {
|
|
|
199
224
|
openMultipleTerminalWindows,
|
|
200
225
|
openTerminalWindow
|
|
201
226
|
};
|
|
202
|
-
//# sourceMappingURL=terminal-
|
|
227
|
+
//# sourceMappingURL=terminal-OMNRFWB3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/utils/terminal.ts"],"sourcesContent":["import { execa } from 'execa'\nimport { existsSync } from 'node:fs'\nimport type { Platform } from '../types/index.js'\n\nexport interface TerminalWindowOptions {\n\tworkspacePath?: string\n\tcommand?: string\n\tbackgroundColor?: { r: number; g: number; b: number }\n\tport?: number\n\tincludeEnvSetup?: boolean // source .env\n\tincludePortExport?: boolean // export PORT=<port>\n\ttitle?: string // Terminal tab title\n}\n\n/**\n * Detect current platform\n */\nexport function detectPlatform(): Platform {\n\tconst platform = process.platform\n\tif (platform === 'darwin') return 'darwin'\n\tif (platform === 'linux') return 'linux'\n\tif (platform === 'win32') return 'win32'\n\treturn 'unsupported'\n}\n\n/**\n * Detect if iTerm2 is installed on macOS\n * Returns false on non-macOS platforms\n */\nexport async function detectITerm2(): Promise<boolean> {\n\tconst platform = detectPlatform()\n\tif (platform !== 'darwin') return false\n\n\t// Check if iTerm.app exists at standard location\n\treturn existsSync('/Applications/iTerm.app')\n}\n\n/**\n * Open new terminal window with specified options\n * Currently supports macOS only\n */\nexport async function openTerminalWindow(\n\toptions: TerminalWindowOptions\n): Promise<void> {\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// Detect if iTerm2 is available\n\tconst hasITerm2 = await detectITerm2()\n\n\t// Build appropriate AppleScript based on terminal availability\n\tconst applescript = hasITerm2\n\t\t? buildITerm2SingleTabScript(options)\n\t\t: buildAppleScript(options)\n\n\ttry {\n\t\tawait execa('osascript', ['-e', applescript])\n\n\t\t// Activate the appropriate terminal application (only needed for Terminal.app)\n\t\t// iTerm2 script includes its own activation\n\t\tif (!hasITerm2) {\n\t\t\tawait execa('osascript', ['-e', 'tell application \"Terminal\" to activate'])\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to open terminal window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t)\n\t}\n}\n\n/**\n * Build AppleScript for macOS Terminal.app\n */\nfunction buildAppleScript(options: TerminalWindowOptions): string {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tbackgroundColor,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\t// Build command sequence\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source .env file\n\tif (includeEnvSetup) {\n\t\tcommands.push('source .env')\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\t// Most shells (bash/zsh) ignore commands starting with space when HISTCONTROL=ignorespace\n\tconst historyFreeCommand = ` ${fullCommand}`\n\n\t// Build AppleScript\n\tlet script = `tell application \"Terminal\"\\n`\n\tscript += ` set newTab to do script \"${escapeForAppleScript(historyFreeCommand)}\"\\n`\n\n\t// Apply background color if provided\n\tif (backgroundColor) {\n\t\tconst { r, g, b } = backgroundColor\n\t\t// Convert 8-bit RGB (0-255) to 16-bit RGB (0-65535)\n\t\tscript += ` set background color of newTab to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\tscript += `end tell`\n\n\treturn script\n}\n\n/**\n * Escape path for AppleScript string\n * Single quotes in path need special escaping\n */\nfunction escapePathForAppleScript(path: string): string {\n\t// Replace single quote with '\\''\n\treturn path.replace(/'/g, \"'\\\\''\")\n}\n\n/**\n * Escape command for AppleScript do script\n * Must handle double quotes and backslashes\n */\nfunction escapeForAppleScript(command: string): string {\n\treturn (\n\t\tcommand\n\t\t\t.replace(/\\\\/g, '\\\\\\\\') // Escape backslashes\n\t\t\t.replace(/\"/g, '\\\\\"') // Escape double quotes\n\t)\n}\n\n/**\n * Build iTerm2 AppleScript for single tab\n */\nfunction buildITerm2SingleTabScript(options: TerminalWindowOptions): string {\n\tconst command = buildCommandSequence(options)\n\n\tlet script = 'tell application id \"com.googlecode.iterm2\"\\n'\n\tscript += ' create window with default profile\\n'\n\tscript += ' set s1 to current session of current window\\n\\n'\n\n\t// Set background color\n\tif (options.backgroundColor) {\n\t\tconst { r, g, b } = options.backgroundColor\n\t\tscript += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\t// Execute command\n\tscript += ` tell s1 to write text \"${escapeForAppleScript(command)}\"\\n\\n`\n\n\t// Set session name (tab title)\n\tif (options.title) {\n\t\tscript += ` set name of s1 to \"${escapeForAppleScript(options.title)}\"\\n\\n`\n\t}\n\n\t// Activate iTerm2\n\tscript += ' activate\\n'\n\tscript += 'end tell'\n\n\treturn script\n}\n\n/**\n * Build command sequence for terminal\n */\nfunction buildCommandSequence(options: TerminalWindowOptions): string {\n\tconst {\n\t\tworkspacePath,\n\t\tcommand,\n\t\tport,\n\t\tincludeEnvSetup,\n\t\tincludePortExport,\n\t} = options\n\n\tconst commands: string[] = []\n\n\t// Navigate to workspace\n\tif (workspacePath) {\n\t\tcommands.push(`cd '${escapePathForAppleScript(workspacePath)}'`)\n\t}\n\n\t// Source .env file\n\tif (includeEnvSetup) {\n\t\tcommands.push('source .env')\n\t}\n\n\t// Export PORT variable\n\tif (includePortExport && port !== undefined) {\n\t\tcommands.push(`export PORT=${port}`)\n\t}\n\n\t// Add custom command\n\tif (command) {\n\t\tcommands.push(command)\n\t}\n\n\t// Join with &&\n\tconst fullCommand = commands.join(' && ')\n\n\t// Prefix with space to prevent shell history pollution\n\treturn ` ${fullCommand}`\n}\n\n/**\n * Build iTerm2 AppleScript for multiple tabs (2+) in single window\n */\nfunction buildITerm2MultiTabScript(\n\toptionsArray: TerminalWindowOptions[]\n): string {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('buildITerm2MultiTabScript requires at least 2 terminal options')\n\t}\n\n\tlet script = 'tell application id \"com.googlecode.iterm2\"\\n'\n\tscript += ' create window with default profile\\n'\n\tscript += ' set newWindow to current window\\n'\n\n\t// First tab\n\tconst options1 = optionsArray[0]\n\tif (!options1) {\n\t\tthrow new Error('First terminal option is undefined')\n\t}\n\tconst command1 = buildCommandSequence(options1)\n\n\tscript += ' set s1 to current session of newWindow\\n\\n'\n\n\t// Set background color for first tab\n\tif (options1.backgroundColor) {\n\t\tconst { r, g, b } = options1.backgroundColor\n\t\tscript += ` set background color of s1 to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t}\n\n\t// Execute command in first tab\n\tscript += ` tell s1 to write text \"${escapeForAppleScript(command1)}\"\\n\\n`\n\n\t// Set tab title for first tab\n\tif (options1.title) {\n\t\tscript += ` set name of s1 to \"${escapeForAppleScript(options1.title)}\"\\n\\n`\n\t}\n\n\t// Subsequent tabs (2, 3, ...)\n\tfor (let i = 1; i < optionsArray.length; i++) {\n\t\tconst options = optionsArray[i]\n\t\tif (!options) {\n\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t}\n\t\tconst command = buildCommandSequence(options)\n\t\tconst sessionVar = `s${i + 1}`\n\n\t\t// Create tab\n\t\tscript += ' tell newWindow\\n'\n\t\tscript += ` set newTab${i} to (create tab with default profile)\\n`\n\t\tscript += ' end tell\\n'\n\t\tscript += ` set ${sessionVar} to current session of newTab${i}\\n\\n`\n\n\t\t// Set background color\n\t\tif (options.backgroundColor) {\n\t\t\tconst { r, g, b } = options.backgroundColor\n\t\t\tscript += ` set background color of ${sessionVar} to {${Math.round(r * 257)}, ${Math.round(g * 257)}, ${Math.round(b * 257)}}\\n`\n\t\t}\n\n\t\t// Execute command\n\t\tscript += ` tell ${sessionVar} to write text \"${escapeForAppleScript(command)}\"\\n\\n`\n\n\t\t// Set tab title\n\t\tif (options.title) {\n\t\t\tscript += ` set name of ${sessionVar} to \"${escapeForAppleScript(options.title)}\"\\n\\n`\n\t\t}\n\t}\n\n\t// Activate iTerm2\n\tscript += ' activate\\n'\n\tscript += 'end tell'\n\n\treturn script\n}\n\n/**\n * Open multiple terminal windows/tabs (2+) with specified options\n * If iTerm2 is available on macOS, creates single window with multiple tabs\n * Otherwise falls back to multiple separate Terminal.app windows\n */\nexport async function openMultipleTerminalWindows(\n\toptionsArray: TerminalWindowOptions[]\n): Promise<void> {\n\tif (optionsArray.length < 2) {\n\t\tthrow new Error('openMultipleTerminalWindows requires at least 2 terminal options. Use openTerminalWindow for single terminal.')\n\t}\n\n\tconst platform = detectPlatform()\n\n\tif (platform !== 'darwin') {\n\t\tthrow new Error(\n\t\t\t`Terminal window launching not yet supported on ${platform}. ` +\n\t\t\t\t`Currently only macOS is supported.`\n\t\t)\n\t}\n\n\t// Detect if iTerm2 is available\n\tconst hasITerm2 = await detectITerm2()\n\n\tif (hasITerm2) {\n\t\t// Use iTerm2 with multiple tabs in single window\n\t\tconst applescript = buildITerm2MultiTabScript(optionsArray)\n\n\t\ttry {\n\t\t\tawait execa('osascript', ['-e', applescript])\n\t\t} catch (error) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to open iTerm2 window: ${error instanceof Error ? error.message : 'Unknown error'}`\n\t\t\t)\n\t\t}\n\t} else {\n\t\t// Fall back to multiple Terminal.app windows\n\t\tfor (let i = 0; i < optionsArray.length; i++) {\n\t\t\tconst options = optionsArray[i]\n\t\t\tif (!options) {\n\t\t\t\tthrow new Error(`Terminal option at index ${i} is undefined`)\n\t\t\t}\n\t\t\tawait openTerminalWindow(options)\n\n\t\t\t// Brief pause between terminals (except after last one)\n\t\t\tif (i < optionsArray.length - 1) {\n\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 1000))\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Open dual terminal windows/tabs with specified options\n * If iTerm2 is available on macOS, creates single window with two tabs\n * Otherwise falls back to two separate Terminal.app windows\n */\nexport async function openDualTerminalWindow(\n\toptions1: TerminalWindowOptions,\n\toptions2: TerminalWindowOptions\n): Promise<void> {\n\t// Delegate to openMultipleTerminalWindows for consistency\n\tawait openMultipleTerminalWindows([options1, options2])\n}\n"],"mappings":"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","names":[]}
|
|
@@ -25,11 +25,11 @@ import {
|
|
|
25
25
|
} from "./chunk-YETJNRQM.js";
|
|
26
26
|
import {
|
|
27
27
|
GitWorktreeManager
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-VETG35MF.js";
|
|
29
29
|
import {
|
|
30
30
|
SettingsManager
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-T3KEIB4D.js";
|
|
32
|
+
import "./chunk-HBVFXN7R.js";
|
|
33
33
|
import {
|
|
34
34
|
logger
|
|
35
35
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -273,4 +273,4 @@ Make sure the project is built (run 'il start' first)`
|
|
|
273
273
|
export {
|
|
274
274
|
OpenCommand
|
|
275
275
|
};
|
|
276
|
-
//# sourceMappingURL=open-
|
|
276
|
+
//# sourceMappingURL=open-NXSN7XOC.js.map
|
|
@@ -271,6 +271,24 @@ The following JSON Schema defines valid iloom settings:
|
|
|
271
271
|
},
|
|
272
272
|
"additionalProperties": false,
|
|
273
273
|
"description": "Issue management configuration"
|
|
274
|
+
},
|
|
275
|
+
"mergeBehavior": {
|
|
276
|
+
"type": "object",
|
|
277
|
+
"properties": {
|
|
278
|
+
"mode": {
|
|
279
|
+
"type": "string",
|
|
280
|
+
"enum": [
|
|
281
|
+
"local",
|
|
282
|
+
"github-pr"
|
|
283
|
+
],
|
|
284
|
+
"default": "local"
|
|
285
|
+
},
|
|
286
|
+
"remote": {
|
|
287
|
+
"type": "string"
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
"additionalProperties": false,
|
|
291
|
+
"description": "Merge behavior configuration: local (merge locally) or github-pr (create PR)"
|
|
274
292
|
}
|
|
275
293
|
},
|
|
276
294
|
"additionalProperties": false
|
|
@@ -364,6 +382,7 @@ Extract these current values if they exist:
|
|
|
364
382
|
- `currentWorktreePrefix` from `worktreePrefix` field (default: "" which means use default)
|
|
365
383
|
- `currentPermissionMode` from `workflows.issue.permissionMode` field (default: "acceptEdits")
|
|
366
384
|
- `currentBasePort` from `capabilities.web.basePort` field (default: 3000)
|
|
385
|
+
- `currentMergeMode` from `mergeBehavior.mode` field (default: "local")
|
|
367
386
|
|
|
368
387
|
**If configuration already exists, display current configuration summary:**
|
|
369
388
|
|
|
@@ -474,6 +493,16 @@ Use AskUserQuestion to ask ALL essential settings questions **IN A SINGLE BATCH*
|
|
|
474
493
|
- Validation: Number between 1 and 65535
|
|
475
494
|
- Store answer as: `capabilities.web.basePort`
|
|
476
495
|
|
|
496
|
+
5. **Merge Mode** (only ask if multiple remotes detected OR user requests advanced config)
|
|
497
|
+
- Question format: "How should iloom handle finishing work?{{#IF currentMergeMode}} (Currently: [currentMergeMode]){{/IF currentMergeMode}}"
|
|
498
|
+
- Options:
|
|
499
|
+
- "local" - Merge changes locally (traditional workflow)
|
|
500
|
+
- "github-pr" - Create GitHub PR instead of merging locally (for PR-based workflows or forks)
|
|
501
|
+
- Default: currentMergeMode or "local"
|
|
502
|
+
- Validation: Must be one of: local, github-pr
|
|
503
|
+
- Store answer as: `mergeBehavior.mode`
|
|
504
|
+
- Context: Fork workflows (with upstream remote) should typically use "github-pr" mode
|
|
505
|
+
|
|
477
506
|
**Implementation Details:**
|
|
478
507
|
- Set multiSelect: false for all questions (user picks one answer per question)
|
|
479
508
|
- Use the AskUserQuestion tool with all 4 questions in the questions array
|
|
@@ -502,11 +531,13 @@ Then ask: "Does this configuration look correct?"
|
|
|
502
531
|
|
|
503
532
|
### Phase 3: Choose Configuration File
|
|
504
533
|
|
|
534
|
+
**Fork/Contributor Check:** If "GitHub Remote Configuration" shows both "origin" and "upstream" remotes (classic fork pattern), display: "It looks like you're working on a fork (origin + upstream remotes detected). For contributors, we recommend saving to settings.local.json to avoid including personal settings in PRs to upstream, but you can still choose shared if needed."
|
|
535
|
+
|
|
505
536
|
**CRITICAL: You MUST ask the user where to save the configuration. DO NOT make assumptions or decide for them.**
|
|
506
537
|
|
|
507
538
|
Ask the user this exact question: "Should this configuration be committed to git?
|
|
508
539
|
- yes: Save to .iloom/settings.json (shared with team)
|
|
509
|
-
- no: Save to .iloom/settings.local.json (local only, gitignored)"
|
|
540
|
+
- no: Save to .iloom/settings.local.json (local only, gitignored)" <-- recommend this for fork setups
|
|
510
541
|
|
|
511
542
|
**DO NOT:**
|
|
512
543
|
- Assume what the user wants based on the type of setting
|
|
@@ -20,6 +20,94 @@ Before sending any response, verify it doesn't contain:
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
+
{{#IF FIRST_TIME_USER}}
|
|
24
|
+
## First-Time User Context
|
|
25
|
+
|
|
26
|
+
This is the user's first time running through the `iloom` workflow. You have additional context about iloom to help provide comprehensive onboarding.
|
|
27
|
+
|
|
28
|
+
### iloom System Overview
|
|
29
|
+
|
|
30
|
+
iloom is a CLI tool for scaling a human's understanding of the AI's work, as the AI's output scales. It does this by managing isolated Git worktrees in a guided workflow, with significant AI integration. It orchestrates multi-phase workflows:
|
|
31
|
+
|
|
32
|
+
1. **Enhancement** - Expands brief issues into detailed requirements
|
|
33
|
+
2. **Complexity Evaluation** - Categorizes as SIMPLE or COMPLEX based on scope
|
|
34
|
+
3. **Analysis** - Investigates root causes and technical constraints
|
|
35
|
+
4. **Planning** - Creates implementation roadmap with file specifications
|
|
36
|
+
5. **Implementation** - Executes the plan with validation
|
|
37
|
+
|
|
38
|
+
Each phase creates GitHub comments for team visibility and traceability.
|
|
39
|
+
|
|
40
|
+
### Key iloom Commands
|
|
41
|
+
- `iloom init` / `iloom config` - Interactive configuration wizard
|
|
42
|
+
- `iloom start <issue>` - Create isolated loom for issue
|
|
43
|
+
- `iloom spin` - Resume work in current loom with full context
|
|
44
|
+
- `iloom finish` - Validate, commit, merge and cleanup
|
|
45
|
+
- `iloom list` - Show active looms
|
|
46
|
+
- `iloom feedback` - Submit a bug report or a feedback request
|
|
47
|
+
|
|
48
|
+
The `il` command can also be used as a shorter alias.
|
|
49
|
+
|
|
50
|
+
### Loom Isolation Features
|
|
51
|
+
Each loom provides:
|
|
52
|
+
- Dedicated Git worktree (no branch conflicts)
|
|
53
|
+
- Unique database branch via Neon integration
|
|
54
|
+
- Color-coded terminal/VS Code for visual context switching
|
|
55
|
+
- Deterministic port assignment (3000 + issue number)
|
|
56
|
+
|
|
57
|
+
### Configuration System
|
|
58
|
+
**IMPORTANT: NEVER run `iloom init` or `iloom config` as Bash commands from within this session.**
|
|
59
|
+
|
|
60
|
+
These are interactive configuration commands that launch Claude themselves. Instead, tell users to:
|
|
61
|
+
1. Exit this Claude session (type `/exit`)
|
|
62
|
+
2. From their main terminal/worktree, run: `iloom init` or `iloom config`
|
|
63
|
+
3. Complete the configuration with the interactive assistant
|
|
64
|
+
4. Return to their work
|
|
65
|
+
|
|
66
|
+
Settings are stored in `.iloom/settings.json` and .iloom/settings.local.json:
|
|
67
|
+
- `mainBranch` - Primary branch name (auto-detected)
|
|
68
|
+
- `workflows` - Permission modes per workflow type
|
|
69
|
+
- `agents` - Model selection per phase
|
|
70
|
+
- `capabilities.web.basePort` - Development server base port
|
|
71
|
+
|
|
72
|
+
### Complete Documentation Reference - use this to provide answers to questions about functionality or limitations.
|
|
73
|
+
|
|
74
|
+
README_CONTENT
|
|
75
|
+
|
|
76
|
+
### Settings Schema Documentation - use this to suggest solutions to any questions that might be asked
|
|
77
|
+
|
|
78
|
+
SETTINGS_SCHEMA_CONTENT
|
|
79
|
+
|
|
80
|
+
### User Onboarding Instructions
|
|
81
|
+
|
|
82
|
+
**CRITICAL FOR FIRST-TIME USERS: You MUST use AskUserQuestion tool for ALL interactions.**
|
|
83
|
+
|
|
84
|
+
Since this is a first-time user:
|
|
85
|
+
1. **IMMEDIATELY** after your initial greeting, use the AskUserQuestion tool to ask:
|
|
86
|
+
- If they want an explanation of how iloom works
|
|
87
|
+
- If they have questions about the workflow phases
|
|
88
|
+
- If they want to proceed directly to working on their issue
|
|
89
|
+
|
|
90
|
+
2. **IF they ask for explanation**: Use progressive disclosure with AskUserQuestion tool:
|
|
91
|
+
- Give brief overview (2-3 sentences)
|
|
92
|
+
- Use AskUserQuestion to ask what specific aspect they want to learn more about:
|
|
93
|
+
* How looms work (isolation, ports, databases)
|
|
94
|
+
* Workflow phases (enhancement, analysis, planning, implementation)
|
|
95
|
+
* Key commands (start, spin, finish, list)
|
|
96
|
+
* Configuration options (but NEVER run `iloom config` or `iloom init` commands - tell them to exit and run externally)
|
|
97
|
+
- After each explanation, use AskUserQuestion to ask what they want to do next
|
|
98
|
+
|
|
99
|
+
3. **ALWAYS** use AskUserQuestion tool for decision points - NEVER leave users with just text questions
|
|
100
|
+
|
|
101
|
+
4. Only start the workflow after they explicitly choose to proceed via AskUserQuestion
|
|
102
|
+
|
|
103
|
+
5. Be more educational in your explanations throughout the process
|
|
104
|
+
|
|
105
|
+
6. Reference the documentation above when answering questions
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
{{/IF FIRST_TIME_USER}}
|
|
110
|
+
|
|
23
111
|
You are orchestrating a set of agents through a development process, with human review at each step. This is referred to as the "iloom workflow".
|
|
24
112
|
|
|
25
113
|
**IMPORTANT: Unless otherwise instructed, each step requires explicit human approval. Do not proceed to any step until explicitly told to do so.**
|
|
@@ -117,7 +205,16 @@ Only execute if workflow plan determined NEEDS_ENHANCEMENT:
|
|
|
117
205
|
1. Execute: @agent-iloom-issue-enhancer ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
|
|
118
206
|
2. Upon completion: Extract issue+comment link (including comment ID) from agent output and provide link to GitHub issue comment
|
|
119
207
|
3. Mark todo #2 and #3 as completed
|
|
120
|
-
4.
|
|
208
|
+
4. Use AskUserQuestion tool with a single question:
|
|
209
|
+
- Question: "Enhancement complete. See results at: [GitHub comment URL]. How would you like to proceed?"
|
|
210
|
+
- Options:
|
|
211
|
+
- "Continue to complexity evaluation" (default)
|
|
212
|
+
- "Provide feedback on enhancement"
|
|
213
|
+
- "Exit workflow"
|
|
214
|
+
- multiSelect: false
|
|
215
|
+
- If user chooses "Provide feedback on enhancement": Process their input and re-run enhancement if needed, telling the agent to edit their comment and provide the comment id
|
|
216
|
+
- If user chooses "Exit workflow": End workflow gracefully
|
|
217
|
+
- If user chooses "Continue to complexity evaluation": Proceed to next step
|
|
121
218
|
|
|
122
219
|
If workflow plan determined SKIP_ENHANCEMENT:
|
|
123
220
|
1. Mark todos #2 and #3 as completed
|
|
@@ -145,10 +242,20 @@ Only execute if workflow plan determined NEEDS_COMPLEXITY_EVAL:
|
|
|
145
242
|
|
|
146
243
|
Reasoning: [reasoning text from evaluator]
|
|
147
244
|
```
|
|
148
|
-
5.
|
|
149
|
-
|
|
245
|
+
5. Use AskUserQuestion tool with a single question:
|
|
246
|
+
- Question: "Complexity evaluated as [SIMPLE/COMPLEX]. See assessment at: [GitHub comment URL]. Do you agree with this classification?"
|
|
247
|
+
- Options:
|
|
248
|
+
- "Yes, proceed with [SIMPLE/COMPLEX] workflow" (default)
|
|
249
|
+
- "No, reclassify as SIMPLE"
|
|
250
|
+
- "No, reclassify as COMPLEX"
|
|
251
|
+
- "Provide feedback before deciding"
|
|
252
|
+
- "Exit workflow"
|
|
253
|
+
- multiSelect: false
|
|
254
|
+
- If user reclassifies: Update confirmed complexity accordingly
|
|
255
|
+
- If user chooses "Provide feedback before deciding": Process and re-evaluate if needed, telling the agent to edit their comment and provide the comment id
|
|
256
|
+
- If user chooses "Exit workflow": End workflow gracefully
|
|
150
257
|
6. Mark todos #5, #6, and #7 as completed
|
|
151
|
-
7.
|
|
258
|
+
7. Proceed to ROUTING DECISION POINT with confirmed complexity
|
|
152
259
|
|
|
153
260
|
If workflow plan determined SKIP_COMPLEXITY_EVAL:
|
|
154
261
|
1. Mark todos #5, #6, and #7 as completed
|
|
@@ -167,7 +274,16 @@ Only execute if workflow plan determined NEEDS_ANALYSIS AND complexity is COMPLE
|
|
|
167
274
|
1. Execute: @agent-iloom-issue-analyzer ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
|
|
168
275
|
2. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
|
|
169
276
|
3. Mark todos #10, #11, and #12 as completed (todo #9 was already marked at routing decision point)
|
|
170
|
-
4.
|
|
277
|
+
4. Use AskUserQuestion tool with a single question:
|
|
278
|
+
- Question: "Analysis complete. See findings at: [GitHub comment URL]. How would you like to proceed?"
|
|
279
|
+
- Options:
|
|
280
|
+
- "Continue to planning phase" (default)
|
|
281
|
+
- "Provide feedback on analysis"
|
|
282
|
+
- "Exit workflow"
|
|
283
|
+
- multiSelect: false
|
|
284
|
+
- If user chooses "Provide feedback on analysis": Process their input and re-run analysis if needed, telling the agent to edit their comment and provide the comment id
|
|
285
|
+
- If user chooses "Exit workflow": End workflow gracefully
|
|
286
|
+
- If user chooses "Continue to planning phase": Proceed to planning phase
|
|
171
287
|
|
|
172
288
|
If workflow plan determined SKIP_ANALYSIS:
|
|
173
289
|
1. Mark todos #10, #11, and #12 as completed (todo #9 was already marked at routing decision point)
|
|
@@ -206,9 +322,16 @@ Execute combined analyze-and-plan agent:
|
|
|
206
322
|
2. Execute: @agent-iloom-issue-analyze-and-plan ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
|
|
207
323
|
3. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
|
|
208
324
|
4. Mark todo #9 as completed (COMPLEX todos #10-15 were already marked at routing decision point)
|
|
209
|
-
5.
|
|
210
|
-
|
|
211
|
-
|
|
325
|
+
5. Use AskUserQuestion tool with a single question:
|
|
326
|
+
- Question: "Combined analysis and planning complete. See results at: [GitHub comment URL]. How would you like to proceed?"
|
|
327
|
+
- Options:
|
|
328
|
+
- "Continue to implementation" (default)
|
|
329
|
+
- "Provide feedback on plan"
|
|
330
|
+
- "Exit workflow"
|
|
331
|
+
- multiSelect: false
|
|
332
|
+
- If user chooses "Provide feedback on plan": Process their input and re-run planning if needed, telling the agent to edit their comment and provide the comment id
|
|
333
|
+
- If user chooses "Exit workflow": End workflow gracefully
|
|
334
|
+
- If user chooses "Continue to implementation": Proceed to STEP 4 (Implementation Phase)
|
|
212
335
|
|
|
213
336
|
---
|
|
214
337
|
|
|
@@ -220,7 +343,16 @@ Only execute if workflow plan determined NEEDS_PLANNING AND complexity is COMPLE
|
|
|
220
343
|
1. Execute: @agent-iloom-issue-planner ISSUE_NUMBER instructing them to add their own answers to any questions they asked in the question tables they create in their GitHub comments. This documents assumptions made during execution.
|
|
221
344
|
2. Upon completion: Extract issue+comment link from agent output and provide link to GitHub issue comment (including comment ID)
|
|
222
345
|
3. Mark todos #13, #14, and #15 as completed (todos #9-12 were already marked at routing decision point and analysis phase)
|
|
223
|
-
4.
|
|
346
|
+
4. Use AskUserQuestion tool with a single question:
|
|
347
|
+
- Question: "Planning complete. See implementation plan at: [GitHub comment URL]. How would you like to proceed?"
|
|
348
|
+
- Options:
|
|
349
|
+
- "Continue to implementation" (default)
|
|
350
|
+
- "Provide feedback on plan"
|
|
351
|
+
- "Exit workflow"
|
|
352
|
+
- multiSelect: false
|
|
353
|
+
- If user chooses "Provide feedback on plan": Process their input and re-run planning if needed, telling the agent to edit their comment and provide the comment id
|
|
354
|
+
- If user chooses "Exit workflow": End workflow gracefully
|
|
355
|
+
- If user chooses "Continue to implementation": Proceed to STEP 4 (Implementation Phase)
|
|
224
356
|
|
|
225
357
|
If workflow plan determined SKIP_PLANNING AND complexity is COMPLEX:
|
|
226
358
|
1. Mark todos #13, #14, and #15 as completed (todos #9-12 were already marked at routing decision point and analysis phase)
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
MergeManager
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4IV6W4U5.js";
|
|
5
|
+
import "./chunk-MFU53H6J.js";
|
|
6
6
|
import {
|
|
7
7
|
GitWorktreeManager
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VETG35MF.js";
|
|
9
9
|
import {
|
|
10
10
|
SettingsManager
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-T3KEIB4D.js";
|
|
12
12
|
import {
|
|
13
13
|
getRepoRoot,
|
|
14
14
|
isValidGitRepo
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-HBVFXN7R.js";
|
|
16
16
|
import {
|
|
17
17
|
logger
|
|
18
18
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -92,4 +92,4 @@ export {
|
|
|
92
92
|
RebaseCommand,
|
|
93
93
|
WorktreeValidationError
|
|
94
94
|
};
|
|
95
|
-
//# sourceMappingURL=rebase-
|
|
95
|
+
//# sourceMappingURL=rebase-DUNFOJVS.js.map
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getConfiguredRepoFromSettings,
|
|
4
|
+
getEffectivePRTargetRemote,
|
|
4
5
|
hasMultipleRemotes,
|
|
5
6
|
parseGitRemotes,
|
|
6
7
|
validateConfiguredRemote
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-DJUGYNQE.js";
|
|
8
9
|
export {
|
|
9
10
|
getConfiguredRepoFromSettings,
|
|
11
|
+
getEffectivePRTargetRemote,
|
|
10
12
|
hasMultipleRemotes,
|
|
11
13
|
parseGitRemotes,
|
|
12
14
|
validateConfiguredRemote
|
|
13
15
|
};
|
|
14
|
-
//# sourceMappingURL=remote-
|
|
16
|
+
//# sourceMappingURL=remote-ZCXJVVNW.js.map
|
|
@@ -25,11 +25,11 @@ import {
|
|
|
25
25
|
} from "./chunk-YETJNRQM.js";
|
|
26
26
|
import {
|
|
27
27
|
GitWorktreeManager
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-VETG35MF.js";
|
|
29
29
|
import {
|
|
30
30
|
SettingsManager
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-T3KEIB4D.js";
|
|
32
|
+
import "./chunk-HBVFXN7R.js";
|
|
33
33
|
import {
|
|
34
34
|
logger
|
|
35
35
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -273,4 +273,4 @@ Make sure the project is built (run 'il start' first)`
|
|
|
273
273
|
export {
|
|
274
274
|
RunCommand
|
|
275
275
|
};
|
|
276
|
-
//# sourceMappingURL=run-
|
|
276
|
+
//# sourceMappingURL=run-O7ZK7CKA.js.map
|
|
@@ -262,6 +262,24 @@
|
|
|
262
262
|
},
|
|
263
263
|
"additionalProperties": false,
|
|
264
264
|
"description": "Issue management configuration"
|
|
265
|
+
},
|
|
266
|
+
"mergeBehavior": {
|
|
267
|
+
"type": "object",
|
|
268
|
+
"properties": {
|
|
269
|
+
"mode": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"enum": [
|
|
272
|
+
"local",
|
|
273
|
+
"github-pr"
|
|
274
|
+
],
|
|
275
|
+
"default": "local"
|
|
276
|
+
},
|
|
277
|
+
"remote": {
|
|
278
|
+
"type": "string"
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
"additionalProperties": false,
|
|
282
|
+
"description": "Merge behavior configuration: local (merge locally) or github-pr (create PR)"
|
|
265
283
|
}
|
|
266
284
|
},
|
|
267
285
|
"additionalProperties": false
|