@iloom/cli 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -7
- package/dist/ClaudeContextManager-LD3VB6EM.js +13 -0
- package/dist/ClaudeService-CFFI7DD5.js +12 -0
- package/dist/{GitHubService-F7Z3XJOS.js → GitHubService-SH4H6VS5.js} +3 -3
- package/dist/{LoomLauncher-MODG2SEM.js → LoomLauncher-FB2MV2ZI.js} +7 -7
- package/dist/{PromptTemplateManager-7FINLRDE.js → PromptTemplateManager-WM5GIPEF.js} +2 -2
- package/dist/{SettingsManager-VAZF26S2.js → SettingsManager-SKLUVE3K.js} +6 -2
- package/dist/{add-issue-22JBNOML.js → add-issue-L5HX6LEV.js} +23 -8
- package/dist/add-issue-L5HX6LEV.js.map +1 -0
- package/dist/{chunk-U3WU5OWO.js → chunk-4XIDC3NF.js} +2 -2
- package/dist/chunk-4XIDC3NF.js.map +1 -0
- package/dist/{chunk-SSR5AVRJ.js → chunk-6OTVPRXH.js} +21 -8
- package/dist/chunk-6OTVPRXH.js.map +1 -0
- package/dist/{chunk-KQDEK2ZW.js → chunk-DGEKUT7Q.js} +9 -5
- package/dist/chunk-DGEKUT7Q.js.map +1 -0
- package/dist/chunk-FXV24OYZ.js +83 -0
- package/dist/chunk-FXV24OYZ.js.map +1 -0
- package/dist/{chunk-HPJJSYNS.js → chunk-H5LDRGVK.js} +6 -8
- package/dist/{chunk-HPJJSYNS.js.map → chunk-H5LDRGVK.js.map} +1 -1
- package/dist/{chunk-WKEWRSDB.js → chunk-HURVAQRK.js} +3 -3
- package/dist/{chunk-T7QPXANZ.js → chunk-IIPTBZQW.js} +17 -17
- package/dist/chunk-IIPTBZQW.js.map +1 -0
- package/dist/{chunk-QEPVTTHD.js → chunk-IO4WFTL2.js} +17 -11
- package/dist/chunk-IO4WFTL2.js.map +1 -0
- package/dist/{chunk-JQ7VOSTC.js → chunk-KOCQAD2E.js} +3 -3
- package/dist/{chunk-F3XBU2R7.js → chunk-L4QGC27H.js} +68 -2
- package/dist/chunk-L4QGC27H.js.map +1 -0
- package/dist/{chunk-YYSKGAZT.js → chunk-LAPY6NAE.js} +17 -8
- package/dist/chunk-LAPY6NAE.js.map +1 -0
- package/dist/{chunk-O2QWO64Z.js → chunk-PV3GAXQO.js} +56 -3
- package/dist/chunk-PV3GAXQO.js.map +1 -0
- package/dist/{chunk-CP2NU2JC.js → chunk-Q2KYPAH2.js} +7 -7
- package/dist/{chunk-CP2NU2JC.js.map → chunk-Q2KYPAH2.js.map} +1 -1
- package/dist/{chunk-Y7SAGNUT.js → chunk-SLIMABFA.js} +2 -2
- package/dist/{chunk-W3DQTW63.js → chunk-USVVV3FP.js} +4 -4
- package/dist/chunk-VVH3ANF2.js +307 -0
- package/dist/chunk-VVH3ANF2.js.map +1 -0
- package/dist/{chunk-JBH2ZYYZ.js → chunk-VYQLLHZ7.js} +22 -3
- package/dist/chunk-VYQLLHZ7.js.map +1 -0
- package/dist/{chunk-SJUQ2NDR.js → chunk-ZMNQBJUI.js} +24 -19
- package/dist/chunk-ZMNQBJUI.js.map +1 -0
- package/dist/{cleanup-3LUWPSM7.js → cleanup-ZHROIBSQ.js} +12 -16
- package/dist/cleanup-ZHROIBSQ.js.map +1 -0
- package/dist/cli.js +97 -49
- package/dist/cli.js.map +1 -1
- package/dist/{enhance-XJIQHVPD.js → enhance-VVMAKMVZ.js} +18 -8
- package/dist/enhance-VVMAKMVZ.js.map +1 -0
- package/dist/{feedback-23CLXKFT.js → feedback-AKHD7QIM.js} +8 -8
- package/dist/{finish-CY4CIH6O.js → finish-WGPISUEH.js} +60 -313
- package/dist/finish-WGPISUEH.js.map +1 -0
- package/dist/{git-LVRZ57GJ.js → git-OUYMVYJX.js} +2 -2
- package/dist/{ignite-WXEF2ID5.js → ignite-JEN3K3OT.js} +7 -7
- package/dist/index.d.ts +791 -712
- package/dist/index.js +126 -32
- package/dist/index.js.map +1 -1
- package/dist/init-EVUT4ZQJ.js +339 -0
- package/dist/init-EVUT4ZQJ.js.map +1 -0
- package/dist/mcp/github-comment-server.js +12 -9
- package/dist/mcp/github-comment-server.js.map +1 -1
- package/dist/neon-helpers-ZVIRPKCI.js +10 -0
- package/dist/{open-X6BTENPV.js → open-ETZUFSE4.js} +15 -17
- package/dist/{open-X6BTENPV.js.map → open-ETZUFSE4.js.map} +1 -1
- package/dist/prompts/init-prompt.txt +746 -0
- package/dist/prompts/issue-prompt.txt +48 -5
- package/dist/prompts/pr-prompt.txt +46 -1
- package/dist/prompts/regular-prompt.txt +22 -0
- package/dist/rebase-KBWFDZCN.js +95 -0
- package/dist/rebase-KBWFDZCN.js.map +1 -0
- package/dist/remote-GJEZWRCC.js +14 -0
- package/dist/{run-2JCPQAX3.js → run-4SVQ3WEU.js} +15 -17
- package/dist/{run-2JCPQAX3.js.map → run-4SVQ3WEU.js.map} +1 -1
- package/dist/schema/settings.schema.json +51 -1
- package/dist/{start-LWVRBJ6S.js → start-2NEZU7SE.js} +54 -53
- package/dist/{start-LWVRBJ6S.js.map → start-2NEZU7SE.js.map} +1 -1
- package/dist/{test-git-XPF4SZXJ.js → test-git-MKZATGZN.js} +3 -3
- package/dist/{test-prefix-XGFXFAYN.js → test-prefix-ZNLWDI3K.js} +3 -3
- package/dist/{update-3ZT2XX2G.js → update-4TDDUR5K.js} +11 -5
- package/dist/{update-3ZT2XX2G.js.map → update-4TDDUR5K.js.map} +1 -1
- package/dist/{update-notifier-QSSEB5KC.js → update-notifier-QEX3CJHA.js} +2 -2
- package/package.json +1 -1
- package/dist/ClaudeContextManager-XOSXQ67R.js +0 -13
- package/dist/ClaudeService-YSZ6EXWP.js +0 -12
- package/dist/NeonProvider-PAGPUH7F.js +0 -12
- package/dist/add-issue-22JBNOML.js.map +0 -1
- package/dist/chunk-37DYYFVK.js +0 -29
- package/dist/chunk-37DYYFVK.js.map +0 -1
- package/dist/chunk-F3XBU2R7.js.map +0 -1
- package/dist/chunk-JBH2ZYYZ.js.map +0 -1
- package/dist/chunk-KQDEK2ZW.js.map +0 -1
- package/dist/chunk-O2QWO64Z.js.map +0 -1
- package/dist/chunk-QEPVTTHD.js.map +0 -1
- package/dist/chunk-SJUQ2NDR.js.map +0 -1
- package/dist/chunk-SSR5AVRJ.js.map +0 -1
- package/dist/chunk-T7QPXANZ.js.map +0 -1
- package/dist/chunk-U3WU5OWO.js.map +0 -1
- package/dist/chunk-YYSKGAZT.js.map +0 -1
- package/dist/cleanup-3LUWPSM7.js.map +0 -1
- package/dist/enhance-XJIQHVPD.js.map +0 -1
- package/dist/env-MDFL4ZXL.js +0 -23
- package/dist/finish-CY4CIH6O.js.map +0 -1
- package/dist/init-RHACUR4E.js +0 -123
- package/dist/init-RHACUR4E.js.map +0 -1
- /package/dist/{ClaudeContextManager-XOSXQ67R.js.map → ClaudeContextManager-LD3VB6EM.js.map} +0 -0
- /package/dist/{ClaudeService-YSZ6EXWP.js.map → ClaudeService-CFFI7DD5.js.map} +0 -0
- /package/dist/{GitHubService-F7Z3XJOS.js.map → GitHubService-SH4H6VS5.js.map} +0 -0
- /package/dist/{LoomLauncher-MODG2SEM.js.map → LoomLauncher-FB2MV2ZI.js.map} +0 -0
- /package/dist/{NeonProvider-PAGPUH7F.js.map → PromptTemplateManager-WM5GIPEF.js.map} +0 -0
- /package/dist/{PromptTemplateManager-7FINLRDE.js.map → SettingsManager-SKLUVE3K.js.map} +0 -0
- /package/dist/{chunk-WKEWRSDB.js.map → chunk-HURVAQRK.js.map} +0 -0
- /package/dist/{chunk-JQ7VOSTC.js.map → chunk-KOCQAD2E.js.map} +0 -0
- /package/dist/{chunk-Y7SAGNUT.js.map → chunk-SLIMABFA.js.map} +0 -0
- /package/dist/{chunk-W3DQTW63.js.map → chunk-USVVV3FP.js.map} +0 -0
- /package/dist/{feedback-23CLXKFT.js.map → feedback-AKHD7QIM.js.map} +0 -0
- /package/dist/{SettingsManager-VAZF26S2.js.map → git-OUYMVYJX.js.map} +0 -0
- /package/dist/{ignite-WXEF2ID5.js.map → ignite-JEN3K3OT.js.map} +0 -0
- /package/dist/{env-MDFL4ZXL.js.map → neon-helpers-ZVIRPKCI.js.map} +0 -0
- /package/dist/{git-LVRZ57GJ.js.map → remote-GJEZWRCC.js.map} +0 -0
- /package/dist/{test-git-XPF4SZXJ.js.map → test-git-MKZATGZN.js.map} +0 -0
- /package/dist/{test-prefix-XGFXFAYN.js.map → test-prefix-ZNLWDI3K.js.map} +0 -0
- /package/dist/{update-notifier-QSSEB5KC.js.map → update-notifier-QEX3CJHA.js.map} +0 -0
package/README.md
CHANGED
|
@@ -318,6 +318,12 @@ iloom finish
|
|
|
318
318
|
# AI assisted validation, commit, merge steps, as well as loom cleanup (run this from the loom directory)
|
|
319
319
|
# Alias: dn
|
|
320
320
|
|
|
321
|
+
iloom rebase
|
|
322
|
+
# Rebase current branch on main with Claude-assisted conflict resolution (run this from a loom directory)
|
|
323
|
+
# Options:
|
|
324
|
+
# -f, --force - Skip confirmation prompts
|
|
325
|
+
# -n, --dry-run - Preview actions without executing
|
|
326
|
+
|
|
321
327
|
iloom cleanup [identifier...]
|
|
322
328
|
# Remove a loom without merging (safely, by default)
|
|
323
329
|
|
|
@@ -414,6 +420,12 @@ This allows teams to share project defaults via `settings.json` while individual
|
|
|
414
420
|
"web": { "basePort": 3000 },
|
|
415
421
|
"database": { "databaseUrlEnvVarName": "DATABASE_URL" }
|
|
416
422
|
},
|
|
423
|
+
"databaseProviders": {
|
|
424
|
+
"neon": {
|
|
425
|
+
"projectId": "fantastic-fox-3566354",
|
|
426
|
+
"parentBranch": "main"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
417
429
|
"workflows": {
|
|
418
430
|
"issue": {
|
|
419
431
|
"permissionMode": "default",
|
|
@@ -424,23 +436,24 @@ This allows teams to share project defaults via `settings.json` while individual
|
|
|
424
436
|
}
|
|
425
437
|
},
|
|
426
438
|
"agents": {
|
|
427
|
-
"iloom-issue-enhancer": "
|
|
428
|
-
"iloom-issue-analyzer": "
|
|
429
|
-
"iloom-issue-analyze-and-plan":
|
|
430
|
-
"iloom-issue-implementer":
|
|
439
|
+
"iloom-issue-enhancer": {"model":"opus"},
|
|
440
|
+
"iloom-issue-analyzer": {"model":"opus"},
|
|
441
|
+
"iloom-issue-analyze-and-plan": {"model":"opus"},
|
|
442
|
+
"iloom-issue-implementer": {"model":"haiku"}
|
|
431
443
|
}
|
|
432
444
|
}
|
|
433
445
|
```
|
|
434
446
|
|
|
435
|
-
**Note on agent configuration:** All agents use the latest Sonnet model by default.
|
|
447
|
+
**Note on agent configuration:** All agents use the latest Sonnet model by default, except complexity evaluator which uses Haiku. You could also try a some different configurations:
|
|
436
448
|
- **Opus for analysis/enhancement** - Maximum reasoning capability for understanding requirements and planning
|
|
437
449
|
- **Haiku for implementation** - Cost-effective execution of detailed plans (recommended for token-conscious users)
|
|
438
|
-
- Other agents (complexity evaluator, planner, reviewer) remain on Sonnet by default
|
|
439
450
|
|
|
440
|
-
**
|
|
451
|
+
** Common configuration options:**
|
|
441
452
|
- `mainBranch` - Primary branch for merging (default: "main")
|
|
442
453
|
- `capabilities.web.basePort` - Base port for dev servers (default: 3000)
|
|
443
454
|
- `capabilities.database.databaseUrlEnvVarName` - Name of environment variable for database connection URL (default: "DATABASE_URL")
|
|
455
|
+
- `databaseProviders.neon.projectId` - Neon project ID (found in project URL, e.g., "fantastic-fox-3566354")
|
|
456
|
+
- `databaseProviders.neon.parentBranch` - Branch from which new database branches are created (default: "main")
|
|
444
457
|
- `workflows` - Per-workflow Claude CLI permission modes and tool launching
|
|
445
458
|
- `agents` - Claude model selection (sonnet/opus/haiku) per agent type
|
|
446
459
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClaudeContextManager
|
|
4
|
+
} from "./chunk-SLIMABFA.js";
|
|
5
|
+
import "./chunk-HURVAQRK.js";
|
|
6
|
+
import "./chunk-PXZBAC2M.js";
|
|
7
|
+
import "./chunk-L4QGC27H.js";
|
|
8
|
+
import "./chunk-VYQLLHZ7.js";
|
|
9
|
+
import "./chunk-GEHQXLEI.js";
|
|
10
|
+
export {
|
|
11
|
+
ClaudeContextManager
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=ClaudeContextManager-LD3VB6EM.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ClaudeService
|
|
4
|
+
} from "./chunk-HURVAQRK.js";
|
|
5
|
+
import "./chunk-PXZBAC2M.js";
|
|
6
|
+
import "./chunk-L4QGC27H.js";
|
|
7
|
+
import "./chunk-VYQLLHZ7.js";
|
|
8
|
+
import "./chunk-GEHQXLEI.js";
|
|
9
|
+
export {
|
|
10
|
+
ClaudeService
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=ClaudeService-CFFI7DD5.js.map
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
GitHubService
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-IIPTBZQW.js";
|
|
5
|
+
import "./chunk-DGEKUT7Q.js";
|
|
6
6
|
import "./chunk-JNKJ7NJV.js";
|
|
7
7
|
import "./chunk-GEHQXLEI.js";
|
|
8
8
|
export {
|
|
9
9
|
GitHubService
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=GitHubService-
|
|
11
|
+
//# sourceMappingURL=GitHubService-SH4H6VS5.js.map
|
|
@@ -3,6 +3,9 @@ import {
|
|
|
3
3
|
getDevServerLaunchCommand
|
|
4
4
|
} from "./chunk-GZP4UGGM.js";
|
|
5
5
|
import "./chunk-BLCTGFZN.js";
|
|
6
|
+
import {
|
|
7
|
+
ClaudeContextManager
|
|
8
|
+
} from "./chunk-SLIMABFA.js";
|
|
6
9
|
import {
|
|
7
10
|
openMultipleTerminalWindows,
|
|
8
11
|
openTerminalWindow
|
|
@@ -10,13 +13,10 @@ import {
|
|
|
10
13
|
import {
|
|
11
14
|
generateColorFromBranchName
|
|
12
15
|
} from "./chunk-ZZZWQGTS.js";
|
|
13
|
-
import
|
|
14
|
-
ClaudeContextManager
|
|
15
|
-
} from "./chunk-Y7SAGNUT.js";
|
|
16
|
-
import "./chunk-WKEWRSDB.js";
|
|
16
|
+
import "./chunk-HURVAQRK.js";
|
|
17
17
|
import "./chunk-PXZBAC2M.js";
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-L4QGC27H.js";
|
|
19
|
+
import "./chunk-VYQLLHZ7.js";
|
|
20
20
|
import {
|
|
21
21
|
logger
|
|
22
22
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -260,4 +260,4 @@ var LoomLauncher = class {
|
|
|
260
260
|
export {
|
|
261
261
|
LoomLauncher
|
|
262
262
|
};
|
|
263
|
-
//# sourceMappingURL=LoomLauncher-
|
|
263
|
+
//# sourceMappingURL=LoomLauncher-FB2MV2ZI.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PromptTemplateManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-L4QGC27H.js";
|
|
5
5
|
import "./chunk-GEHQXLEI.js";
|
|
6
6
|
export {
|
|
7
7
|
PromptTemplateManager
|
|
8
8
|
};
|
|
9
|
-
//# sourceMappingURL=PromptTemplateManager-
|
|
9
|
+
//# sourceMappingURL=PromptTemplateManager-WM5GIPEF.js.map
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
import {
|
|
3
3
|
AgentSettingsSchema,
|
|
4
4
|
CapabilitiesSettingsSchema,
|
|
5
|
+
DatabaseProvidersSettingsSchema,
|
|
5
6
|
IloomSettingsSchema,
|
|
7
|
+
NeonSettingsSchema,
|
|
6
8
|
SettingsManager,
|
|
7
9
|
WorkflowPermissionSchema,
|
|
8
10
|
WorkflowsSettingsSchema
|
|
9
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
10
12
|
import "./chunk-GEHQXLEI.js";
|
|
11
13
|
export {
|
|
12
14
|
AgentSettingsSchema,
|
|
13
15
|
CapabilitiesSettingsSchema,
|
|
16
|
+
DatabaseProvidersSettingsSchema,
|
|
14
17
|
IloomSettingsSchema,
|
|
18
|
+
NeonSettingsSchema,
|
|
15
19
|
SettingsManager,
|
|
16
20
|
WorkflowPermissionSchema,
|
|
17
21
|
WorkflowsSettingsSchema
|
|
18
22
|
};
|
|
19
|
-
//# sourceMappingURL=SettingsManager-
|
|
23
|
+
//# sourceMappingURL=SettingsManager-SKLUVE3K.js.map
|
|
@@ -6,24 +6,31 @@ import {
|
|
|
6
6
|
AgentManager
|
|
7
7
|
} from "./chunk-OC4H6HJD.js";
|
|
8
8
|
import "./chunk-YETJNRQM.js";
|
|
9
|
+
import {
|
|
10
|
+
getConfiguredRepoFromSettings,
|
|
11
|
+
hasMultipleRemotes
|
|
12
|
+
} from "./chunk-FXV24OYZ.js";
|
|
9
13
|
import {
|
|
10
14
|
GitHubService
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-IIPTBZQW.js";
|
|
16
|
+
import "./chunk-DGEKUT7Q.js";
|
|
13
17
|
import "./chunk-PXZBAC2M.js";
|
|
14
18
|
import {
|
|
15
19
|
SettingsManager
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-VYQLLHZ7.js";
|
|
17
21
|
import "./chunk-JNKJ7NJV.js";
|
|
18
|
-
import
|
|
22
|
+
import {
|
|
23
|
+
logger
|
|
24
|
+
} from "./chunk-GEHQXLEI.js";
|
|
19
25
|
|
|
20
26
|
// src/commands/add-issue.ts
|
|
21
27
|
var AddIssueCommand = class {
|
|
22
|
-
constructor(enhancementService) {
|
|
28
|
+
constructor(enhancementService, settingsManager) {
|
|
29
|
+
this.settingsManager = settingsManager ?? new SettingsManager();
|
|
23
30
|
this.enhancementService = enhancementService ?? new IssueEnhancementService(
|
|
24
31
|
new GitHubService(),
|
|
25
32
|
new AgentManager(),
|
|
26
|
-
|
|
33
|
+
this.settingsManager
|
|
27
34
|
);
|
|
28
35
|
}
|
|
29
36
|
/**
|
|
@@ -36,13 +43,21 @@ var AddIssueCommand = class {
|
|
|
36
43
|
*/
|
|
37
44
|
async execute(input) {
|
|
38
45
|
const { description } = input;
|
|
46
|
+
const settings = await this.settingsManager.loadSettings();
|
|
47
|
+
let repo;
|
|
48
|
+
const multipleRemotes = await hasMultipleRemotes();
|
|
49
|
+
if (multipleRemotes) {
|
|
50
|
+
repo = await getConfiguredRepoFromSettings(settings);
|
|
51
|
+
logger.info(`Using GitHub repository: ${repo}`);
|
|
52
|
+
}
|
|
39
53
|
if (!description || !this.enhancementService.validateDescription(description)) {
|
|
40
54
|
throw new Error("Description is required and must be more than 30 characters with at least 3 words");
|
|
41
55
|
}
|
|
42
56
|
const enhancedDescription = await this.enhancementService.enhanceDescription(description);
|
|
43
57
|
const result = await this.enhancementService.createEnhancedIssue(
|
|
44
58
|
description,
|
|
45
|
-
enhancedDescription
|
|
59
|
+
enhancedDescription,
|
|
60
|
+
repo
|
|
46
61
|
);
|
|
47
62
|
await this.enhancementService.waitForReviewAndOpen(result.number);
|
|
48
63
|
return result.number;
|
|
@@ -51,4 +66,4 @@ var AddIssueCommand = class {
|
|
|
51
66
|
export {
|
|
52
67
|
AddIssueCommand
|
|
53
68
|
};
|
|
54
|
-
//# sourceMappingURL=add-issue-
|
|
69
|
+
//# sourceMappingURL=add-issue-L5HX6LEV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/add-issue.ts"],"sourcesContent":["import type { AddIssueOptions } from '../types/index.js'\nimport { IssueEnhancementService } from '../lib/IssueEnhancementService.js'\nimport { GitHubService } from '../lib/GitHubService.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { getConfiguredRepoFromSettings, hasMultipleRemotes } from '../utils/remote.js'\nimport { logger } from '../utils/logger.js'\n\n/**\n * Input structure for AddIssueCommand\n */\nexport interface AddIssueCommandInput {\n\tdescription: string\n\toptions: AddIssueOptions\n}\n\n/**\n * Command to create and enhance GitHub issues without creating workspaces.\n * This separates the \"document the work\" step from the \"start the work\" step.\n */\nexport class AddIssueCommand {\n\tprivate enhancementService: IssueEnhancementService\n\tprivate settingsManager: SettingsManager\n\n\tconstructor(enhancementService?: IssueEnhancementService, settingsManager?: SettingsManager) {\n\t\t// Use provided service or create default\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.enhancementService = enhancementService ?? new IssueEnhancementService(\n\t\t\tnew GitHubService(),\n\t\t\tnew AgentManager(),\n\t\t\tthis.settingsManager\n\t\t)\n\t}\n\n\t/**\n\t * Execute the add-issue command workflow:\n\t * 1. Validate description format\n\t * 2. Enhance description with Claude AI\n\t * 3. Create GitHub issue\n\t * 4. Wait for keypress and open browser for review\n\t * 5. Return issue number\n\t */\n\tpublic async execute(input: AddIssueCommandInput): Promise<number> {\n\t\tconst { description } = input\n\n\t\t// Step 0: Load settings and get configured repo for GitHub operations\n\t\tconst settings = await this.settingsManager.loadSettings()\n\t\tlet repo: string | undefined\n\n\t\tconst multipleRemotes = await hasMultipleRemotes()\n\t\tif (multipleRemotes) {\n\t\t\trepo = await getConfiguredRepoFromSettings(settings)\n\t\t\tlogger.info(`Using GitHub repository: ${repo}`)\n\t\t}\n\n\t\t// Step 1: Validate description format\n\t\tif (!description || !this.enhancementService.validateDescription(description)) {\n\t\t\tthrow new Error('Description is required and must be more than 30 characters with at least 3 words')\n\t\t}\n\n\t\t// Step 2: Enhance description using Claude AI\n\t\tconst enhancedDescription = await this.enhancementService.enhanceDescription(description)\n\n\t\t// Step 3: Create GitHub issue with original as title, enhanced as body\n\t\tconst result = await this.enhancementService.createEnhancedIssue(\n\t\t\tdescription,\n\t\t\tenhancedDescription,\n\t\t\trepo\n\t\t)\n\n\t\t// Step 4: Wait for keypress and open issue in browser for review\n\t\tawait this.enhancementService.waitForReviewAndOpen(result.number)\n\n\t\t// Step 5: Return issue number for reference\n\t\treturn result.number\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoBO,IAAM,kBAAN,MAAsB;AAAA,EAI5B,YAAY,oBAA8C,iBAAmC;AAE5F,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,qBAAqB,sBAAsB,IAAI;AAAA,MACnD,IAAI,cAAc;AAAA,MAClB,IAAI,aAAa;AAAA,MACjB,KAAK;AAAA,IACN;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,QAAQ,OAA8C;AAClE,UAAM,EAAE,YAAY,IAAI;AAGxB,UAAM,WAAW,MAAM,KAAK,gBAAgB,aAAa;AACzD,QAAI;AAEJ,UAAM,kBAAkB,MAAM,mBAAmB;AACjD,QAAI,iBAAiB;AACpB,aAAO,MAAM,8BAA8B,QAAQ;AACnD,aAAO,KAAK,4BAA4B,IAAI,EAAE;AAAA,IAC/C;AAGA,QAAI,CAAC,eAAe,CAAC,KAAK,mBAAmB,oBAAoB,WAAW,GAAG;AAC9E,YAAM,IAAI,MAAM,mFAAmF;AAAA,IACpG;AAGA,UAAM,sBAAsB,MAAM,KAAK,mBAAmB,mBAAmB,WAAW;AAGxF,UAAM,SAAS,MAAM,KAAK,mBAAmB;AAAA,MAC5C;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAGA,UAAM,KAAK,mBAAmB,qBAAqB,OAAO,MAAM;AAGhE,WAAO,OAAO;AAAA,EACf;AACD;","names":[]}
|
|
@@ -71,7 +71,7 @@ var UpdateNotifier = class {
|
|
|
71
71
|
const content = await fs.readFile(this.cacheFilePath, "utf8");
|
|
72
72
|
logger.debug(`getCachedCheck: Cache file content: ${content}`);
|
|
73
73
|
const cache = JSON.parse(content);
|
|
74
|
-
const cacheTimeoutMins = parseInt(process.env.ILOOM_UPDATE_CACHE_TIMEOUT_MINS ?? "
|
|
74
|
+
const cacheTimeoutMins = parseInt(process.env.ILOOM_UPDATE_CACHE_TIMEOUT_MINS ?? "60", 10);
|
|
75
75
|
const cacheTimeoutMs = cacheTimeoutMins * 60 * 1e3;
|
|
76
76
|
logger.debug(`getCachedCheck: Using cache timeout of ${cacheTimeoutMins} minutes`);
|
|
77
77
|
const now = Date.now();
|
|
@@ -200,4 +200,4 @@ export {
|
|
|
200
200
|
UpdateNotifier,
|
|
201
201
|
checkAndNotifyUpdate
|
|
202
202
|
};
|
|
203
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-4XIDC3NF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/update-notifier.ts"],"sourcesContent":["import os from 'os'\nimport path from 'path'\nimport fs from 'fs-extra'\nimport { execa } from 'execa'\nimport chalk from 'chalk'\nimport { logger } from './logger.js'\n\nexport interface UpdateCheckCache {\n lastCheck: number // timestamp\n latestVersion: string\n}\n\nexport interface UpdateCheckResult {\n currentVersion: string\n latestVersion: string\n updateAvailable: boolean\n}\n\nexport class UpdateNotifier {\n private cacheFilePath: string\n private currentVersion: string\n private packageName: string\n\n constructor(currentVersion: string, packageName: string) {\n this.currentVersion = currentVersion\n this.packageName = packageName\n // Cross-platform cache directory\n const configDir = path.join(os.homedir(), '.config', 'iloom-ai')\n this.cacheFilePath = path.join(configDir, 'update-check.json')\n logger.debug(`UpdateNotifier initialized: version=${currentVersion}, package=${packageName}, cachePath=${this.cacheFilePath}`)\n }\n\n /**\n * Check for updates, respecting 24hr cache\n * Returns UpdateCheckResult or null if check failed/not needed\n */\n async checkForUpdates(): Promise<UpdateCheckResult | null> {\n logger.debug('checkForUpdates: Starting update check')\n try {\n // Check cache first\n logger.debug('checkForUpdates: Checking cache')\n const cached = await this.getCachedCheck()\n if (cached !== null) {\n logger.debug(`checkForUpdates: Using cached result - latest=${cached.latestVersion}, lastCheck=${new Date(cached.lastCheck).toISOString()}`)\n const updateAvailable = this.isNewerVersion(this.currentVersion, cached.latestVersion)\n logger.debug(`checkForUpdates: Update available from cache: ${updateAvailable}`)\n return {\n currentVersion: this.currentVersion,\n latestVersion: cached.latestVersion,\n updateAvailable,\n }\n }\n\n logger.debug('checkForUpdates: No valid cache, querying npm registry')\n // Query npm registry\n const latestVersion = await this.fetchLatestVersion()\n if (latestVersion === null) {\n logger.debug('checkForUpdates: Failed to fetch latest version from npm')\n return null\n }\n\n logger.debug(`checkForUpdates: Fetched latest version: ${latestVersion}`)\n\n // Save to cache\n logger.debug('checkForUpdates: Saving to cache')\n await this.saveCacheFile(latestVersion)\n\n const updateAvailable = this.isNewerVersion(this.currentVersion, latestVersion)\n logger.debug(`checkForUpdates: Update available: ${updateAvailable} (current=${this.currentVersion}, latest=${latestVersion})`)\n\n return {\n currentVersion: this.currentVersion,\n latestVersion,\n updateAvailable,\n }\n } catch (error) {\n // Handle all errors gracefully - update check should never break user experience\n logger.debug(`checkForUpdates: Error during update check: ${error}`)\n return null\n }\n }\n\n /**\n * Read cache file, return null if stale or missing\n */\n private async getCachedCheck(): Promise<UpdateCheckCache | null> {\n logger.debug(`getCachedCheck: Checking cache file at ${this.cacheFilePath}`)\n try {\n if (!fs.existsSync(this.cacheFilePath)) {\n logger.debug('getCachedCheck: Cache file does not exist')\n return null\n }\n\n logger.debug('getCachedCheck: Cache file exists, reading contents')\n const content = await fs.readFile(this.cacheFilePath, 'utf8')\n logger.debug(`getCachedCheck: Cache file content: ${content}`)\n const cache = JSON.parse(content) as UpdateCheckCache\n\n // Check if cache is still fresh (< configurable hours)\n const cacheTimeoutMins = parseInt(process.env.ILOOM_UPDATE_CACHE_TIMEOUT_MINS ?? '60', 10) // Default 1 hour\n const cacheTimeoutMs = cacheTimeoutMins * 60 * 1000\n logger.debug(`getCachedCheck: Using cache timeout of ${cacheTimeoutMins} minutes`)\n const now = Date.now()\n const age = now - cache.lastCheck\n const ageHours = age / (60 * 60 * 1000)\n logger.debug(`getCachedCheck: Cache age: ${ageHours.toFixed(2)} hours (threshold: ${cacheTimeoutMins / 60} hours)`)\n\n if (now - cache.lastCheck < cacheTimeoutMs) {\n logger.debug('getCachedCheck: Cache is fresh, returning cached data')\n return cache\n }\n\n logger.debug('getCachedCheck: Cache is stale, will query npm registry')\n return null\n } catch (error) {\n // If cache is corrupted or unreadable, treat as missing\n logger.debug(`getCachedCheck: Error reading cache: ${error}`)\n return null\n }\n }\n\n /**\n * Save successful check to cache\n */\n private async saveCacheFile(latestVersion: string): Promise<void> {\n logger.debug(`saveCacheFile: Attempting to save cache for version ${latestVersion}`)\n try {\n // Ensure cache directory exists\n const configDir = path.dirname(this.cacheFilePath)\n logger.debug(`saveCacheFile: Ensuring cache directory exists: ${configDir}`)\n await fs.ensureDir(configDir)\n\n // Write cache file\n const cache: UpdateCheckCache = {\n lastCheck: Date.now(),\n latestVersion,\n }\n const cacheJson = JSON.stringify(cache, null, 2)\n logger.debug(`saveCacheFile: Writing cache file: ${cacheJson}`)\n await fs.writeFile(this.cacheFilePath, cacheJson, 'utf8')\n logger.debug(`saveCacheFile: Cache file saved successfully to ${this.cacheFilePath}`)\n } catch (error) {\n // Log debug message but don't throw - cache write failure shouldn't break anything\n logger.debug(`saveCacheFile: Failed to save update check cache to ${this.cacheFilePath}: ${error}`)\n }\n }\n\n /**\n * Display update notification to user\n */\n displayUpdateNotification(result: UpdateCheckResult): void {\n logger.debug(`displayUpdateNotification: updateAvailable=${result.updateAvailable}, current=${result.currentVersion}, latest=${result.latestVersion}`)\n if (result.updateAvailable) {\n logger.debug('displayUpdateNotification: Displaying update notification to user')\n // Simple, clear update notification\n /* eslint-disable no-console */\n console.log('')\n console.log(' ' + chalk.bold(`Update available: ${result.currentVersion} → ${result.latestVersion}`))\n console.log(' ' + chalk.bold('Run: il update'))\n console.log('')\n /* eslint-enable no-console */\n } else {\n logger.debug('displayUpdateNotification: No update available, skipping notification')\n }\n }\n\n /**\n * Query npm registry for latest version\n */\n private async fetchLatestVersion(): Promise<string | null> {\n logger.debug(`fetchLatestVersion: Querying npm for package ${this.packageName}`)\n try {\n const { stdout } = await execa('npm', ['view', this.packageName, 'version'], {\n timeout: 5000,\n })\n const version = stdout.trim()\n logger.debug(`fetchLatestVersion: npm returned version: ${version}`)\n return version\n } catch (error) {\n // Network errors, timeouts, npm not available, or package not found\n logger.debug(`fetchLatestVersion: Failed to query npm: ${error}`)\n return null\n }\n }\n\n /**\n * Compare semver versions\n * Returns true if latest > current\n */\n private isNewerVersion(current: string, latest: string): boolean {\n logger.debug(`isNewerVersion: Comparing versions - current=${current}, latest=${latest}`)\n // Simple version comparison: split by dots and compare numerically\n try {\n const currentParts = current.split('.').map(p => parseInt(p, 10))\n const latestParts = latest.split('.').map(p => parseInt(p, 10))\n logger.debug(`isNewerVersion: Parsed parts - current=[${currentParts.join(', ')}], latest=[${latestParts.join(', ')}]`)\n\n for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {\n const curr = currentParts[i] ?? 0\n const next = latestParts[i] ?? 0\n\n logger.debug(`isNewerVersion: Comparing part ${i}: current=${curr}, latest=${next}`)\n\n if (next > curr) {\n logger.debug(`isNewerVersion: Latest is newer (${next} > ${curr})`)\n return true\n }\n if (next < curr) {\n logger.debug(`isNewerVersion: Current is newer (${curr} > ${next})`)\n return false\n }\n }\n\n logger.debug('isNewerVersion: Versions are equal')\n return false\n } catch (error) {\n // If parsing fails, assume no update\n logger.debug(`isNewerVersion: Error comparing versions: ${error}`)\n return false\n }\n }\n}\n\n/**\n * Main entry point for update check\n * Call from CLI postAction hook\n */\nexport async function checkAndNotifyUpdate(\n currentVersion: string,\n packageName: string,\n installMethod: string\n): Promise<void> {\n logger.debug(`checkAndNotifyUpdate: Called with version=${currentVersion}, package=${packageName}, installMethod=${installMethod}`)\n try {\n // Only check for global installations\n if (installMethod !== 'global') {\n logger.debug(`checkAndNotifyUpdate: Skipping update check - not a global installation (method=${installMethod})`)\n return\n }\n\n logger.debug('checkAndNotifyUpdate: Creating UpdateNotifier instance')\n const notifier = new UpdateNotifier(currentVersion, packageName)\n\n logger.debug('checkAndNotifyUpdate: Calling checkForUpdates()')\n const result = await notifier.checkForUpdates()\n\n if (result !== null) {\n logger.debug(`checkAndNotifyUpdate: Got result, calling displayUpdateNotification`)\n notifier.displayUpdateNotification(result)\n } else {\n logger.debug('checkAndNotifyUpdate: Result was null, not displaying notification')\n }\n\n logger.debug('checkAndNotifyUpdate: Completed')\n } catch (error) {\n // All errors handled internally - this should never throw\n logger.debug(`checkAndNotifyUpdate: Unexpected error: ${error}`)\n }\n}\n"],"mappings":"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","names":["updateAvailable"]}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getRepoInfo
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DGEKUT7Q.js";
|
|
5
5
|
import {
|
|
6
6
|
logger
|
|
7
7
|
} from "./chunk-GEHQXLEI.js";
|
|
8
8
|
|
|
9
9
|
// src/utils/mcp.ts
|
|
10
10
|
import path from "path";
|
|
11
|
-
async function generateGitHubCommentMcpConfig(contextType) {
|
|
12
|
-
|
|
11
|
+
async function generateGitHubCommentMcpConfig(contextType, repo) {
|
|
12
|
+
let owner;
|
|
13
|
+
let name;
|
|
14
|
+
if (repo) {
|
|
15
|
+
const parts = repo.split("/");
|
|
16
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
17
|
+
throw new Error(`Invalid repo format: ${repo}. Expected "owner/repo"`);
|
|
18
|
+
}
|
|
19
|
+
owner = parts[0];
|
|
20
|
+
name = parts[1];
|
|
21
|
+
} else {
|
|
22
|
+
const repoInfo = await getRepoInfo();
|
|
23
|
+
owner = repoInfo.owner;
|
|
24
|
+
name = repoInfo.name;
|
|
25
|
+
}
|
|
13
26
|
const githubEventName = contextType === "issue" ? "issues" : contextType === "pr" ? "pull_request" : void 0;
|
|
14
27
|
const mcpServerConfig = {
|
|
15
28
|
mcpServers: {
|
|
@@ -18,8 +31,8 @@ async function generateGitHubCommentMcpConfig(contextType) {
|
|
|
18
31
|
command: "node",
|
|
19
32
|
args: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), "../dist/mcp/github-comment-server.js")],
|
|
20
33
|
env: {
|
|
21
|
-
REPO_OWNER:
|
|
22
|
-
REPO_NAME:
|
|
34
|
+
REPO_OWNER: owner,
|
|
35
|
+
REPO_NAME: name,
|
|
23
36
|
GITHUB_API_URL: "https://api.github.com/",
|
|
24
37
|
...githubEventName && { GITHUB_EVENT_NAME: githubEventName }
|
|
25
38
|
}
|
|
@@ -27,8 +40,8 @@ async function generateGitHubCommentMcpConfig(contextType) {
|
|
|
27
40
|
}
|
|
28
41
|
};
|
|
29
42
|
logger.debug("Generated MCP config for GitHub comment broker", {
|
|
30
|
-
repoOwner:
|
|
31
|
-
repoName:
|
|
43
|
+
repoOwner: owner,
|
|
44
|
+
repoName: name,
|
|
32
45
|
contextType: contextType ?? "auto-detect",
|
|
33
46
|
githubEventName: githubEventName ?? "auto-detect"
|
|
34
47
|
});
|
|
@@ -38,4 +51,4 @@ async function generateGitHubCommentMcpConfig(contextType) {
|
|
|
38
51
|
export {
|
|
39
52
|
generateGitHubCommentMcpConfig
|
|
40
53
|
};
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-6OTVPRXH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/mcp.ts"],"sourcesContent":["import path from 'path'\nimport { getRepoInfo } from './github.js'\nimport { logger } from './logger.js'\n\n/**\n * Generate MCP configuration for GitHub comment broker\n * Uses a single server that can handle both issues and pull requests\n * Returns array of MCP server config objects\n * @param contextType - Optional context type (issue or pr)\n * @param repo - Optional repo in \"owner/repo\" format. If not provided, will auto-detect from git.\n */\nexport async function generateGitHubCommentMcpConfig(\n\tcontextType?: 'issue' | 'pr',\n\trepo?: string\n): Promise<Record<string, unknown>[]> {\n\t// Get repository information - either from provided repo string or auto-detect\n\tlet owner: string\n\tlet name: string\n\n\tif (repo) {\n\t\tconst parts = repo.split('/')\n\t\tif (parts.length !== 2 || !parts[0] || !parts[1]) {\n\t\t\tthrow new Error(`Invalid repo format: ${repo}. Expected \"owner/repo\"`)\n\t\t}\n\t\towner = parts[0]\n\t\tname = parts[1]\n\t} else {\n\t\tconst repoInfo = await getRepoInfo()\n\t\towner = repoInfo.owner\n\t\tname = repoInfo.name\n\t}\n\n\t// Map logical types to GitHub's webhook event names (handle GitHub's naming quirk here)\n\tconst githubEventName = contextType === 'issue' ? 'issues' : contextType === 'pr' ? 'pull_request' : undefined\n\n\t// Generate single MCP server config\n\tconst mcpServerConfig = {\n\t\tmcpServers: {\n\t\t\tgithub_comment: {\n\t\t\t\ttransport: 'stdio',\n\t\t\t\tcommand: 'node',\n\t\t\t\targs: [path.join(path.dirname(new globalThis.URL(import.meta.url).pathname), '../dist/mcp/github-comment-server.js')],\n\t\t\t\tenv: {\n\t\t\t\t\tREPO_OWNER: owner,\n\t\t\t\t\tREPO_NAME: name,\n\t\t\t\t\tGITHUB_API_URL: 'https://api.github.com/',\n\t\t\t\t\t...(githubEventName && { GITHUB_EVENT_NAME: githubEventName }),\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tlogger.debug('Generated MCP config for GitHub comment broker', {\n\t\trepoOwner: owner,\n\t\trepoName: name,\n\t\tcontextType: contextType ?? 'auto-detect',\n\t\tgithubEventName: githubEventName ?? 'auto-detect'\n\t})\n\n\treturn [mcpServerConfig]\n}"],"mappings":";;;;;;;;;AAAA,OAAO,UAAU;AAWjB,eAAsB,+BACrB,aACA,MACqC;AAErC,MAAI;AACJ,MAAI;AAEJ,MAAI,MAAM;AACT,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,MAAM,WAAW,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG;AACjD,YAAM,IAAI,MAAM,wBAAwB,IAAI,yBAAyB;AAAA,IACtE;AACA,YAAQ,MAAM,CAAC;AACf,WAAO,MAAM,CAAC;AAAA,EACf,OAAO;AACN,UAAM,WAAW,MAAM,YAAY;AACnC,YAAQ,SAAS;AACjB,WAAO,SAAS;AAAA,EACjB;AAGA,QAAM,kBAAkB,gBAAgB,UAAU,WAAW,gBAAgB,OAAO,iBAAiB;AAGrG,QAAM,kBAAkB;AAAA,IACvB,YAAY;AAAA,MACX,gBAAgB;AAAA,QACf,WAAW;AAAA,QACX,SAAS;AAAA,QACT,MAAM,CAAC,KAAK,KAAK,KAAK,QAAQ,IAAI,WAAW,IAAI,YAAY,GAAG,EAAE,QAAQ,GAAG,sCAAsC,CAAC;AAAA,QACpH,KAAK;AAAA,UACJ,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,gBAAgB;AAAA,UAChB,GAAI,mBAAmB,EAAE,mBAAmB,gBAAgB;AAAA,QAC7D;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAEA,SAAO,MAAM,kDAAkD;AAAA,IAC9D,WAAW;AAAA,IACX,UAAU;AAAA,IACV,aAAa,eAAe;AAAA,IAC5B,iBAAiB,mBAAmB;AAAA,EACrC,CAAC;AAED,SAAO,CAAC,eAAe;AACxB;","names":[]}
|
|
@@ -52,15 +52,19 @@ async function fetchGhIssue(issueNumber, repo) {
|
|
|
52
52
|
}
|
|
53
53
|
return executeGhCommand(args);
|
|
54
54
|
}
|
|
55
|
-
async function fetchGhPR(prNumber) {
|
|
56
|
-
logger.debug("Fetching GitHub PR", { prNumber });
|
|
57
|
-
|
|
55
|
+
async function fetchGhPR(prNumber, repo) {
|
|
56
|
+
logger.debug("Fetching GitHub PR", { prNumber, repo });
|
|
57
|
+
const args = [
|
|
58
58
|
"pr",
|
|
59
59
|
"view",
|
|
60
60
|
String(prNumber),
|
|
61
61
|
"--json",
|
|
62
62
|
"number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt"
|
|
63
|
-
]
|
|
63
|
+
];
|
|
64
|
+
if (repo) {
|
|
65
|
+
args.push("--repo", repo);
|
|
66
|
+
}
|
|
67
|
+
return executeGhCommand(args);
|
|
64
68
|
}
|
|
65
69
|
async function fetchProjectList(owner) {
|
|
66
70
|
const result = await executeGhCommand([
|
|
@@ -196,4 +200,4 @@ export {
|
|
|
196
200
|
createIssue,
|
|
197
201
|
getRepoInfo
|
|
198
202
|
};
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
203
|
+
//# sourceMappingURL=chunk-DGEKUT7Q.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\trepo?: string\n): Promise<GitHubPullRequest> {\n\tlogger.debug('Fetching GitHub PR', { prNumber, repo })\n\n\tconst args = [\n\t\t'pr',\n\t\t'view',\n\t\tString(prNumber),\n\t\t'--json',\n\t\t'number,title,body,state,headRefName,baseRefName,url,isDraft,mergeable,createdAt,updatedAt',\n\t]\n\n\tif (repo) {\n\t\targs.push('--repo', repo)\n\t}\n\n\treturn executeGhCommand<GitHubPullRequest>(args)\n}\n\n// Project operations\nexport async function fetchProjectList(\n\towner: string\n): Promise<GitHubProject[]> {\n\tconst result = await executeGhCommand<{ projects: GitHubProject[] }>([\n\t\t'project',\n\t\t'list',\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'100',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.projects ?? []\n}\n\nexport async function fetchProjectItems(\n\tprojectNumber: number,\n\towner: string\n): Promise<ProjectItem[]> {\n\tconst result = await executeGhCommand<{ items: ProjectItem[] }>([\n\t\t'project',\n\t\t'item-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--limit',\n\t\t'10000',\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result?.items ?? []\n}\n\nexport async function fetchProjectFields(\n\tprojectNumber: number,\n\towner: string\n): Promise<{ fields: ProjectField[] }> {\n\tconst result = await executeGhCommand<{ fields: ProjectField[] }>([\n\t\t'project',\n\t\t'field-list',\n\t\tString(projectNumber),\n\t\t'--owner',\n\t\towner,\n\t\t'--format',\n\t\t'json',\n\t])\n\n\treturn result ?? { fields: [] }\n}\n\nexport async function updateProjectItemField(\n\titemId: string,\n\tprojectId: string,\n\tfieldId: string,\n\toptionId: string\n): Promise<void> {\n\tawait executeGhCommand([\n\t\t'project',\n\t\t'item-edit',\n\t\t'--id',\n\t\titemId,\n\t\t'--project-id',\n\t\tprojectId,\n\t\t'--field-id',\n\t\tfieldId,\n\t\t'--single-select-option-id',\n\t\toptionId,\n\t\t'--format',\n\t\t'json',\n\t])\n}\n\n// Branch name generation strategies\nexport class SimpleBranchNameStrategy implements BranchNameStrategy {\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Create a simple slug from the title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 20) // Keep it short for the simple strategy\n\n\t\treturn `feat/issue-${issueNumber}-${slug}`\n\t}\n}\n\nexport class ClaudeBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private claudeModel = 'haiku') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Import dynamically to avoid circular dependency\n\t\tconst { generateBranchName } = await import('../utils/claude.js')\n\n\t\t// Delegate to the shared implementation\n\t\treturn generateBranchName(title, issueNumber, this.claudeModel)\n\t}\n}\n\n// Template-based strategy for custom patterns\nexport class TemplateBranchNameStrategy implements BranchNameStrategy {\n\tconstructor(private template = '{prefix}/issue-{number}-{slug}') {}\n\n\tasync generate(issueNumber: number, title: string): Promise<string> {\n\t\t// Determine prefix based on title\n\t\tconst prefix = this.determinePrefix(title)\n\n\t\t// Create slug from title\n\t\tconst slug = title\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t\t.replace(/^-|-$/g, '')\n\t\t\t.substring(0, 30)\n\n\t\treturn this.template\n\t\t\t.replace('{prefix}', prefix)\n\t\t\t.replace('{number}', String(issueNumber))\n\t\t\t.replace('{slug}', slug)\n\t}\n\n\tprivate determinePrefix(title: string): string {\n\t\tconst lowerTitle = title.toLowerCase()\n\t\tif (lowerTitle.includes('fix') || lowerTitle.includes('bug')) return 'fix'\n\t\tif (lowerTitle.includes('doc')) return 'docs'\n\t\tif (lowerTitle.includes('test')) return 'test'\n\t\tif (lowerTitle.includes('refactor')) return 'refactor'\n\t\treturn 'feat'\n\t}\n}\n\n// GitHub Issue Operations\n\ninterface IssueCreateResponse {\n\tnumber: number\n\turl: string\n}\n\n/**\n * Create a new GitHub issue\n * @param title - The issue title\n * @param body - The issue body (markdown supported)\n * @param options - Optional configuration\n * @param options.repo - Repository in format \"owner/repo\" (uses current repo if not provided)\n * @param options.labels - Array of label names to add to the issue\n * @returns Issue metadata including number and URL\n */\nexport async function createIssue(\n\ttitle: string,\n\tbody: string,\n\toptions?: { repo?: string | undefined; labels?: string[] | undefined }\n): Promise<IssueCreateResponse> {\n\tconst { repo, labels } = options ?? {}\n\n\tlogger.debug('Creating GitHub issue', { title, repo, labels })\n\n\tconst args = [\n\t\t'issue',\n\t\t'create',\n\t\t'--title',\n\t\ttitle,\n\t\t'--body',\n\t\tbody,\n\t]\n\n\t// Add repo if provided\n\tif (repo) {\n\t\targs.splice(2, 0, '--repo', repo)\n\t}\n\n\t// Add labels if provided\n\tif (labels && labels.length > 0) {\n\t\targs.push('--label', labels.join(','))\n\t}\n\n\tconst execaOptions: { timeout: number; encoding: 'utf8'; cwd?: string } = {\n\t\ttimeout: 30000,\n\t\tencoding: 'utf8',\n\t}\n\n\tif (!repo) {\n\t\texecaOptions.cwd = process.cwd()\n\t}\n\n\tconst result = await execa('gh', args, execaOptions)\n\n\t// Parse the URL from the output (format: \"https://github.com/owner/repo/issues/123\")\n\tconst urlMatch = result.stdout.trim().match(/https:\\/\\/github\\.com\\/[^/]+\\/[^/]+\\/issues\\/(\\d+)/)\n\tif (!urlMatch?.[1]) {\n\t\tthrow new Error(`Failed to parse issue URL from gh output: ${result.stdout}`)\n\t}\n\n\tconst issueNumber = parseInt(urlMatch[1], 10)\n\tconst issueUrl = urlMatch[0]\n\n\treturn {\n\t\tnumber: issueNumber,\n\t\turl: issueUrl,\n\t}\n}\n\n/**\n * @deprecated Use createIssue with options.repo instead\n * Create a new GitHub issue in a specific repository\n * @param title - Issue title\n * @param body - Issue body (markdown)\n * @param repository - Repository in format \"owner/repo\"\n * @param labels - Optional array of label names to add to the issue\n * @returns Issue number and URL\n */\nexport async function createIssueInRepo(\n\ttitle: string,\n\tbody: string,\n\trepository: string,\n\tlabels?: string[]\n): Promise<IssueCreateResponse> {\n\treturn createIssue(title, body, { repo: repository, labels })\n}\n\n// GitHub Comment Operations\n\ninterface CommentResponse {\n\tid: number\n\turl: string\n\tcreated_at?: string\n\tupdated_at?: string\n}\n\ninterface RepoInfo {\n\towner: string\n\tname: string\n}\n\n/**\n * Create a comment on a GitHub issue\n * @param issueNumber - The issue number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createIssueComment(\n\tissueNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating issue comment', { issueNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${issueNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${issueNumber}/comments`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Update an existing GitHub comment\n * @param commentId - The comment ID\n * @param body - The updated comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Updated comment metadata\n */\nexport async function updateIssueComment(\n\tcommentId: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Updating issue comment', { commentId, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/comments/${commentId}`\n\t\t: `repos/:owner/:repo/issues/comments/${commentId}`\n\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-X',\n\t\t'PATCH',\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, updated_at: .updated_at}',\n\t])\n}\n\n/**\n * Create a comment on a GitHub pull request\n * Note: PR comments use the same endpoint as issue comments\n * @param prNumber - The PR number\n * @param body - The comment body (markdown supported)\n * @param repo - Optional repo in \"owner/repo\" format\n * @returns Comment metadata including ID and URL\n */\nexport async function createPRComment(\n\tprNumber: number,\n\tbody: string,\n\trepo?: string\n): Promise<CommentResponse> {\n\tlogger.debug('Creating PR comment', { prNumber, repo })\n\n\tconst apiPath = repo\n\t\t? `repos/${repo}/issues/${prNumber}/comments`\n\t\t: `repos/:owner/:repo/issues/${prNumber}/comments`\n\n\t// PR comments use the issues endpoint\n\treturn executeGhCommand<CommentResponse>([\n\t\t'api',\n\t\tapiPath,\n\t\t'-f',\n\t\t`body=${body}`,\n\t\t'--jq',\n\t\t'{id: .id, url: .html_url, created_at: .created_at}',\n\t])\n}\n\n/**\n * Get repository owner and name from current directory\n * @returns Repository owner and name\n */\nexport async function getRepoInfo(): Promise<RepoInfo> {\n\tlogger.debug('Fetching repository info')\n\n\tconst result = await executeGhCommand<{ owner: { login: string }; name: string }>([\n\t\t'repo',\n\t\t'view',\n\t\t'--json',\n\t\t'owner,name',\n\t])\n\n\treturn {\n\t\towner: result.owner.login,\n\t\tname: result.name,\n\t}\n}"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/utils/remote.ts
|
|
4
|
+
import { execa } from "execa";
|
|
5
|
+
async function parseGitRemotes(cwd) {
|
|
6
|
+
const result = await execa("git", ["remote", "-v"], {
|
|
7
|
+
cwd: cwd ?? process.cwd(),
|
|
8
|
+
encoding: "utf8"
|
|
9
|
+
});
|
|
10
|
+
const lines = result.stdout.trim().split("\n");
|
|
11
|
+
const remoteMap = /* @__PURE__ */ new Map();
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+\((fetch|push)\)/);
|
|
14
|
+
if (!match) continue;
|
|
15
|
+
const name = match[1];
|
|
16
|
+
const url = match[2];
|
|
17
|
+
if (!name || !url) continue;
|
|
18
|
+
if (remoteMap.has(name)) continue;
|
|
19
|
+
const ownerRepo = extractOwnerRepoFromUrl(url);
|
|
20
|
+
if (!ownerRepo) continue;
|
|
21
|
+
remoteMap.set(name, {
|
|
22
|
+
name,
|
|
23
|
+
url,
|
|
24
|
+
owner: ownerRepo.owner,
|
|
25
|
+
repo: ownerRepo.repo
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return Array.from(remoteMap.values());
|
|
29
|
+
}
|
|
30
|
+
function extractOwnerRepoFromUrl(url) {
|
|
31
|
+
const cleanUrl = url.replace(/\.git$/, "");
|
|
32
|
+
const httpsMatch = cleanUrl.match(/https?:\/\/github\.com\/([^/]+)\/([^/]+)/);
|
|
33
|
+
if ((httpsMatch == null ? void 0 : httpsMatch[1]) && (httpsMatch == null ? void 0 : httpsMatch[2])) {
|
|
34
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] };
|
|
35
|
+
}
|
|
36
|
+
const sshMatch = cleanUrl.match(/git@github\.com:([^/]+)\/(.+)/);
|
|
37
|
+
if ((sshMatch == null ? void 0 : sshMatch[1]) && (sshMatch == null ? void 0 : sshMatch[2])) {
|
|
38
|
+
return { owner: sshMatch[1], repo: sshMatch[2] };
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async function hasMultipleRemotes(cwd) {
|
|
43
|
+
const remotes = await parseGitRemotes(cwd);
|
|
44
|
+
return remotes.length > 1;
|
|
45
|
+
}
|
|
46
|
+
async function getConfiguredRepoFromSettings(settings, cwd) {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
const remoteName = (_b = (_a = settings.issueManagement) == null ? void 0 : _a.github) == null ? void 0 : _b.remote;
|
|
49
|
+
if (!remoteName) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
'GitHub remote not configured. Run "il init" to configure which repository to use for GitHub operations.'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
await validateConfiguredRemote(remoteName, cwd);
|
|
55
|
+
const remotes = await parseGitRemotes(cwd);
|
|
56
|
+
const remote = remotes.find((r) => r.name === remoteName);
|
|
57
|
+
if (!remote) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Configured remote "${remoteName}" not found in git remotes. Run "il init" to reconfigure.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return `${remote.owner}/${remote.repo}`;
|
|
63
|
+
}
|
|
64
|
+
async function validateConfiguredRemote(remoteName, cwd) {
|
|
65
|
+
try {
|
|
66
|
+
await execa("git", ["remote", "get-url", remoteName], {
|
|
67
|
+
cwd: cwd ?? process.cwd(),
|
|
68
|
+
encoding: "utf8"
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Remote "${remoteName}" does not exist in git configuration. Run "il init" to reconfigure.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
parseGitRemotes,
|
|
79
|
+
hasMultipleRemotes,
|
|
80
|
+
getConfiguredRepoFromSettings,
|
|
81
|
+
validateConfiguredRemote
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=chunk-FXV24OYZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/remote.ts"],"sourcesContent":["import { execa } from 'execa'\nimport type { IloomSettings } from '../lib/SettingsManager.js'\n\n/**\n * Represents a parsed git remote\n */\nexport interface GitRemote {\n\tname: string\n\turl: string\n\towner: string\n\trepo: string\n}\n\n/**\n * Parse git remotes from `git remote -v` output\n * Deduplicates fetch/push entries and extracts owner/repo from URLs\n */\nexport async function parseGitRemotes(cwd?: string): Promise<GitRemote[]> {\n\tconst result = await execa('git', ['remote', '-v'], {\n\t\tcwd: cwd ?? process.cwd(),\n\t\tencoding: 'utf8',\n\t})\n\n\tconst lines = result.stdout.trim().split('\\n')\n\tconst remoteMap = new Map<string, GitRemote>()\n\n\tfor (const line of lines) {\n\t\t// Format: \"origin git@github.com:owner/repo.git (fetch)\"\n\t\t// Format: \"origin https://github.com/owner/repo.git (fetch)\"\n\t\tconst match = line.match(/^(\\S+)\\s+(\\S+)\\s+\\((fetch|push)\\)/)\n\t\tif (!match) continue\n\n\t\tconst name = match[1]\n\t\tconst url = match[2]\n\t\tif (!name || !url) continue\n\n\t\t// Skip if we already processed this remote\n\t\tif (remoteMap.has(name)) continue\n\n\t\t// Extract owner/repo from URL\n\t\tconst ownerRepo = extractOwnerRepoFromUrl(url)\n\t\tif (!ownerRepo) continue\n\n\t\tremoteMap.set(name, {\n\t\t\tname,\n\t\t\turl,\n\t\t\towner: ownerRepo.owner,\n\t\t\trepo: ownerRepo.repo,\n\t\t})\n\t}\n\n\treturn Array.from(remoteMap.values())\n}\n\n/**\n * Extract owner and repo from GitHub URL\n * Supports both HTTPS and SSH formats\n */\nfunction extractOwnerRepoFromUrl(url: string): { owner: string; repo: string } | null {\n\t// Remove .git suffix if present\n\tconst cleanUrl = url.replace(/\\.git$/, '')\n\n\t// HTTPS format: https://github.com/owner/repo\n\tconst httpsMatch = cleanUrl.match(/https?:\\/\\/github\\.com\\/([^/]+)\\/([^/]+)/)\n\tif (httpsMatch?.[1] && httpsMatch?.[2]) {\n\t\treturn { owner: httpsMatch[1], repo: httpsMatch[2] }\n\t}\n\n\t// SSH format: git@github.com:owner/repo\n\tconst sshMatch = cleanUrl.match(/git@github\\.com:([^/]+)\\/(.+)/)\n\tif (sshMatch?.[1] && sshMatch?.[2]) {\n\t\treturn { owner: sshMatch[1], repo: sshMatch[2] }\n\t}\n\n\treturn null\n}\n\n/**\n * Check if repository has multiple remotes\n */\nexport async function hasMultipleRemotes(cwd?: string): Promise<boolean> {\n\tconst remotes = await parseGitRemotes(cwd)\n\treturn remotes.length > 1\n}\n\n/**\n * Get configured repository string from settings\n * Returns \"owner/repo\" format for use with gh CLI --repo flag\n * Throws if configured remote not found\n */\nexport async function getConfiguredRepoFromSettings(\n\tsettings: IloomSettings,\n\tcwd?: string,\n): Promise<string> {\n\tconst remoteName = settings.issueManagement?.github?.remote\n\n\tif (!remoteName) {\n\t\tthrow new Error(\n\t\t\t'GitHub remote not configured. Run \"il init\" to configure which repository to use for GitHub operations.',\n\t\t)\n\t}\n\n\t// Validate configured remote exists\n\tawait validateConfiguredRemote(remoteName, cwd)\n\n\t// Parse remotes and find the configured one\n\tconst remotes = await parseGitRemotes(cwd)\n\tconst remote = remotes.find((r) => r.name === remoteName)\n\n\tif (!remote) {\n\t\tthrow new Error(\n\t\t\t`Configured remote \"${remoteName}\" not found in git remotes. Run \"il init\" to reconfigure.`,\n\t\t)\n\t}\n\n\treturn `${remote.owner}/${remote.repo}`\n}\n\n/**\n * Validate that a remote exists in git config\n * Throws if remote doesn't exist\n */\nexport async function validateConfiguredRemote(remoteName: string, cwd?: string): Promise<void> {\n\ttry {\n\t\tawait execa('git', ['remote', 'get-url', remoteName], {\n\t\t\tcwd: cwd ?? process.cwd(),\n\t\t\tencoding: 'utf8',\n\t\t})\n\t} catch {\n\t\tthrow new Error(\n\t\t\t`Remote \"${remoteName}\" does not exist in git configuration. Run \"il init\" to reconfigure.`,\n\t\t)\n\t}\n}\n"],"mappings":";;;AAAA,SAAS,aAAa;AAiBtB,eAAsB,gBAAgB,KAAoC;AACzE,QAAM,SAAS,MAAM,MAAM,OAAO,CAAC,UAAU,IAAI,GAAG;AAAA,IACnD,KAAK,OAAO,QAAQ,IAAI;AAAA,IACxB,UAAU;AAAA,EACX,CAAC;AAED,QAAM,QAAQ,OAAO,OAAO,KAAK,EAAE,MAAM,IAAI;AAC7C,QAAM,YAAY,oBAAI,IAAuB;AAE7C,aAAW,QAAQ,OAAO;AAGzB,UAAM,QAAQ,KAAK,MAAM,mCAAmC;AAC5D,QAAI,CAAC,MAAO;AAEZ,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,MAAM,MAAM,CAAC;AACnB,QAAI,CAAC,QAAQ,CAAC,IAAK;AAGnB,QAAI,UAAU,IAAI,IAAI,EAAG;AAGzB,UAAM,YAAY,wBAAwB,GAAG;AAC7C,QAAI,CAAC,UAAW;AAEhB,cAAU,IAAI,MAAM;AAAA,MACnB;AAAA,MACA;AAAA,MACA,OAAO,UAAU;AAAA,MACjB,MAAM,UAAU;AAAA,IACjB,CAAC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AACrC;AAMA,SAAS,wBAAwB,KAAqD;AAErF,QAAM,WAAW,IAAI,QAAQ,UAAU,EAAE;AAGzC,QAAM,aAAa,SAAS,MAAM,0CAA0C;AAC5E,OAAI,yCAAa,QAAM,yCAAa,KAAI;AACvC,WAAO,EAAE,OAAO,WAAW,CAAC,GAAG,MAAM,WAAW,CAAC,EAAE;AAAA,EACpD;AAGA,QAAM,WAAW,SAAS,MAAM,+BAA+B;AAC/D,OAAI,qCAAW,QAAM,qCAAW,KAAI;AACnC,WAAO,EAAE,OAAO,SAAS,CAAC,GAAG,MAAM,SAAS,CAAC,EAAE;AAAA,EAChD;AAEA,SAAO;AACR;AAKA,eAAsB,mBAAmB,KAAgC;AACxE,QAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,SAAO,QAAQ,SAAS;AACzB;AAOA,eAAsB,8BACrB,UACA,KACkB;AA7FnB;AA8FC,QAAM,cAAa,oBAAS,oBAAT,mBAA0B,WAA1B,mBAAkC;AAErD,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI;AAAA,MACT;AAAA,IACD;AAAA,EACD;AAGA,QAAM,yBAAyB,YAAY,GAAG;AAG9C,QAAM,UAAU,MAAM,gBAAgB,GAAG;AACzC,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU;AAExD,MAAI,CAAC,QAAQ;AACZ,UAAM,IAAI;AAAA,MACT,sBAAsB,UAAU;AAAA,IACjC;AAAA,EACD;AAEA,SAAO,GAAG,OAAO,KAAK,IAAI,OAAO,IAAI;AACtC;AAMA,eAAsB,yBAAyB,YAAoB,KAA6B;AAC/F,MAAI;AACH,UAAM,MAAM,OAAO,CAAC,UAAU,WAAW,UAAU,GAAG;AAAA,MACrD,KAAK,OAAO,QAAQ,IAAI;AAAA,MACxB,UAAU;AAAA,IACX,CAAC;AAAA,EACF,QAAQ;AACP,UAAM,IAAI;AAAA,MACT,WAAW,UAAU;AAAA,IACtB;AAAA,EACD;AACD;","names":[]}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
calculatePortForBranch
|
|
4
|
-
|
|
3
|
+
calculatePortForBranch,
|
|
4
|
+
formatEnvLine,
|
|
5
|
+
parseEnvFile,
|
|
6
|
+
validateEnvVariable
|
|
7
|
+
} from "./chunk-ZMNQBJUI.js";
|
|
5
8
|
import {
|
|
6
9
|
runScript
|
|
7
10
|
} from "./chunk-BLCTGFZN.js";
|
|
@@ -9,11 +12,6 @@ import {
|
|
|
9
12
|
hasScript,
|
|
10
13
|
readPackageJson
|
|
11
14
|
} from "./chunk-2ZPFJQ3B.js";
|
|
12
|
-
import {
|
|
13
|
-
formatEnvLine,
|
|
14
|
-
parseEnvFile,
|
|
15
|
-
validateEnvVariable
|
|
16
|
-
} from "./chunk-SJUQ2NDR.js";
|
|
17
15
|
import {
|
|
18
16
|
createLogger,
|
|
19
17
|
logger
|
|
@@ -641,4 +639,4 @@ export {
|
|
|
641
639
|
CLIIsolationManager,
|
|
642
640
|
DatabaseManager
|
|
643
641
|
};
|
|
644
|
-
//# sourceMappingURL=chunk-
|
|
642
|
+
//# sourceMappingURL=chunk-H5LDRGVK.js.map
|