@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.
Files changed (161) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +711 -0
  3. package/dist/ClaudeContextManager-XOSXQ67R.js +13 -0
  4. package/dist/ClaudeContextManager-XOSXQ67R.js.map +1 -0
  5. package/dist/ClaudeService-YSZ6EXWP.js +12 -0
  6. package/dist/ClaudeService-YSZ6EXWP.js.map +1 -0
  7. package/dist/GitHubService-F7Z3XJOS.js +11 -0
  8. package/dist/GitHubService-F7Z3XJOS.js.map +1 -0
  9. package/dist/LoomLauncher-MODG2SEM.js +263 -0
  10. package/dist/LoomLauncher-MODG2SEM.js.map +1 -0
  11. package/dist/NeonProvider-PAGPUH7F.js +12 -0
  12. package/dist/NeonProvider-PAGPUH7F.js.map +1 -0
  13. package/dist/PromptTemplateManager-7FINLRDE.js +9 -0
  14. package/dist/PromptTemplateManager-7FINLRDE.js.map +1 -0
  15. package/dist/SettingsManager-VAZF26S2.js +19 -0
  16. package/dist/SettingsManager-VAZF26S2.js.map +1 -0
  17. package/dist/SettingsMigrationManager-MTQIMI54.js +146 -0
  18. package/dist/SettingsMigrationManager-MTQIMI54.js.map +1 -0
  19. package/dist/add-issue-22JBNOML.js +54 -0
  20. package/dist/add-issue-22JBNOML.js.map +1 -0
  21. package/dist/agents/iloom-issue-analyze-and-plan.md +580 -0
  22. package/dist/agents/iloom-issue-analyzer.md +290 -0
  23. package/dist/agents/iloom-issue-complexity-evaluator.md +224 -0
  24. package/dist/agents/iloom-issue-enhancer.md +266 -0
  25. package/dist/agents/iloom-issue-implementer.md +262 -0
  26. package/dist/agents/iloom-issue-planner.md +358 -0
  27. package/dist/agents/iloom-issue-reviewer.md +63 -0
  28. package/dist/chunk-2ZPFJQ3B.js +63 -0
  29. package/dist/chunk-2ZPFJQ3B.js.map +1 -0
  30. package/dist/chunk-37DYYFVK.js +29 -0
  31. package/dist/chunk-37DYYFVK.js.map +1 -0
  32. package/dist/chunk-BLCTGFZN.js +121 -0
  33. package/dist/chunk-BLCTGFZN.js.map +1 -0
  34. package/dist/chunk-CP2NU2JC.js +545 -0
  35. package/dist/chunk-CP2NU2JC.js.map +1 -0
  36. package/dist/chunk-CWR2SANQ.js +39 -0
  37. package/dist/chunk-CWR2SANQ.js.map +1 -0
  38. package/dist/chunk-F3XBU2R7.js +110 -0
  39. package/dist/chunk-F3XBU2R7.js.map +1 -0
  40. package/dist/chunk-GEHQXLEI.js +130 -0
  41. package/dist/chunk-GEHQXLEI.js.map +1 -0
  42. package/dist/chunk-GYCR2LOU.js +143 -0
  43. package/dist/chunk-GYCR2LOU.js.map +1 -0
  44. package/dist/chunk-GZP4UGGM.js +48 -0
  45. package/dist/chunk-GZP4UGGM.js.map +1 -0
  46. package/dist/chunk-H4E4THUZ.js +55 -0
  47. package/dist/chunk-H4E4THUZ.js.map +1 -0
  48. package/dist/chunk-HPJJSYNS.js +644 -0
  49. package/dist/chunk-HPJJSYNS.js.map +1 -0
  50. package/dist/chunk-JBH2ZYYZ.js +220 -0
  51. package/dist/chunk-JBH2ZYYZ.js.map +1 -0
  52. package/dist/chunk-JNKJ7NJV.js +78 -0
  53. package/dist/chunk-JNKJ7NJV.js.map +1 -0
  54. package/dist/chunk-JQ7VOSTC.js +437 -0
  55. package/dist/chunk-JQ7VOSTC.js.map +1 -0
  56. package/dist/chunk-KQDEK2ZW.js +199 -0
  57. package/dist/chunk-KQDEK2ZW.js.map +1 -0
  58. package/dist/chunk-O2QWO64Z.js +179 -0
  59. package/dist/chunk-O2QWO64Z.js.map +1 -0
  60. package/dist/chunk-OC4H6HJD.js +248 -0
  61. package/dist/chunk-OC4H6HJD.js.map +1 -0
  62. package/dist/chunk-PR7FKQBG.js +120 -0
  63. package/dist/chunk-PR7FKQBG.js.map +1 -0
  64. package/dist/chunk-PXZBAC2M.js +250 -0
  65. package/dist/chunk-PXZBAC2M.js.map +1 -0
  66. package/dist/chunk-QEPVTTHD.js +383 -0
  67. package/dist/chunk-QEPVTTHD.js.map +1 -0
  68. package/dist/chunk-RSRO7564.js +203 -0
  69. package/dist/chunk-RSRO7564.js.map +1 -0
  70. package/dist/chunk-SJUQ2NDR.js +146 -0
  71. package/dist/chunk-SJUQ2NDR.js.map +1 -0
  72. package/dist/chunk-SPYPLHMK.js +177 -0
  73. package/dist/chunk-SPYPLHMK.js.map +1 -0
  74. package/dist/chunk-SSCQCCJ7.js +75 -0
  75. package/dist/chunk-SSCQCCJ7.js.map +1 -0
  76. package/dist/chunk-SSR5AVRJ.js +41 -0
  77. package/dist/chunk-SSR5AVRJ.js.map +1 -0
  78. package/dist/chunk-T7QPXANZ.js +315 -0
  79. package/dist/chunk-T7QPXANZ.js.map +1 -0
  80. package/dist/chunk-U3WU5OWO.js +203 -0
  81. package/dist/chunk-U3WU5OWO.js.map +1 -0
  82. package/dist/chunk-W3DQTW63.js +124 -0
  83. package/dist/chunk-W3DQTW63.js.map +1 -0
  84. package/dist/chunk-WKEWRSDB.js +151 -0
  85. package/dist/chunk-WKEWRSDB.js.map +1 -0
  86. package/dist/chunk-Y7SAGNUT.js +66 -0
  87. package/dist/chunk-Y7SAGNUT.js.map +1 -0
  88. package/dist/chunk-YETJNRQM.js +39 -0
  89. package/dist/chunk-YETJNRQM.js.map +1 -0
  90. package/dist/chunk-YYSKGAZT.js +384 -0
  91. package/dist/chunk-YYSKGAZT.js.map +1 -0
  92. package/dist/chunk-ZZZWQGTS.js +169 -0
  93. package/dist/chunk-ZZZWQGTS.js.map +1 -0
  94. package/dist/claude-7LUVDZZ4.js +17 -0
  95. package/dist/claude-7LUVDZZ4.js.map +1 -0
  96. package/dist/cleanup-3LUWPSM7.js +412 -0
  97. package/dist/cleanup-3LUWPSM7.js.map +1 -0
  98. package/dist/cli-overrides-XFZWY7CM.js +16 -0
  99. package/dist/cli-overrides-XFZWY7CM.js.map +1 -0
  100. package/dist/cli.js +603 -0
  101. package/dist/cli.js.map +1 -0
  102. package/dist/color-ZVALX37U.js +21 -0
  103. package/dist/color-ZVALX37U.js.map +1 -0
  104. package/dist/enhance-XJIQHVPD.js +166 -0
  105. package/dist/enhance-XJIQHVPD.js.map +1 -0
  106. package/dist/env-MDFL4ZXL.js +23 -0
  107. package/dist/env-MDFL4ZXL.js.map +1 -0
  108. package/dist/feedback-23CLXKFT.js +158 -0
  109. package/dist/feedback-23CLXKFT.js.map +1 -0
  110. package/dist/finish-CY4CIH6O.js +1608 -0
  111. package/dist/finish-CY4CIH6O.js.map +1 -0
  112. package/dist/git-LVRZ57GJ.js +43 -0
  113. package/dist/git-LVRZ57GJ.js.map +1 -0
  114. package/dist/ignite-WXEF2ID5.js +359 -0
  115. package/dist/ignite-WXEF2ID5.js.map +1 -0
  116. package/dist/index.d.ts +1341 -0
  117. package/dist/index.js +3058 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/init-RHACUR4E.js +123 -0
  120. package/dist/init-RHACUR4E.js.map +1 -0
  121. package/dist/installation-detector-VARGFFRZ.js +11 -0
  122. package/dist/installation-detector-VARGFFRZ.js.map +1 -0
  123. package/dist/logger-MKYH4UDV.js +12 -0
  124. package/dist/logger-MKYH4UDV.js.map +1 -0
  125. package/dist/mcp/chunk-6SDFJ42P.js +62 -0
  126. package/dist/mcp/chunk-6SDFJ42P.js.map +1 -0
  127. package/dist/mcp/claude-YHHHLSXH.js +249 -0
  128. package/dist/mcp/claude-YHHHLSXH.js.map +1 -0
  129. package/dist/mcp/color-QS5BFCNN.js +168 -0
  130. package/dist/mcp/color-QS5BFCNN.js.map +1 -0
  131. package/dist/mcp/github-comment-server.js +165 -0
  132. package/dist/mcp/github-comment-server.js.map +1 -0
  133. package/dist/mcp/terminal-SDCMDVD7.js +202 -0
  134. package/dist/mcp/terminal-SDCMDVD7.js.map +1 -0
  135. package/dist/open-X6BTENPV.js +278 -0
  136. package/dist/open-X6BTENPV.js.map +1 -0
  137. package/dist/prompt-ANTQWHUF.js +13 -0
  138. package/dist/prompt-ANTQWHUF.js.map +1 -0
  139. package/dist/prompts/issue-prompt.txt +230 -0
  140. package/dist/prompts/pr-prompt.txt +35 -0
  141. package/dist/prompts/regular-prompt.txt +14 -0
  142. package/dist/run-2JCPQAX3.js +278 -0
  143. package/dist/run-2JCPQAX3.js.map +1 -0
  144. package/dist/schema/settings.schema.json +221 -0
  145. package/dist/start-LWVRBJ6S.js +982 -0
  146. package/dist/start-LWVRBJ6S.js.map +1 -0
  147. package/dist/terminal-3D6TUAKJ.js +16 -0
  148. package/dist/terminal-3D6TUAKJ.js.map +1 -0
  149. package/dist/test-git-XPF4SZXJ.js +52 -0
  150. package/dist/test-git-XPF4SZXJ.js.map +1 -0
  151. package/dist/test-prefix-XGFXFAYN.js +68 -0
  152. package/dist/test-prefix-XGFXFAYN.js.map +1 -0
  153. package/dist/test-tabs-JRKY3QMM.js +69 -0
  154. package/dist/test-tabs-JRKY3QMM.js.map +1 -0
  155. package/dist/test-webserver-M2I3EV4J.js +62 -0
  156. package/dist/test-webserver-M2I3EV4J.js.map +1 -0
  157. package/dist/update-3ZT2XX2G.js +79 -0
  158. package/dist/update-3ZT2XX2G.js.map +1 -0
  159. package/dist/update-notifier-QSSEB5KC.js +11 -0
  160. package/dist/update-notifier-QSSEB5KC.js.map +1 -0
  161. package/package.json +113 -0
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger
4
+ } from "./chunk-GEHQXLEI.js";
5
+
6
+ // src/utils/github.ts
7
+ import { execa } from "execa";
8
+ async function executeGhCommand(args, options) {
9
+ const result = await execa("gh", args, {
10
+ cwd: (options == null ? void 0 : options.cwd) ?? process.cwd(),
11
+ timeout: (options == null ? void 0 : options.timeout) ?? 3e4,
12
+ encoding: "utf8"
13
+ });
14
+ const isJson = args.includes("--json") || args.includes("--jq") || args.includes("--format") && args[args.indexOf("--format") + 1] === "json";
15
+ const data = isJson ? JSON.parse(result.stdout) : result.stdout;
16
+ return data;
17
+ }
18
+ async function checkGhAuth() {
19
+ var _a, _b;
20
+ try {
21
+ const output = await executeGhCommand(["auth", "status"]);
22
+ const scopeMatch = output.match(/Token scopes: (.+)/);
23
+ const userMatch = output.match(/Logged in to github\.com as ([^\s]+)/);
24
+ const username = userMatch == null ? void 0 : userMatch[1];
25
+ return {
26
+ hasAuth: true,
27
+ scopes: ((_a = scopeMatch == null ? void 0 : scopeMatch[1]) == null ? void 0 : _a.split(", ").map((scope) => scope.replace(/^'|'$/g, ""))) ?? [],
28
+ ...username && { username }
29
+ };
30
+ } catch (error) {
31
+ if (error instanceof Error && "stderr" in error && ((_b = error.stderr) == null ? void 0 : _b.includes("You are not logged into any GitHub hosts"))) {
32
+ return { hasAuth: false, scopes: [] };
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ async function hasProjectScope() {
38
+ const auth = await checkGhAuth();
39
+ return auth.scopes.includes("project");
40
+ }
41
+ async function fetchGhIssue(issueNumber, repo) {
42
+ logger.debug("Fetching GitHub issue", { issueNumber, repo });
43
+ const args = [
44
+ "issue",
45
+ "view",
46
+ String(issueNumber),
47
+ "--json",
48
+ "number,title,body,state,labels,assignees,url,createdAt,updatedAt"
49
+ ];
50
+ if (repo) {
51
+ args.push("--repo", repo);
52
+ }
53
+ return executeGhCommand(args);
54
+ }
55
+ async function fetchGhPR(prNumber) {
56
+ logger.debug("Fetching GitHub PR", { prNumber });
57
+ return executeGhCommand([
58
+ "pr",
59
+ "view",
60
+ String(prNumber),
61
+ "--json",
62
+ "number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt"
63
+ ]);
64
+ }
65
+ async function fetchProjectList(owner) {
66
+ const result = await executeGhCommand([
67
+ "project",
68
+ "list",
69
+ "--owner",
70
+ owner,
71
+ "--limit",
72
+ "100",
73
+ "--format",
74
+ "json"
75
+ ]);
76
+ return (result == null ? void 0 : result.projects) ?? [];
77
+ }
78
+ async function fetchProjectItems(projectNumber, owner) {
79
+ const result = await executeGhCommand([
80
+ "project",
81
+ "item-list",
82
+ String(projectNumber),
83
+ "--owner",
84
+ owner,
85
+ "--limit",
86
+ "10000",
87
+ "--format",
88
+ "json"
89
+ ]);
90
+ return (result == null ? void 0 : result.items) ?? [];
91
+ }
92
+ async function fetchProjectFields(projectNumber, owner) {
93
+ const result = await executeGhCommand([
94
+ "project",
95
+ "field-list",
96
+ String(projectNumber),
97
+ "--owner",
98
+ owner,
99
+ "--format",
100
+ "json"
101
+ ]);
102
+ return result ?? { fields: [] };
103
+ }
104
+ async function updateProjectItemField(itemId, projectId, fieldId, optionId) {
105
+ await executeGhCommand([
106
+ "project",
107
+ "item-edit",
108
+ "--id",
109
+ itemId,
110
+ "--project-id",
111
+ projectId,
112
+ "--field-id",
113
+ fieldId,
114
+ "--single-select-option-id",
115
+ optionId,
116
+ "--format",
117
+ "json"
118
+ ]);
119
+ }
120
+ var SimpleBranchNameStrategy = class {
121
+ async generate(issueNumber, title) {
122
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").substring(0, 20);
123
+ return `feat/issue-${issueNumber}-${slug}`;
124
+ }
125
+ };
126
+ var ClaudeBranchNameStrategy = class {
127
+ constructor(claudeModel = "haiku") {
128
+ this.claudeModel = claudeModel;
129
+ }
130
+ async generate(issueNumber, title) {
131
+ const { generateBranchName } = await import("./claude-7LUVDZZ4.js");
132
+ return generateBranchName(title, issueNumber, this.claudeModel);
133
+ }
134
+ };
135
+ async function createIssue(title, body, options) {
136
+ const { repo, labels } = options ?? {};
137
+ logger.debug("Creating GitHub issue", { title, repo, labels });
138
+ const args = [
139
+ "issue",
140
+ "create",
141
+ "--title",
142
+ title,
143
+ "--body",
144
+ body
145
+ ];
146
+ if (repo) {
147
+ args.splice(2, 0, "--repo", repo);
148
+ }
149
+ if (labels && labels.length > 0) {
150
+ args.push("--label", labels.join(","));
151
+ }
152
+ const execaOptions = {
153
+ timeout: 3e4,
154
+ encoding: "utf8"
155
+ };
156
+ if (!repo) {
157
+ execaOptions.cwd = process.cwd();
158
+ }
159
+ const result = await execa("gh", args, execaOptions);
160
+ const urlMatch = result.stdout.trim().match(/https:\/\/github\.com\/[^/]+\/[^/]+\/issues\/(\d+)/);
161
+ if (!(urlMatch == null ? void 0 : urlMatch[1])) {
162
+ throw new Error(`Failed to parse issue URL from gh output: ${result.stdout}`);
163
+ }
164
+ const issueNumber = parseInt(urlMatch[1], 10);
165
+ const issueUrl = urlMatch[0];
166
+ return {
167
+ number: issueNumber,
168
+ url: issueUrl
169
+ };
170
+ }
171
+ async function getRepoInfo() {
172
+ logger.debug("Fetching repository info");
173
+ const result = await executeGhCommand([
174
+ "repo",
175
+ "view",
176
+ "--json",
177
+ "owner,name"
178
+ ]);
179
+ return {
180
+ owner: result.owner.login,
181
+ name: result.name
182
+ };
183
+ }
184
+
185
+ export {
186
+ executeGhCommand,
187
+ hasProjectScope,
188
+ fetchGhIssue,
189
+ fetchGhPR,
190
+ fetchProjectList,
191
+ fetchProjectItems,
192
+ fetchProjectFields,
193
+ updateProjectItemField,
194
+ SimpleBranchNameStrategy,
195
+ ClaudeBranchNameStrategy,
196
+ createIssue,
197
+ getRepoInfo
198
+ };
199
+ //# sourceMappingURL=chunk-KQDEK2ZW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/github.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type {\n\tGitHubIssue,\n\tGitHubPullRequest,\n\tGitHubProject,\n\tGitHubAuthStatus,\n\tBranchNameStrategy,\n\tProjectItem,\n\tProjectField,\n} from '../types/github.js'\nimport { logger } from './logger.js'\n\n// Core GitHub CLI execution wrapper\nexport async function executeGhCommand<T = unknown>(\n\targs: string[],\n\toptions?: { cwd?: string; timeout?: number }\n): Promise<T> {\n\tconst result = await execa('gh', args, {\n\t\tcwd: options?.cwd ?? process.cwd(),\n\t\ttimeout: options?.timeout ?? 30000,\n\t\tencoding: 'utf8',\n\t})\n\n\t// Parse JSON output if --json flag, --format json, or --jq was used\n\tconst isJson =\n\t\targs.includes('--json') ||\n\t\targs.includes('--jq') ||\n\t\t(args.includes('--format') && args[args.indexOf('--format') + 1] === 'json')\n\tconst data = isJson ? JSON.parse(result.stdout) : result.stdout\n\n\treturn data as T\n}\n\n// Authentication checking\nexport async function checkGhAuth(): Promise<GitHubAuthStatus> {\n\ttry {\n\t\tconst output = await executeGhCommand<string>(['auth', 'status'])\n\n\t\t// Parse auth status output\n\t\tconst scopeMatch = output.match(/Token scopes: (.+)/)\n\t\tconst userMatch = output.match(/Logged in to github\\.com as ([^\\s]+)/)\n\n\t\tconst username = userMatch?.[1]\n\n\t\treturn {\n\t\t\thasAuth: true,\n\t\t\tscopes: scopeMatch?.[1]?.split(', ').map(scope => scope.replace(/^'|'$/g, '')) ?? [],\n\t\t\t...(username && { username }),\n\t\t}\n\t} catch (error) {\n\t\t// Only return \"no auth\" for specific authentication errors\n\t\tif (error instanceof Error && 'stderr' in error && (error as {stderr?: string}).stderr?.includes('You are not logged into any GitHub hosts')) {\n\t\t\treturn { hasAuth: false, scopes: [] }\n\t\t}\n\t\t// Re-throw unexpected errors\n\t\tthrow error\n\t}\n}\n\nexport async function hasProjectScope(): Promise<boolean> {\n\tconst auth = await checkGhAuth()\n\treturn auth.scopes.includes('project')\n}\n\n// Issue fetching\nexport async function fetchGhIssue(\n\tissueNumber: number,\n\trepo?: string\n): Promise<GitHubIssue> {\n\tlogger.debug('Fetching GitHub issue', { issueNumber, repo })\n\n\tconst args = [\n\t\t'issue',\n\t\t'view',\n\t\tString(issueNumber),\n\t\t'--json',\n\t\t'number,title,body,state,labels,assignees,url,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubIssue>(args)\n}\n\n// PR fetching\nexport async function fetchGhPR(\n\tprNumber: number\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber })\n\n\treturn executeGhCommand<GitHubPullRequest>([\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t])\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber })\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/${issueNumber}/comments`,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId })\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/comments/${commentId}`,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber })\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\t`repos/:owner/:repo/issues/${prNumber}/comments`,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":";;;;;;AAAA,SAAS,aAAa;AAatB,eAAsB,iBACrB,MACA,SACa;AACb,QAAM,SAAS,MAAM,MAAM,MAAM,MAAM;AAAA,IACtC,MAAK,mCAAS,QAAO,QAAQ,IAAI;AAAA,IACjC,UAAS,mCAAS,YAAW;AAAA,IAC7B,UAAU;AAAA,EACX,CAAC;AAGD,QAAM,SACL,KAAK,SAAS,QAAQ,KACtB,KAAK,SAAS,MAAM,KACnB,KAAK,SAAS,UAAU,KAAK,KAAK,KAAK,QAAQ,UAAU,IAAI,CAAC,MAAM;AACtE,QAAM,OAAO,SAAS,KAAK,MAAM,OAAO,MAAM,IAAI,OAAO;AAEzD,SAAO;AACR;AAGA,eAAsB,cAAyC;AAlC/D;AAmCC,MAAI;AACH,UAAM,SAAS,MAAM,iBAAyB,CAAC,QAAQ,QAAQ,CAAC;AAGhE,UAAM,aAAa,OAAO,MAAM,oBAAoB;AACpD,UAAM,YAAY,OAAO,MAAM,sCAAsC;AAErE,UAAM,WAAW,uCAAY;AAE7B,WAAO;AAAA,MACN,SAAS;AAAA,MACT,UAAQ,8CAAa,OAAb,mBAAiB,MAAM,MAAM,IAAI,WAAS,MAAM,QAAQ,UAAU,EAAE,OAAM,CAAC;AAAA,MACnF,GAAI,YAAY,EAAE,SAAS;AAAA,IAC5B;AAAA,EACD,SAAS,OAAO;AAEf,QAAI,iBAAiB,SAAS,YAAY,WAAU,WAA4B,WAA5B,mBAAoC,SAAS,8CAA6C;AAC7I,aAAO,EAAE,SAAS,OAAO,QAAQ,CAAC,EAAE;AAAA,IACrC;AAEA,UAAM;AAAA,EACP;AACD;AAEA,eAAsB,kBAAoC;AACzD,QAAM,OAAO,MAAM,YAAY;AAC/B,SAAO,KAAK,OAAO,SAAS,SAAS;AACtC;AAGA,eAAsB,aACrB,aACA,MACuB;AACvB,SAAO,MAAM,yBAAyB,EAAE,aAAa,KAAK,CAAC;AAE3D,QAAM,OAAO;AAAA,IACZ;AAAA,IACA;AAAA,IACA,OAAO,WAAW;AAAA,IAClB;AAAA,IACA;AAAA,EACD;AAEA,MAAI,MAAM;AACT,SAAK,KAAK,UAAU,IAAI;AAAA,EACzB;AAEA,SAAO,iBAA8B,IAAI;AAC1C;AAGA,eAAsB,UACrB,UAC6B;AAC7B,SAAO,MAAM,sBAAsB,EAAE,SAAS,CAAC;AAE/C,SAAO,iBAAoC;AAAA,IAC1C;AAAA,IACA;AAAA,IACA,OAAO,QAAQ;AAAA,IACf;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAGA,eAAsB,iBACrB,OAC2B;AAC3B,QAAM,SAAS,MAAM,iBAAgD;AAAA,IACpE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AAED,UAAO,iCAAQ,aAAY,CAAC;AAC7B;AAEA,eAAsB,kBACrB,eACA,OACyB;AACzB,QAAM,SAAS,MAAM,iBAA2C;AAAA,IAC/D;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AAED,UAAO,iCAAQ,UAAS,CAAC;AAC1B;AAEA,eAAsB,mBACrB,eACA,OACsC;AACtC,QAAM,SAAS,MAAM,iBAA6C;AAAA,IACjE;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AAED,SAAO,UAAU,EAAE,QAAQ,CAAC,EAAE;AAC/B;AAEA,eAAsB,uBACrB,QACA,WACA,SACA,UACgB;AAChB,QAAM,iBAAiB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAGO,IAAM,2BAAN,MAA6D;AAAA,EACnE,MAAM,SAAS,aAAqB,OAAgC;AAEnE,UAAM,OAAO,MACX,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,UAAU,EAAE,EACpB,UAAU,GAAG,EAAE;AAEjB,WAAO,cAAc,WAAW,IAAI,IAAI;AAAA,EACzC;AACD;AAEO,IAAM,2BAAN,MAA6D;AAAA,EACnE,YAAoB,cAAc,SAAS;AAAvB;AAAA,EAAwB;AAAA,EAE5C,MAAM,SAAS,aAAqB,OAAgC;AAEnE,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,sBAAoB;AAGhE,WAAO,mBAAmB,OAAO,aAAa,KAAK,WAAW;AAAA,EAC/D;AACD;AAiDA,eAAsB,YACrB,OACA,MACA,SAC+B;AAC/B,QAAM,EAAE,MAAM,OAAO,IAAI,WAAW,CAAC;AAErC,SAAO,MAAM,yBAAyB,EAAE,OAAO,MAAM,OAAO,CAAC;AAE7D,QAAM,OAAO;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAGA,MAAI,MAAM;AACT,SAAK,OAAO,GAAG,GAAG,UAAU,IAAI;AAAA,EACjC;AAGA,MAAI,UAAU,OAAO,SAAS,GAAG;AAChC,SAAK,KAAK,WAAW,OAAO,KAAK,GAAG,CAAC;AAAA,EACtC;AAEA,QAAM,eAAoE;AAAA,IACzE,SAAS;AAAA,IACT,UAAU;AAAA,EACX;AAEA,MAAI,CAAC,MAAM;AACV,iBAAa,MAAM,QAAQ,IAAI;AAAA,EAChC;AAEA,QAAM,SAAS,MAAM,MAAM,MAAM,MAAM,YAAY;AAGnD,QAAM,WAAW,OAAO,OAAO,KAAK,EAAE,MAAM,oDAAoD;AAChG,MAAI,EAAC,qCAAW,KAAI;AACnB,UAAM,IAAI,MAAM,6CAA6C,OAAO,MAAM,EAAE;AAAA,EAC7E;AAEA,QAAM,cAAc,SAAS,SAAS,CAAC,GAAG,EAAE;AAC5C,QAAM,WAAW,SAAS,CAAC;AAE3B,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,KAAK;AAAA,EACN;AACD;AA4GA,eAAsB,cAAiC;AACtD,SAAO,MAAM,0BAA0B;AAEvC,QAAM,SAAS,MAAM,iBAA6D;AAAA,IACjF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,CAAC;AAED,SAAO;AAAA,IACN,OAAO,OAAO,MAAM;AAAA,IACpB,MAAM,OAAO;AAAA,EACd;AACD;","names":[]}
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ GitWorktreeManager
4
+ } from "./chunk-QEPVTTHD.js";
5
+ import {
6
+ logger
7
+ } from "./chunk-GEHQXLEI.js";
8
+
9
+ // src/lib/ShellCompletion.ts
10
+ import omelette from "omelette";
11
+ var ShellCompletion = class {
12
+ constructor(commandName) {
13
+ // omelette instance - no types available
14
+ this.COMPLETION_TIMEOUT = 1e3;
15
+ this.commandName = commandName ?? this.detectCommandName();
16
+ this.completion = omelette(`${this.commandName} <command> <arg>`);
17
+ this.setupHandlers();
18
+ }
19
+ detectCommandName() {
20
+ const scriptPath = process.argv[1] ?? "il";
21
+ const baseName = scriptPath.split("/").pop() ?? "il";
22
+ return baseName.replace(/\.js$/, "");
23
+ }
24
+ setupHandlers() {
25
+ this.completion.on("command", ({ reply }) => {
26
+ reply([
27
+ "start",
28
+ "finish",
29
+ "spin",
30
+ "ignite",
31
+ "open",
32
+ "run",
33
+ "cleanup",
34
+ "list",
35
+ "init"
36
+ // Intentionally exclude test-* commands from autocomplete
37
+ ]);
38
+ });
39
+ this.completion.on("arg", async ({ line, reply }) => {
40
+ if (line.includes("cleanup")) {
41
+ const suggestions = await this.getBranchSuggestionsWithTimeout();
42
+ reply(suggestions);
43
+ } else {
44
+ reply([]);
45
+ }
46
+ });
47
+ }
48
+ /**
49
+ * Get branch suggestions with timeout to prevent blocking
50
+ */
51
+ async getBranchSuggestionsWithTimeout() {
52
+ try {
53
+ return await Promise.race([
54
+ this.getBranchSuggestions(),
55
+ this.timeout(this.COMPLETION_TIMEOUT, [])
56
+ ]);
57
+ } catch (error) {
58
+ logger.debug(`Autocomplete branch suggestions failed: ${error}`);
59
+ return [];
60
+ }
61
+ }
62
+ async timeout(ms, defaultValue) {
63
+ return new Promise((resolve) => {
64
+ setTimeout(() => resolve(defaultValue), ms);
65
+ });
66
+ }
67
+ async getBranchSuggestions() {
68
+ try {
69
+ const manager = new GitWorktreeManager();
70
+ const worktrees = await manager.listWorktrees({ porcelain: true });
71
+ const repoInfo = await manager.getRepoInfo();
72
+ const repoRoot = repoInfo.root;
73
+ const currentBranch = repoInfo.currentBranch;
74
+ return worktrees.filter((wt) => wt.path !== repoRoot).filter((wt) => wt.branch !== currentBranch).map((wt) => wt.branch);
75
+ } catch (error) {
76
+ logger.debug(`Failed to get branch suggestions: ${error}`);
77
+ return [];
78
+ }
79
+ }
80
+ /**
81
+ * Initialize completion - must be called before program.parseAsync()
82
+ */
83
+ init() {
84
+ this.completion.init();
85
+ }
86
+ /**
87
+ * Detect user's current shell
88
+ */
89
+ detectShell() {
90
+ const shell = process.env.SHELL ?? "";
91
+ if (shell.includes("bash")) return "bash";
92
+ if (shell.includes("zsh")) return "zsh";
93
+ if (shell.includes("fish")) return "fish";
94
+ return "unknown";
95
+ }
96
+ /**
97
+ * Get completion script for a specific shell
98
+ */
99
+ getCompletionScript(shell) {
100
+ switch (shell) {
101
+ case "bash":
102
+ return this.completion.setupShellInitFile("bash");
103
+ case "zsh":
104
+ return this.completion.setupShellInitFile("zsh");
105
+ case "fish":
106
+ return this.completion.setupShellInitFile("fish");
107
+ default:
108
+ throw new Error(`Unsupported shell type: ${shell}`);
109
+ }
110
+ }
111
+ /**
112
+ * Get setup instructions for manual installation
113
+ */
114
+ getSetupInstructions(shell) {
115
+ const binaryName = this.commandName;
116
+ switch (shell) {
117
+ case "bash":
118
+ return `
119
+ Add the following to your ~/.bashrc or ~/.bash_profile:
120
+
121
+ eval "$(${binaryName} --completion)"
122
+
123
+ Then reload your shell:
124
+
125
+ source ~/.bashrc
126
+ `;
127
+ case "zsh":
128
+ return `
129
+ Add the following to your ~/.zshrc:
130
+
131
+ eval "$(${binaryName} --completion)"
132
+
133
+ Then reload your shell:
134
+
135
+ source ~/.zshrc
136
+ `;
137
+ case "fish":
138
+ return `
139
+ Add the following to your ~/.config/fish/config.fish:
140
+
141
+ ${binaryName} --completion | source
142
+
143
+ Then reload your shell:
144
+
145
+ source ~/.config/fish/config.fish
146
+ `;
147
+ default:
148
+ return `
149
+ Shell autocomplete is supported for bash, zsh, and fish.
150
+ Your current shell (${shell}) may not be supported.
151
+
152
+ Please consult your shell's documentation for setting up custom completions.
153
+ `;
154
+ }
155
+ }
156
+ /**
157
+ * Generate completion script and print to stdout
158
+ * Used by: il --completion
159
+ */
160
+ printCompletionScript(shell) {
161
+ const detectedShell = shell ?? this.detectShell();
162
+ if (detectedShell === "unknown") {
163
+ logger.error("Could not detect shell type. Please specify --shell bash|zsh|fish");
164
+ process.exit(1);
165
+ }
166
+ try {
167
+ const script = this.getCompletionScript(detectedShell);
168
+ console.log(script);
169
+ } catch (error) {
170
+ logger.error(`Failed to generate completion script: ${error}`);
171
+ process.exit(1);
172
+ }
173
+ }
174
+ };
175
+
176
+ export {
177
+ ShellCompletion
178
+ };
179
+ //# sourceMappingURL=chunk-O2QWO64Z.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/ShellCompletion.ts"],"sourcesContent":["import omelette from 'omelette'\nimport { GitWorktreeManager } from './GitWorktreeManager.js'\nimport { logger } from '../utils/logger.js'\n\nexport type ShellType = 'bash' | 'zsh' | 'fish' | 'unknown'\n\n/**\n * Manages shell autocomplete functionality for the iloom CLI\n * Uses omelette to provide tab-completion for commands in bash/zsh/fish\n */\nexport class ShellCompletion {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n private completion: any // omelette instance - no types available\n private readonly COMPLETION_TIMEOUT = 1000 // ms - prevent blocking\n private readonly commandName: string\n\n constructor(commandName?: string) {\n // Detect command name from process.argv[1] if not provided\n this.commandName = commandName ?? this.detectCommandName()\n\n // Initialize omelette with detected command name\n // Template covers: <commandName> <command> <arg>\n // This allows for two-level completion: command completion + argument completion\n this.completion = omelette(`${this.commandName} <command> <arg>`)\n this.setupHandlers()\n }\n\n private detectCommandName(): string {\n // Get the actual command name used to invoke this script\n const scriptPath = process.argv[1] ?? 'il'\n const baseName = scriptPath.split('/').pop() ?? 'il'\n\n // Remove .js extension if present\n return baseName.replace(/\\.js$/, '')\n }\n\n private setupHandlers(): void {\n // Handler for command-level completion\n // When user types: il <TAB>\n this.completion.on('command', ({ reply }: { reply: (suggestions: string[]) => void }) => {\n reply([\n 'start',\n 'finish',\n 'spin',\n 'ignite',\n 'open',\n 'run',\n 'cleanup',\n 'list',\n 'init',\n // Intentionally exclude test-* commands from autocomplete\n ])\n })\n\n // Handler for argument-level completion\n // When user types: il <command> <TAB>\n this.completion.on('arg', async ({ line, reply }: { line: string; reply: (suggestions: string[]) => void }) => {\n // Check if the command is 'cleanup' to provide dynamic branch suggestions\n if (line.includes('cleanup')) {\n // Use timeout to prevent blocking if worktree listing is slow\n const suggestions = await this.getBranchSuggestionsWithTimeout()\n reply(suggestions)\n } else {\n // For other commands, no argument suggestions\n reply([])\n }\n })\n }\n\n /**\n * Get branch suggestions with timeout to prevent blocking\n */\n private async getBranchSuggestionsWithTimeout(): Promise<string[]> {\n try {\n return await Promise.race([\n this.getBranchSuggestions(),\n this.timeout(this.COMPLETION_TIMEOUT, []),\n ])\n } catch (error) {\n logger.debug(`Autocomplete branch suggestions failed: ${error}`)\n return []\n }\n }\n\n private async timeout<T>(ms: number, defaultValue: T): Promise<T> {\n return new Promise((resolve) => {\n // eslint-disable-next-line no-undef\n setTimeout(() => resolve(defaultValue), ms)\n })\n }\n\n async getBranchSuggestions(): Promise<string[]> {\n // Retrieve worktree branches for dynamic completion\n // Used by cleanup command autocomplete\n try {\n const manager = new GitWorktreeManager()\n const worktrees = await manager.listWorktrees({ porcelain: true })\n const repoInfo = await manager.getRepoInfo()\n\n // Filter out:\n // 1. Main worktree (at repo root) - can't be cleaned up\n // 2. Current worktree (where we're working) - shouldn't clean up current location\n const repoRoot = repoInfo.root\n const currentBranch = repoInfo.currentBranch\n\n return worktrees\n .filter((wt) => wt.path !== repoRoot) // Not the main worktree\n .filter((wt) => wt.branch !== currentBranch) // Not current worktree\n .map((wt) => wt.branch)\n } catch (error) {\n // Silently fail - autocomplete should never break the CLI\n logger.debug(`Failed to get branch suggestions: ${error}`)\n return []\n }\n }\n\n /**\n * Initialize completion - must be called before program.parseAsync()\n */\n init(): void {\n this.completion.init()\n }\n\n /**\n * Detect user's current shell\n */\n detectShell(): ShellType {\n const shell = process.env.SHELL ?? ''\n\n if (shell.includes('bash')) return 'bash'\n if (shell.includes('zsh')) return 'zsh'\n if (shell.includes('fish')) return 'fish'\n\n return 'unknown'\n }\n\n /**\n * Get completion script for a specific shell\n */\n getCompletionScript(shell: ShellType): string {\n switch (shell) {\n case 'bash':\n return this.completion.setupShellInitFile('bash')\n case 'zsh':\n return this.completion.setupShellInitFile('zsh')\n case 'fish':\n return this.completion.setupShellInitFile('fish')\n default:\n throw new Error(`Unsupported shell type: ${shell}`)\n }\n }\n\n /**\n * Get setup instructions for manual installation\n */\n getSetupInstructions(shell: ShellType): string {\n const binaryName = this.commandName\n\n switch (shell) {\n case 'bash':\n return `\nAdd the following to your ~/.bashrc or ~/.bash_profile:\n\n eval \"$(${binaryName} --completion)\"\n\nThen reload your shell:\n\n source ~/.bashrc\n`\n case 'zsh':\n return `\nAdd the following to your ~/.zshrc:\n\n eval \"$(${binaryName} --completion)\"\n\nThen reload your shell:\n\n source ~/.zshrc\n`\n case 'fish':\n return `\nAdd the following to your ~/.config/fish/config.fish:\n\n ${binaryName} --completion | source\n\nThen reload your shell:\n\n source ~/.config/fish/config.fish\n`\n default:\n return `\nShell autocomplete is supported for bash, zsh, and fish.\nYour current shell (${shell}) may not be supported.\n\nPlease consult your shell's documentation for setting up custom completions.\n`\n }\n }\n\n /**\n * Generate completion script and print to stdout\n * Used by: il --completion\n */\n printCompletionScript(shell?: ShellType): void {\n const detectedShell = shell ?? this.detectShell()\n\n if (detectedShell === 'unknown') {\n logger.error('Could not detect shell type. Please specify --shell bash|zsh|fish')\n process.exit(1)\n }\n\n try {\n const script = this.getCompletionScript(detectedShell)\n // eslint-disable-next-line no-console\n console.log(script)\n } catch (error) {\n logger.error(`Failed to generate completion script: ${error}`)\n process.exit(1)\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger
4
+ } from "./chunk-GEHQXLEI.js";
5
+
6
+ // src/lib/AgentManager.ts
7
+ import { readFile } from "fs/promises";
8
+ import { accessSync } from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+
12
+ // src/utils/MarkdownAgentParser.ts
13
+ var MarkdownAgentParser = class {
14
+ /**
15
+ * Parse markdown content with YAML frontmatter
16
+ * @param content - Raw markdown file content
17
+ * @returns Object with parsed frontmatter data and markdown body content
18
+ * @throws Error if frontmatter is malformed or missing
19
+ */
20
+ static parse(content) {
21
+ var _a, _b;
22
+ const lines = content.split("\n");
23
+ if (((_a = lines[0]) == null ? void 0 : _a.trim()) !== "---") {
24
+ throw new Error("Missing opening frontmatter delimiter (---)");
25
+ }
26
+ let closingDelimiterIndex = -1;
27
+ for (let i = 1; i < lines.length; i++) {
28
+ if (((_b = lines[i]) == null ? void 0 : _b.trim()) === "---") {
29
+ closingDelimiterIndex = i;
30
+ break;
31
+ }
32
+ }
33
+ if (closingDelimiterIndex === -1) {
34
+ throw new Error("Missing closing frontmatter delimiter (---)");
35
+ }
36
+ const frontmatterLines = lines.slice(1, closingDelimiterIndex);
37
+ const bodyLines = lines.slice(closingDelimiterIndex + 1);
38
+ const markdownBody = bodyLines.join("\n");
39
+ const data = this.parseYaml(frontmatterLines.join("\n"));
40
+ return {
41
+ data,
42
+ content: markdownBody
43
+ };
44
+ }
45
+ /**
46
+ * Parse simplified YAML into key-value object
47
+ * Supports:
48
+ * - Simple key: value pairs
49
+ * - Multiline values with | indicator
50
+ * - Values with special characters and newlines
51
+ *
52
+ * @param yaml - YAML string to parse
53
+ * @returns Object with parsed key-value pairs
54
+ */
55
+ static parseYaml(yaml) {
56
+ const result = {};
57
+ const lines = yaml.split("\n");
58
+ let currentKey = null;
59
+ let currentValue = [];
60
+ let isMultiline = false;
61
+ const finalizeCurrent = () => {
62
+ if (currentKey && currentValue.length > 0) {
63
+ result[currentKey] = currentValue.join("\n").trim();
64
+ currentKey = null;
65
+ currentValue = [];
66
+ }
67
+ };
68
+ for (const line of lines) {
69
+ if (!isMultiline && line.trim() === "") {
70
+ continue;
71
+ }
72
+ const keyValueMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
73
+ if (keyValueMatch && !isMultiline) {
74
+ finalizeCurrent();
75
+ const [, key, value] = keyValueMatch;
76
+ if (!key || value === void 0) {
77
+ continue;
78
+ }
79
+ currentKey = key;
80
+ if (value.trim() === "|") {
81
+ isMultiline = true;
82
+ currentValue = [];
83
+ } else {
84
+ currentValue = [value];
85
+ finalizeCurrent();
86
+ isMultiline = false;
87
+ }
88
+ } else if (isMultiline && currentKey) {
89
+ if (line.match(/^[a-zA-Z_][a-zA-Z0-9_-]*\s*:/) && !line.startsWith(" ")) {
90
+ finalizeCurrent();
91
+ isMultiline = false;
92
+ const match = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
93
+ if (match) {
94
+ const [, key, value] = match;
95
+ if (key && value !== void 0) {
96
+ currentKey = key;
97
+ currentValue = [value];
98
+ finalizeCurrent();
99
+ }
100
+ }
101
+ } else {
102
+ const trimmedLine = line.replace(/^ {2}/, "");
103
+ currentValue.push(trimmedLine);
104
+ }
105
+ }
106
+ }
107
+ finalizeCurrent();
108
+ return result;
109
+ }
110
+ };
111
+
112
+ // src/lib/AgentManager.ts
113
+ var AgentManager = class {
114
+ constructor(agentDir) {
115
+ if (agentDir) {
116
+ this.agentDir = agentDir;
117
+ } else {
118
+ const currentFileUrl = import.meta.url;
119
+ const currentFilePath = fileURLToPath(currentFileUrl);
120
+ const distDir = path.dirname(currentFilePath);
121
+ let agentDirPath = path.join(distDir, "agents");
122
+ let currentDir = distDir;
123
+ while (currentDir !== path.dirname(currentDir)) {
124
+ const candidatePath = path.join(currentDir, "agents");
125
+ try {
126
+ accessSync(candidatePath);
127
+ agentDirPath = candidatePath;
128
+ break;
129
+ } catch {
130
+ currentDir = path.dirname(currentDir);
131
+ }
132
+ }
133
+ this.agentDir = agentDirPath;
134
+ logger.debug("AgentManager initialized", { agentDir: this.agentDir });
135
+ }
136
+ }
137
+ /**
138
+ * Load all agent configuration files from markdown (.md) format
139
+ * Optionally apply model overrides from settings
140
+ * Throws error if agents directory doesn't exist or files are malformed
141
+ * @param settings - Optional project settings with per-agent model overrides
142
+ */
143
+ async loadAgents(settings) {
144
+ const { readdir } = await import("fs/promises");
145
+ const files = await readdir(this.agentDir);
146
+ const agentFiles = files.filter((file) => file.endsWith(".md"));
147
+ const agents = {};
148
+ for (const filename of agentFiles) {
149
+ const agentPath = path.join(this.agentDir, filename);
150
+ try {
151
+ const content = await readFile(agentPath, "utf-8");
152
+ const parsed = this.parseMarkdownAgent(content, filename);
153
+ const agentConfig = parsed.config;
154
+ const agentName = parsed.name;
155
+ this.validateAgentConfig(agentConfig, agentName);
156
+ agents[agentName] = agentConfig;
157
+ logger.debug(`Loaded agent: ${agentName}`);
158
+ } catch (error) {
159
+ logger.error(`Failed to load agent from ${filename}`, { error });
160
+ throw new Error(
161
+ `Failed to load agent from ${filename}: ${error instanceof Error ? error.message : "Unknown error"}`
162
+ );
163
+ }
164
+ }
165
+ if (settings == null ? void 0 : settings.agents) {
166
+ for (const [agentName, agentSettings] of Object.entries(settings.agents)) {
167
+ if (agents[agentName] && agentSettings.model) {
168
+ logger.debug(`Overriding model for ${agentName}: ${agents[agentName].model} -> ${agentSettings.model}`);
169
+ agents[agentName] = {
170
+ ...agents[agentName],
171
+ model: agentSettings.model
172
+ };
173
+ } else if (!agents[agentName]) {
174
+ logger.warn(`Settings reference unknown agent: ${agentName}`);
175
+ }
176
+ }
177
+ }
178
+ return agents;
179
+ }
180
+ /**
181
+ * Validate agent configuration has required fields
182
+ */
183
+ validateAgentConfig(config, agentName) {
184
+ const requiredFields = ["description", "prompt", "tools", "model"];
185
+ for (const field of requiredFields) {
186
+ if (!config[field]) {
187
+ throw new Error(`Agent ${agentName} missing required field: ${field}`);
188
+ }
189
+ }
190
+ if (!Array.isArray(config.tools)) {
191
+ throw new Error(`Agent ${agentName} tools must be an array`);
192
+ }
193
+ }
194
+ /**
195
+ * Parse markdown agent file with YAML frontmatter
196
+ * @param content - Raw markdown file content
197
+ * @param filename - Original filename for error messages
198
+ * @returns Parsed agent config and name
199
+ */
200
+ parseMarkdownAgent(content, filename) {
201
+ try {
202
+ const { data, content: markdownBody } = MarkdownAgentParser.parse(content);
203
+ if (!data.name) {
204
+ throw new Error("Missing required field: name");
205
+ }
206
+ if (!data.description) {
207
+ throw new Error("Missing required field: description");
208
+ }
209
+ if (!data.tools) {
210
+ throw new Error("Missing required field: tools");
211
+ }
212
+ if (!data.model) {
213
+ throw new Error("Missing required field: model");
214
+ }
215
+ const tools = data.tools.split(",").map((tool) => tool.trim()).filter((tool) => tool.length > 0);
216
+ const validModels = ["sonnet", "opus", "haiku"];
217
+ if (!validModels.includes(data.model)) {
218
+ logger.warn(
219
+ `Agent ${data.name} uses model "${data.model}" which may not be recognized by Claude CLI, and your workflow may fail or produce unexpected results. Valid values are: ${validModels.join(", ")}`
220
+ );
221
+ }
222
+ const config = {
223
+ description: data.description,
224
+ prompt: markdownBody.trim(),
225
+ tools,
226
+ model: data.model,
227
+ ...data.color && { color: data.color }
228
+ };
229
+ return { config, name: data.name };
230
+ } catch (error) {
231
+ throw new Error(
232
+ `Failed to parse markdown agent ${filename}: ${error instanceof Error ? error.message : "Unknown error"}`
233
+ );
234
+ }
235
+ }
236
+ /**
237
+ * Format loaded agents for Claude CLI --agents flag
238
+ * Returns object suitable for JSON.stringify
239
+ */
240
+ formatForCli(agents) {
241
+ return agents;
242
+ }
243
+ };
244
+
245
+ export {
246
+ AgentManager
247
+ };
248
+ //# sourceMappingURL=chunk-OC4H6HJD.js.map