@iloom/cli 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +35 -18
- package/dist/{BranchNamingService-B5PVRR7F.js → BranchNamingService-FLPUUFOB.js} +2 -2
- package/dist/ClaudeContextManager-KE5TBZVZ.js +14 -0
- package/dist/ClaudeService-CRSETT3A.js +13 -0
- package/dist/{GitHubService-S2OGUTDR.js → GitHubService-O7U4UQ7N.js} +3 -3
- package/dist/{LoomLauncher-5LFM4LXB.js → LoomLauncher-NL65LSKP.js} +6 -6
- package/dist/{MetadataManager-DFI73J3G.js → MetadataManager-XJ2YB762.js} +2 -2
- package/dist/PRManager-2ABCWXHW.js +16 -0
- package/dist/{ProjectCapabilityDetector-S5FLNCFI.js → ProjectCapabilityDetector-UZYW32SY.js} +3 -3
- package/dist/{PromptTemplateManager-C3DK6XZL.js → PromptTemplateManager-7L3HJQQU.js} +2 -2
- package/dist/README.md +35 -18
- package/dist/{SettingsManager-35F5RUJH.js → SettingsManager-YU4VYPTW.js} +2 -2
- package/dist/agents/iloom-issue-analyze-and-plan.md +42 -17
- package/dist/agents/iloom-issue-analyzer.md +14 -14
- package/dist/agents/iloom-issue-complexity-evaluator.md +38 -15
- package/dist/agents/iloom-issue-enhancer.md +15 -15
- package/dist/agents/iloom-issue-implementer.md +44 -15
- package/dist/agents/iloom-issue-planner.md +121 -17
- package/dist/agents/iloom-issue-reviewer.md +15 -15
- package/dist/{build-FJVYP7EV.js → build-O2EJHDEW.js} +9 -9
- package/dist/{chunk-ZPSTA5PR.js → chunk-3CDWFEGL.js} +2 -2
- package/dist/{chunk-VU3QMIP2.js → chunk-453NC377.js} +91 -15
- package/dist/chunk-453NC377.js.map +1 -0
- package/dist/{chunk-UQIXZ3BA.js → chunk-5V74K5ZA.js} +2 -2
- package/dist/{chunk-7WANFUIK.js → chunk-6TL3BYH6.js} +2 -2
- package/dist/{chunk-5TXLVEXT.js → chunk-C3AKFAIR.js} +2 -2
- package/dist/{chunk-K7SEEHKO.js → chunk-CNSTXBJ3.js} +7 -419
- package/dist/chunk-CNSTXBJ3.js.map +1 -0
- package/dist/{chunk-VDA5JMB4.js → chunk-EPPPDVHD.js} +21 -8
- package/dist/chunk-EPPPDVHD.js.map +1 -0
- package/dist/{chunk-LVBRMTE6.js → chunk-FEAJR6PN.js} +6 -6
- package/dist/{chunk-6YSFTPKW.js → chunk-FM4KBPVA.js} +18 -13
- package/dist/chunk-FM4KBPVA.js.map +1 -0
- package/dist/{chunk-AEIMYF4P.js → chunk-FP7G7DG3.js} +6 -2
- package/dist/chunk-FP7G7DG3.js.map +1 -0
- package/dist/{chunk-LT3SGBR7.js → chunk-GCPAZSGV.js} +36 -2
- package/dist/{chunk-LT3SGBR7.js.map → chunk-GCPAZSGV.js.map} +1 -1
- package/dist/chunk-GJMEKEI5.js +517 -0
- package/dist/chunk-GJMEKEI5.js.map +1 -0
- package/dist/{chunk-64O2UIWO.js → chunk-GV5X6XUE.js} +4 -4
- package/dist/{chunk-7Q66W4OH.js → chunk-HBJITKSZ.js} +37 -1
- package/dist/chunk-HBJITKSZ.js.map +1 -0
- package/dist/{chunk-7HIRPCKU.js → chunk-HVQNVRAF.js} +2 -2
- package/dist/{chunk-BXCPJJYM.js → chunk-ITN64ENQ.js} +1 -1
- package/dist/chunk-ITN64ENQ.js.map +1 -0
- package/dist/{chunk-6U6VI4SZ.js → chunk-KVS4XGBQ.js} +4 -4
- package/dist/{chunk-AXX3QIKK.js → chunk-LLWX3PCW.js} +2 -2
- package/dist/{chunk-2A7WQKBE.js → chunk-LQBLDI47.js} +96 -6
- package/dist/chunk-LQBLDI47.js.map +1 -0
- package/dist/{chunk-SN3Z6EZO.js → chunk-N7FVXZNI.js} +2 -2
- package/dist/chunk-NTIZLX42.js +822 -0
- package/dist/chunk-NTIZLX42.js.map +1 -0
- package/dist/{chunk-I75JMBNB.js → chunk-S7YMZQUD.js} +31 -43
- package/dist/chunk-S7YMZQUD.js.map +1 -0
- package/dist/chunk-TIYJEEVO.js +79 -0
- package/dist/chunk-TIYJEEVO.js.map +1 -0
- package/dist/{chunk-EK3XCAAS.js → chunk-UDRZY65Y.js} +2 -2
- package/dist/{chunk-3PT7RKL5.js → chunk-USJSNHGG.js} +2 -2
- package/dist/{chunk-CFUWQHCJ.js → chunk-VWGKGNJP.js} +114 -35
- package/dist/chunk-VWGKGNJP.js.map +1 -0
- package/dist/{chunk-F6WVM437.js → chunk-WFQ5CLTR.js} +6 -3
- package/dist/chunk-WFQ5CLTR.js.map +1 -0
- package/dist/{chunk-TRQ76ISK.js → chunk-Z6BO53V7.js} +9 -9
- package/dist/{chunk-GEXP5IOF.js → chunk-ZA575VLF.js} +21 -8
- package/dist/chunk-ZA575VLF.js.map +1 -0
- package/dist/{claude-H33OQMXO.js → claude-6H36IBHO.js} +4 -2
- package/dist/{cleanup-BRUAINKE.js → cleanup-ZPOMRSNN.js} +20 -16
- package/dist/cleanup-ZPOMRSNN.js.map +1 -0
- package/dist/cli.js +341 -954
- package/dist/cli.js.map +1 -1
- package/dist/commit-6S2RIA2K.js +237 -0
- package/dist/commit-6S2RIA2K.js.map +1 -0
- package/dist/{compile-ULNO5F7Q.js → compile-LRMAADUT.js} +9 -9
- package/dist/{contribute-Q6GX6AXK.js → contribute-GXKOIA42.js} +5 -5
- package/dist/{dev-server-4RCDJ5MU.js → dev-server-GREJUEKW.js} +22 -74
- package/dist/dev-server-GREJUEKW.js.map +1 -0
- package/dist/{feedback-O4Q55SVS.js → feedback-G7G5QCY4.js} +10 -10
- package/dist/{git-FVMGBHC2.js → git-ENLT2VNI.js} +6 -4
- package/dist/hooks/iloom-hook.js +30 -2
- package/dist/{ignite-VHV65WEZ.js → ignite-YUAOJ5PP.js} +20 -20
- package/dist/ignite-YUAOJ5PP.js.map +1 -0
- package/dist/index.d.ts +71 -27
- package/dist/index.js +196 -266
- package/dist/index.js.map +1 -1
- package/dist/init-XQQMFDM6.js +21 -0
- package/dist/{lint-5JMCWE4Y.js → lint-OFVN7FT6.js} +9 -9
- package/dist/mcp/issue-management-server.js +359 -13
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/mcp/recap-server.js +13 -4
- package/dist/mcp/recap-server.js.map +1 -1
- package/dist/{open-WHVUYGPY.js → open-MCWQAPSZ.js} +25 -76
- package/dist/open-MCWQAPSZ.js.map +1 -0
- package/dist/{projects-SA76I4TZ.js → projects-PQOTWUII.js} +11 -4
- package/dist/projects-PQOTWUII.js.map +1 -0
- package/dist/prompts/init-prompt.txt +62 -51
- package/dist/prompts/issue-prompt.txt +132 -63
- package/dist/prompts/pr-prompt.txt +3 -3
- package/dist/prompts/regular-prompt.txt +16 -18
- package/dist/prompts/session-summary-prompt.txt +13 -13
- package/dist/{rebase-Y4AS6LQW.js → rebase-RKQED567.js} +53 -8
- package/dist/rebase-RKQED567.js.map +1 -0
- package/dist/{recap-VOOUXOGP.js → recap-ZKGHZCX6.js} +6 -6
- package/dist/{run-NCRK5NPR.js → run-CCG24PBC.js} +25 -76
- package/dist/run-CCG24PBC.js.map +1 -0
- package/dist/schema/settings.schema.json +14 -3
- package/dist/{shell-SBLXVOVJ.js → shell-2NNSIU34.js} +6 -6
- package/dist/{summary-CVFAMDOJ.js → summary-G6L3VAKK.js} +11 -10
- package/dist/{summary-CVFAMDOJ.js.map → summary-G6L3VAKK.js.map} +1 -1
- package/dist/{test-3KIVXI6J.js → test-QZDOEUIO.js} +9 -9
- package/dist/{test-git-ZB6AGGRW.js → test-git-E2BLXR6M.js} +4 -4
- package/dist/{test-prefix-FBGXKMPA.js → test-prefix-A7JGGYAA.js} +4 -4
- package/dist/{test-webserver-YVQD42W6.js → test-webserver-NRMGT2HB.js} +29 -8
- package/dist/test-webserver-NRMGT2HB.js.map +1 -0
- package/package.json +3 -1
- package/dist/ClaudeContextManager-6J2EB4QU.js +0 -14
- package/dist/ClaudeService-O2PB22GX.js +0 -13
- package/dist/PRManager-GB3FOJ2W.js +0 -14
- package/dist/chunk-2A7WQKBE.js.map +0 -1
- package/dist/chunk-6YSFTPKW.js.map +0 -1
- package/dist/chunk-7Q66W4OH.js.map +0 -1
- package/dist/chunk-AEIMYF4P.js.map +0 -1
- package/dist/chunk-BXCPJJYM.js.map +0 -1
- package/dist/chunk-CFUWQHCJ.js.map +0 -1
- package/dist/chunk-F6WVM437.js.map +0 -1
- package/dist/chunk-GEXP5IOF.js.map +0 -1
- package/dist/chunk-I75JMBNB.js.map +0 -1
- package/dist/chunk-K7SEEHKO.js.map +0 -1
- package/dist/chunk-VDA5JMB4.js.map +0 -1
- package/dist/chunk-VU3QMIP2.js.map +0 -1
- package/dist/chunk-W6WVRHJ6.js +0 -251
- package/dist/chunk-W6WVRHJ6.js.map +0 -1
- package/dist/cleanup-BRUAINKE.js.map +0 -1
- package/dist/dev-server-4RCDJ5MU.js.map +0 -1
- package/dist/ignite-VHV65WEZ.js.map +0 -1
- package/dist/init-UTYRHNJJ.js +0 -21
- package/dist/open-WHVUYGPY.js.map +0 -1
- package/dist/projects-SA76I4TZ.js.map +0 -1
- package/dist/rebase-Y4AS6LQW.js.map +0 -1
- package/dist/run-NCRK5NPR.js.map +0 -1
- package/dist/test-webserver-YVQD42W6.js.map +0 -1
- /package/dist/{BranchNamingService-B5PVRR7F.js.map → BranchNamingService-FLPUUFOB.js.map} +0 -0
- /package/dist/{ClaudeContextManager-6J2EB4QU.js.map → ClaudeContextManager-KE5TBZVZ.js.map} +0 -0
- /package/dist/{ClaudeService-O2PB22GX.js.map → ClaudeService-CRSETT3A.js.map} +0 -0
- /package/dist/{GitHubService-S2OGUTDR.js.map → GitHubService-O7U4UQ7N.js.map} +0 -0
- /package/dist/{LoomLauncher-5LFM4LXB.js.map → LoomLauncher-NL65LSKP.js.map} +0 -0
- /package/dist/{MetadataManager-DFI73J3G.js.map → MetadataManager-XJ2YB762.js.map} +0 -0
- /package/dist/{PRManager-GB3FOJ2W.js.map → PRManager-2ABCWXHW.js.map} +0 -0
- /package/dist/{ProjectCapabilityDetector-S5FLNCFI.js.map → ProjectCapabilityDetector-UZYW32SY.js.map} +0 -0
- /package/dist/{PromptTemplateManager-C3DK6XZL.js.map → PromptTemplateManager-7L3HJQQU.js.map} +0 -0
- /package/dist/{SettingsManager-35F5RUJH.js.map → SettingsManager-YU4VYPTW.js.map} +0 -0
- /package/dist/{build-FJVYP7EV.js.map → build-O2EJHDEW.js.map} +0 -0
- /package/dist/{chunk-ZPSTA5PR.js.map → chunk-3CDWFEGL.js.map} +0 -0
- /package/dist/{chunk-UQIXZ3BA.js.map → chunk-5V74K5ZA.js.map} +0 -0
- /package/dist/{chunk-7WANFUIK.js.map → chunk-6TL3BYH6.js.map} +0 -0
- /package/dist/{chunk-5TXLVEXT.js.map → chunk-C3AKFAIR.js.map} +0 -0
- /package/dist/{chunk-LVBRMTE6.js.map → chunk-FEAJR6PN.js.map} +0 -0
- /package/dist/{chunk-64O2UIWO.js.map → chunk-GV5X6XUE.js.map} +0 -0
- /package/dist/{chunk-7HIRPCKU.js.map → chunk-HVQNVRAF.js.map} +0 -0
- /package/dist/{chunk-6U6VI4SZ.js.map → chunk-KVS4XGBQ.js.map} +0 -0
- /package/dist/{chunk-AXX3QIKK.js.map → chunk-LLWX3PCW.js.map} +0 -0
- /package/dist/{chunk-SN3Z6EZO.js.map → chunk-N7FVXZNI.js.map} +0 -0
- /package/dist/{chunk-EK3XCAAS.js.map → chunk-UDRZY65Y.js.map} +0 -0
- /package/dist/{chunk-3PT7RKL5.js.map → chunk-USJSNHGG.js.map} +0 -0
- /package/dist/{chunk-TRQ76ISK.js.map → chunk-Z6BO53V7.js.map} +0 -0
- /package/dist/{claude-H33OQMXO.js.map → claude-6H36IBHO.js.map} +0 -0
- /package/dist/{compile-ULNO5F7Q.js.map → compile-LRMAADUT.js.map} +0 -0
- /package/dist/{contribute-Q6GX6AXK.js.map → contribute-GXKOIA42.js.map} +0 -0
- /package/dist/{feedback-O4Q55SVS.js.map → feedback-G7G5QCY4.js.map} +0 -0
- /package/dist/{git-FVMGBHC2.js.map → git-ENLT2VNI.js.map} +0 -0
- /package/dist/{init-UTYRHNJJ.js.map → init-XQQMFDM6.js.map} +0 -0
- /package/dist/{lint-5JMCWE4Y.js.map → lint-OFVN7FT6.js.map} +0 -0
- /package/dist/{recap-VOOUXOGP.js.map → recap-ZKGHZCX6.js.map} +0 -0
- /package/dist/{shell-SBLXVOVJ.js.map → shell-2NNSIU34.js.map} +0 -0
- /package/dist/{test-3KIVXI6J.js.map → test-QZDOEUIO.js.map} +0 -0
- /package/dist/{test-git-ZB6AGGRW.js.map → test-git-E2BLXR6M.js.map} +0 -0
- /package/dist/{test-prefix-FBGXKMPA.js.map → test-prefix-A7JGGYAA.js.map} +0 -0
package/dist/mcp/recap-server.js
CHANGED
|
@@ -116,29 +116,38 @@ server.registerTool(
|
|
|
116
116
|
"add_entry",
|
|
117
117
|
{
|
|
118
118
|
title: "Add Entry",
|
|
119
|
-
description: "Append an entry to the recap",
|
|
119
|
+
description: "Append an entry to the recap. If an entry with the same type and content already exists, it will be skipped.",
|
|
120
120
|
inputSchema: {
|
|
121
121
|
type: z.enum(["decision", "insight", "risk", "assumption", "other"]).describe("Entry type"),
|
|
122
122
|
content: z.string().describe("Entry content")
|
|
123
123
|
},
|
|
124
124
|
outputSchema: {
|
|
125
125
|
id: z.string(),
|
|
126
|
-
timestamp: z.string()
|
|
126
|
+
timestamp: z.string(),
|
|
127
|
+
skipped: z.boolean()
|
|
127
128
|
}
|
|
128
129
|
},
|
|
129
130
|
async ({ type, content }) => {
|
|
130
131
|
const filePath = getRecapFilePath();
|
|
131
132
|
const recap = await readRecapFile(filePath);
|
|
133
|
+
recap.entries ??= [];
|
|
134
|
+
const existingEntry = recap.entries.find((e) => e.type === type && e.content === content);
|
|
135
|
+
if (existingEntry) {
|
|
136
|
+
const result2 = { id: existingEntry.id, timestamp: existingEntry.timestamp, skipped: true };
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: JSON.stringify(result2) }],
|
|
139
|
+
structuredContent: result2
|
|
140
|
+
};
|
|
141
|
+
}
|
|
132
142
|
const entry = {
|
|
133
143
|
id: randomUUID(),
|
|
134
144
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
135
145
|
type,
|
|
136
146
|
content
|
|
137
147
|
};
|
|
138
|
-
recap.entries ??= [];
|
|
139
148
|
recap.entries.push(entry);
|
|
140
149
|
await writeRecapFile(filePath, recap);
|
|
141
|
-
const result = { id: entry.id, timestamp: entry.timestamp };
|
|
150
|
+
const result = { id: entry.id, timestamp: entry.timestamp, skipped: false };
|
|
142
151
|
return {
|
|
143
152
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
144
153
|
structuredContent: result
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/recap-server.ts"],"sourcesContent":["/**\n * Loom Recap MCP Server\n *\n * Captures session context (goal, decisions, insights, risks, assumptions)\n * for the VS Code Loom Context Panel.\n *\n * Environment variables:\n * - RECAP_FILE_PATH: Complete path to the recap.json file (read/write)\n * - LOOM_METADATA_JSON: Stringified JSON of the loom metadata (parsed using LoomMetadata type)\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport path from 'path'\nimport fs from 'fs-extra'\nimport { randomUUID } from 'crypto'\nimport type { RecapFile, RecapEntry, RecapOutput, RecapArtifact } from './recap-types.js'\nimport type { LoomMetadata } from '../lib/MetadataManager.js'\n\ninterface EnvConfig {\n\trecapFilePath: string\n\tloomMetadata: LoomMetadata\n}\n\n// Store validated config for use in tool handlers\nlet validatedRecapFilePath: string | null = null\nlet validatedLoomMetadata: LoomMetadata | null = null\n\n/**\n * Validate required environment variables\n * Exits with error if missing (matches issue-management-server.ts pattern)\n */\nfunction validateEnvironment(): EnvConfig {\n\tconst recapFilePath = process.env.RECAP_FILE_PATH\n\tconst loomMetadataJson = process.env.LOOM_METADATA_JSON\n\n\tif (!recapFilePath) {\n\t\tconsole.error('Missing required environment variable: RECAP_FILE_PATH')\n\t\tprocess.exit(1)\n\t}\n\tif (!loomMetadataJson) {\n\t\tconsole.error('Missing required environment variable: LOOM_METADATA_JSON')\n\t\tprocess.exit(1)\n\t}\n\n\tlet loomMetadata: LoomMetadata\n\ttry {\n\t\tloomMetadata = JSON.parse(loomMetadataJson) as LoomMetadata\n\t} catch (error) {\n\t\tconsole.error('Failed to parse LOOM_METADATA_JSON:', error)\n\t\tprocess.exit(1)\n\t}\n\n\t// Store for tool handlers\n\tvalidatedRecapFilePath = recapFilePath\n\tvalidatedLoomMetadata = loomMetadata\n\n\treturn { recapFilePath, loomMetadata }\n}\n\n/**\n * Get the validated recap file path\n * Throws if called before validateEnvironment()\n */\nfunction getRecapFilePath(): string {\n\tif (!validatedRecapFilePath) {\n\t\tthrow new Error('RECAP_FILE_PATH not validated - validateEnvironment() must be called first')\n\t}\n\treturn validatedRecapFilePath\n}\n\n/**\n * Get the validated loom metadata\n * Throws if called before validateEnvironment()\n */\nexport function getLoomMetadata(): LoomMetadata {\n\tif (!validatedLoomMetadata) {\n\t\tthrow new Error('LOOM_METADATA_JSON not validated - validateEnvironment() must be called first')\n\t}\n\treturn validatedLoomMetadata\n}\n\n/**\n * Read recap file (returns empty object if not found or invalid)\n */\nasync function readRecapFile(filePath: string): Promise<RecapFile> {\n\ttry {\n\t\tif (await fs.pathExists(filePath)) {\n\t\t\tconst content = await fs.readFile(filePath, 'utf8')\n\t\t\treturn JSON.parse(content) as RecapFile\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not read recap file: ${error}`)\n\t}\n\treturn {}\n}\n\n/**\n * Write recap file (ensures parent directory exists)\n */\nasync function writeRecapFile(filePath: string, recap: RecapFile): Promise<void> {\n\tawait fs.ensureDir(path.dirname(filePath), { mode: 0o755 })\n\tawait fs.writeFile(filePath, JSON.stringify(recap, null, 2), { mode: 0o644 })\n}\n\n// Initialize MCP server\nconst server = new McpServer({\n\tname: 'loom-recap',\n\tversion: '0.1.0',\n})\n\n// Register set_goal tool\nserver.registerTool(\n\t'set_goal',\n\t{\n\t\ttitle: 'Set Goal',\n\t\tdescription: 'Set the initial goal (called once at session start)',\n\t\tinputSchema: {\n\t\t\tgoal: z.string().describe('The original problem statement'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tsuccess: z.literal(true),\n\t\t},\n\t},\n\tasync ({ goal }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\trecap.goal = goal\n\t\tawait writeRecapFile(filePath, recap)\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify({ success: true }) }],\n\t\t\tstructuredContent: { success: true },\n\t\t}\n\t}\n)\n\n// Register set_complexity tool\nserver.registerTool(\n\t'set_complexity',\n\t{\n\t\ttitle: 'Set Complexity',\n\t\tdescription: 'Set the assessed complexity of the current task',\n\t\tinputSchema: {\n\t\t\tcomplexity: z.enum(['trivial', 'simple', 'complex']).describe('Task complexity level'),\n\t\t\treason: z.string().optional().describe('Brief explanation for the assessment'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tsuccess: z.literal(true),\n\t\t\ttimestamp: z.string(),\n\t\t},\n\t},\n\tasync ({ complexity, reason }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\tconst timestamp = new Date().toISOString()\n\t\trecap.complexity = reason !== undefined ? { level: complexity, reason, timestamp } : { level: complexity, timestamp }\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { success: true as const, timestamp }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register add_entry tool\nserver.registerTool(\n\t'add_entry',\n\t{\n\t\ttitle: 'Add Entry',\n\t\tdescription: 'Append an entry to the recap',\n\t\tinputSchema: {\n\t\t\ttype: z\n\t\t\t\t.enum(['decision', 'insight', 'risk', 'assumption', 'other'])\n\t\t\t\t.describe('Entry type'),\n\t\t\tcontent: z.string().describe('Entry content'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.string(),\n\t\t\ttimestamp: z.string(),\n\t\t},\n\t},\n\tasync ({ type, content }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\tconst entry: RecapEntry = {\n\t\t\tid: randomUUID(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttype,\n\t\t\tcontent,\n\t\t}\n\t\trecap.entries ??= []\n\t\trecap.entries.push(entry)\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { id: entry.id, timestamp: entry.timestamp }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register add_artifact tool\nserver.registerTool(\n\t'add_artifact',\n\t{\n\t\ttitle: 'Add Artifact',\n\t\tdescription:\n\t\t\t'Track an artifact (comment, issue, PR) created during the session. If an artifact with the same primaryUrl already exists, it will be replaced.',\n\t\tinputSchema: {\n\t\t\ttype: z.enum(['comment', 'issue', 'pr']).describe('Artifact type'),\n\t\t\tprimaryUrl: z.string().url().describe('Main URL for the artifact'),\n\t\t\tdescription: z.string().describe('Brief description of the artifact'),\n\t\t\tid: z.string().optional().describe('Optional artifact ID (e.g., comment ID, issue number)'),\n\t\t\turls: z.record(z.string()).optional().describe('Optional additional URLs (e.g., { api: \"...\" })'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.string(),\n\t\t\ttimestamp: z.string(),\n\t\t\treplaced: z.boolean(),\n\t\t},\n\t},\n\tasync ({ type, primaryUrl, description, id, urls }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\n\t\tconst artifact: RecapArtifact = {\n\t\t\tid: id ?? randomUUID(),\n\t\t\ttype,\n\t\t\tprimaryUrl,\n\t\t\turls: urls ?? {},\n\t\t\tdescription,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t}\n\n\t\trecap.artifacts ??= []\n\n\t\t// Deduplication: replace existing artifact with same primaryUrl\n\t\tconst existingIndex = recap.artifacts.findIndex((a) => a.primaryUrl === primaryUrl)\n\t\tconst replaced = existingIndex !== -1\n\n\t\tif (replaced) {\n\t\t\t// Preserve the original id if not explicitly provided\n\t\t\tconst existingArtifact = recap.artifacts[existingIndex]\n\t\t\tif (existingArtifact) {\n\t\t\t\tartifact.id = id ?? existingArtifact.id\n\t\t\t\trecap.artifacts[existingIndex] = artifact\n\t\t\t}\n\t\t} else {\n\t\t\trecap.artifacts.push(artifact)\n\t\t}\n\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { id: artifact.id, timestamp: artifact.timestamp, replaced }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register get_recap tool\nserver.registerTool(\n\t'get_recap',\n\t{\n\t\ttitle: 'Get Recap',\n\t\tdescription: 'Read current recap (for catching up or review)',\n\t\tinputSchema: {},\n\t\toutputSchema: {\n\t\t\tfilePath: z.string(),\n\t\t\tgoal: z.string().nullable(),\n\t\t\tcomplexity: z\n\t\t\t\t.object({\n\t\t\t\t\tlevel: z.enum(['trivial', 'simple', 'complex']),\n\t\t\t\t\treason: z.string().optional(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t})\n\t\t\t\t.nullable(),\n\t\t\tentries: z.array(\n\t\t\t\tz.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t\ttype: z.enum(['decision', 'insight', 'risk', 'assumption', 'other']),\n\t\t\t\t\tcontent: z.string(),\n\t\t\t\t})\n\t\t\t),\n\t\t\tartifacts: z.array(\n\t\t\t\tz.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\ttype: z.enum(['comment', 'issue', 'pr']),\n\t\t\t\t\tprimaryUrl: z.string(),\n\t\t\t\t\turls: z.record(z.string()),\n\t\t\t\t\tdescription: z.string(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t})\n\t\t\t),\n\t\t},\n\t},\n\tasync () => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\t// Use loom description as default goal for new/missing recap files\n\t\tconst defaultGoal = getLoomMetadata().description || null\n\t\tconst result: RecapOutput = {\n\t\t\tfilePath,\n\t\t\tgoal: recap.goal ?? defaultGoal,\n\t\t\tcomplexity: recap.complexity ?? null,\n\t\t\tentries: recap.entries ?? [],\n\t\t\tartifacts: recap.artifacts ?? [],\n\t\t}\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result as unknown as Record<string, unknown>,\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('Starting Loom Recap MCP Server...')\n\tconst { recapFilePath, loomMetadata } = validateEnvironment()\n\tconsole.error(`Recap file path: ${recapFilePath}`)\n\tconsole.error(`Loom: ${loomMetadata.description} (branch: ${loomMetadata.branchName})`)\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\tconsole.error('Loom Recap MCP Server running on stdio transport')\n}\n\nmain().catch((error) => {\n\tconsole.error('Fatal error starting MCP server:', error)\n\tprocess.exit(1)\n})\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/recap-server.ts"],"sourcesContent":["/**\n * Loom Recap MCP Server\n *\n * Captures session context (goal, decisions, insights, risks, assumptions)\n * for the VS Code Loom Context Panel.\n *\n * Environment variables:\n * - RECAP_FILE_PATH: Complete path to the recap.json file (read/write)\n * - LOOM_METADATA_JSON: Stringified JSON of the loom metadata (parsed using LoomMetadata type)\n */\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { z } from 'zod'\nimport path from 'path'\nimport fs from 'fs-extra'\nimport { randomUUID } from 'crypto'\nimport type { RecapFile, RecapEntry, RecapOutput, RecapArtifact } from './recap-types.js'\nimport type { LoomMetadata } from '../lib/MetadataManager.js'\n\ninterface EnvConfig {\n\trecapFilePath: string\n\tloomMetadata: LoomMetadata\n}\n\n// Store validated config for use in tool handlers\nlet validatedRecapFilePath: string | null = null\nlet validatedLoomMetadata: LoomMetadata | null = null\n\n/**\n * Validate required environment variables\n * Exits with error if missing (matches issue-management-server.ts pattern)\n */\nfunction validateEnvironment(): EnvConfig {\n\tconst recapFilePath = process.env.RECAP_FILE_PATH\n\tconst loomMetadataJson = process.env.LOOM_METADATA_JSON\n\n\tif (!recapFilePath) {\n\t\tconsole.error('Missing required environment variable: RECAP_FILE_PATH')\n\t\tprocess.exit(1)\n\t}\n\tif (!loomMetadataJson) {\n\t\tconsole.error('Missing required environment variable: LOOM_METADATA_JSON')\n\t\tprocess.exit(1)\n\t}\n\n\tlet loomMetadata: LoomMetadata\n\ttry {\n\t\tloomMetadata = JSON.parse(loomMetadataJson) as LoomMetadata\n\t} catch (error) {\n\t\tconsole.error('Failed to parse LOOM_METADATA_JSON:', error)\n\t\tprocess.exit(1)\n\t}\n\n\t// Store for tool handlers\n\tvalidatedRecapFilePath = recapFilePath\n\tvalidatedLoomMetadata = loomMetadata\n\n\treturn { recapFilePath, loomMetadata }\n}\n\n/**\n * Get the validated recap file path\n * Throws if called before validateEnvironment()\n */\nfunction getRecapFilePath(): string {\n\tif (!validatedRecapFilePath) {\n\t\tthrow new Error('RECAP_FILE_PATH not validated - validateEnvironment() must be called first')\n\t}\n\treturn validatedRecapFilePath\n}\n\n/**\n * Get the validated loom metadata\n * Throws if called before validateEnvironment()\n */\nexport function getLoomMetadata(): LoomMetadata {\n\tif (!validatedLoomMetadata) {\n\t\tthrow new Error('LOOM_METADATA_JSON not validated - validateEnvironment() must be called first')\n\t}\n\treturn validatedLoomMetadata\n}\n\n/**\n * Read recap file (returns empty object if not found or invalid)\n */\nasync function readRecapFile(filePath: string): Promise<RecapFile> {\n\ttry {\n\t\tif (await fs.pathExists(filePath)) {\n\t\t\tconst content = await fs.readFile(filePath, 'utf8')\n\t\t\treturn JSON.parse(content) as RecapFile\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(`Warning: Could not read recap file: ${error}`)\n\t}\n\treturn {}\n}\n\n/**\n * Write recap file (ensures parent directory exists)\n */\nasync function writeRecapFile(filePath: string, recap: RecapFile): Promise<void> {\n\tawait fs.ensureDir(path.dirname(filePath), { mode: 0o755 })\n\tawait fs.writeFile(filePath, JSON.stringify(recap, null, 2), { mode: 0o644 })\n}\n\n// Initialize MCP server\nconst server = new McpServer({\n\tname: 'loom-recap',\n\tversion: '0.1.0',\n})\n\n// Register set_goal tool\nserver.registerTool(\n\t'set_goal',\n\t{\n\t\ttitle: 'Set Goal',\n\t\tdescription: 'Set the initial goal (called once at session start)',\n\t\tinputSchema: {\n\t\t\tgoal: z.string().describe('The original problem statement'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tsuccess: z.literal(true),\n\t\t},\n\t},\n\tasync ({ goal }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\trecap.goal = goal\n\t\tawait writeRecapFile(filePath, recap)\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify({ success: true }) }],\n\t\t\tstructuredContent: { success: true },\n\t\t}\n\t}\n)\n\n// Register set_complexity tool\nserver.registerTool(\n\t'set_complexity',\n\t{\n\t\ttitle: 'Set Complexity',\n\t\tdescription: 'Set the assessed complexity of the current task',\n\t\tinputSchema: {\n\t\t\tcomplexity: z.enum(['trivial', 'simple', 'complex']).describe('Task complexity level'),\n\t\t\treason: z.string().optional().describe('Brief explanation for the assessment'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tsuccess: z.literal(true),\n\t\t\ttimestamp: z.string(),\n\t\t},\n\t},\n\tasync ({ complexity, reason }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\tconst timestamp = new Date().toISOString()\n\t\trecap.complexity = reason !== undefined ? { level: complexity, reason, timestamp } : { level: complexity, timestamp }\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { success: true as const, timestamp }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register add_entry tool\nserver.registerTool(\n\t'add_entry',\n\t{\n\t\ttitle: 'Add Entry',\n\t\tdescription:\n\t\t\t'Append an entry to the recap. If an entry with the same type and content already exists, it will be skipped.',\n\t\tinputSchema: {\n\t\t\ttype: z\n\t\t\t\t.enum(['decision', 'insight', 'risk', 'assumption', 'other'])\n\t\t\t\t.describe('Entry type'),\n\t\t\tcontent: z.string().describe('Entry content'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.string(),\n\t\t\ttimestamp: z.string(),\n\t\t\tskipped: z.boolean(),\n\t\t},\n\t},\n\tasync ({ type, content }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\trecap.entries ??= []\n\n\t\t// Deduplication: skip if entry with same type and content exists\n\t\tconst existingEntry = recap.entries.find((e) => e.type === type && e.content === content)\n\n\t\tif (existingEntry) {\n\t\t\tconst result = { id: existingEntry.id, timestamp: existingEntry.timestamp, skipped: true }\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\t\tstructuredContent: result,\n\t\t\t}\n\t\t}\n\n\t\tconst entry: RecapEntry = {\n\t\t\tid: randomUUID(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\ttype,\n\t\t\tcontent,\n\t\t}\n\t\trecap.entries.push(entry)\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { id: entry.id, timestamp: entry.timestamp, skipped: false }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register add_artifact tool\nserver.registerTool(\n\t'add_artifact',\n\t{\n\t\ttitle: 'Add Artifact',\n\t\tdescription:\n\t\t\t'Track an artifact (comment, issue, PR) created during the session. If an artifact with the same primaryUrl already exists, it will be replaced.',\n\t\tinputSchema: {\n\t\t\ttype: z.enum(['comment', 'issue', 'pr']).describe('Artifact type'),\n\t\t\tprimaryUrl: z.string().url().describe('Main URL for the artifact'),\n\t\t\tdescription: z.string().describe('Brief description of the artifact'),\n\t\t\tid: z.string().optional().describe('Optional artifact ID (e.g., comment ID, issue number)'),\n\t\t\turls: z.record(z.string()).optional().describe('Optional additional URLs (e.g., { api: \"...\" })'),\n\t\t},\n\t\toutputSchema: {\n\t\t\tid: z.string(),\n\t\t\ttimestamp: z.string(),\n\t\t\treplaced: z.boolean(),\n\t\t},\n\t},\n\tasync ({ type, primaryUrl, description, id, urls }) => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\n\t\tconst artifact: RecapArtifact = {\n\t\t\tid: id ?? randomUUID(),\n\t\t\ttype,\n\t\t\tprimaryUrl,\n\t\t\turls: urls ?? {},\n\t\t\tdescription,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t}\n\n\t\trecap.artifacts ??= []\n\n\t\t// Deduplication: replace existing artifact with same primaryUrl\n\t\tconst existingIndex = recap.artifacts.findIndex((a) => a.primaryUrl === primaryUrl)\n\t\tconst replaced = existingIndex !== -1\n\n\t\tif (replaced) {\n\t\t\t// Preserve the original id if not explicitly provided\n\t\t\tconst existingArtifact = recap.artifacts[existingIndex]\n\t\t\tif (existingArtifact) {\n\t\t\t\tartifact.id = id ?? existingArtifact.id\n\t\t\t\trecap.artifacts[existingIndex] = artifact\n\t\t\t}\n\t\t} else {\n\t\t\trecap.artifacts.push(artifact)\n\t\t}\n\n\t\tawait writeRecapFile(filePath, recap)\n\t\tconst result = { id: artifact.id, timestamp: artifact.timestamp, replaced }\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result,\n\t\t}\n\t}\n)\n\n// Register get_recap tool\nserver.registerTool(\n\t'get_recap',\n\t{\n\t\ttitle: 'Get Recap',\n\t\tdescription: 'Read current recap (for catching up or review)',\n\t\tinputSchema: {},\n\t\toutputSchema: {\n\t\t\tfilePath: z.string(),\n\t\t\tgoal: z.string().nullable(),\n\t\t\tcomplexity: z\n\t\t\t\t.object({\n\t\t\t\t\tlevel: z.enum(['trivial', 'simple', 'complex']),\n\t\t\t\t\treason: z.string().optional(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t})\n\t\t\t\t.nullable(),\n\t\t\tentries: z.array(\n\t\t\t\tz.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t\ttype: z.enum(['decision', 'insight', 'risk', 'assumption', 'other']),\n\t\t\t\t\tcontent: z.string(),\n\t\t\t\t})\n\t\t\t),\n\t\t\tartifacts: z.array(\n\t\t\t\tz.object({\n\t\t\t\t\tid: z.string(),\n\t\t\t\t\ttype: z.enum(['comment', 'issue', 'pr']),\n\t\t\t\t\tprimaryUrl: z.string(),\n\t\t\t\t\turls: z.record(z.string()),\n\t\t\t\t\tdescription: z.string(),\n\t\t\t\t\ttimestamp: z.string(),\n\t\t\t\t})\n\t\t\t),\n\t\t},\n\t},\n\tasync () => {\n\t\tconst filePath = getRecapFilePath()\n\t\tconst recap = await readRecapFile(filePath)\n\t\t// Use loom description as default goal for new/missing recap files\n\t\tconst defaultGoal = getLoomMetadata().description || null\n\t\tconst result: RecapOutput = {\n\t\t\tfilePath,\n\t\t\tgoal: recap.goal ?? defaultGoal,\n\t\t\tcomplexity: recap.complexity ?? null,\n\t\t\tentries: recap.entries ?? [],\n\t\t\tartifacts: recap.artifacts ?? [],\n\t\t}\n\t\treturn {\n\t\t\tcontent: [{ type: 'text' as const, text: JSON.stringify(result) }],\n\t\t\tstructuredContent: result as unknown as Record<string, unknown>,\n\t\t}\n\t}\n)\n\n// Main server startup\nasync function main(): Promise<void> {\n\tconsole.error('Starting Loom Recap MCP Server...')\n\tconst { recapFilePath, loomMetadata } = validateEnvironment()\n\tconsole.error(`Recap file path: ${recapFilePath}`)\n\tconsole.error(`Loom: ${loomMetadata.description} (branch: ${loomMetadata.branchName})`)\n\tconst transport = new StdioServerTransport()\n\tawait server.connect(transport)\n\tconsole.error('Loom Recap MCP Server running on stdio transport')\n}\n\nmain().catch((error) => {\n\tconsole.error('Fatal error starting MCP server:', error)\n\tprocess.exit(1)\n})\n"],"mappings":"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","names":["result"]}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DevServerManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GV5X6XUE.js";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-AXX3QIKK.js";
|
|
6
|
+
getWorkspacePort
|
|
7
|
+
} from "./chunk-453NC377.js";
|
|
9
8
|
import {
|
|
10
9
|
IdentifierParser
|
|
11
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5V74K5ZA.js";
|
|
12
11
|
import {
|
|
13
12
|
ProjectCapabilityDetector
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
13
|
+
} from "./chunk-3CDWFEGL.js";
|
|
14
|
+
import "./chunk-LLWX3PCW.js";
|
|
16
15
|
import {
|
|
17
16
|
GitWorktreeManager
|
|
18
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-UDRZY65Y.js";
|
|
18
|
+
import "./chunk-ITN64ENQ.js";
|
|
19
19
|
import {
|
|
20
20
|
openBrowser
|
|
21
21
|
} from "./chunk-YETJNRQM.js";
|
|
@@ -24,17 +24,14 @@ import {
|
|
|
24
24
|
} from "./chunk-GYCR2LOU.js";
|
|
25
25
|
import {
|
|
26
26
|
extractIssueNumber
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-ZA575VLF.js";
|
|
28
28
|
import {
|
|
29
29
|
SettingsManager
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-WFQ5CLTR.js";
|
|
31
|
+
import "./chunk-VWGKGNJP.js";
|
|
32
32
|
import "./chunk-6MLEBAYZ.js";
|
|
33
33
|
import {
|
|
34
|
-
|
|
35
|
-
findEnvFileContainingVariable,
|
|
36
|
-
logger,
|
|
37
|
-
parseEnvFile
|
|
34
|
+
logger
|
|
38
35
|
} from "./chunk-VT4PDUYT.js";
|
|
39
36
|
|
|
40
37
|
// src/commands/open.ts
|
|
@@ -57,7 +54,7 @@ var OpenCommand = class {
|
|
|
57
54
|
const { capabilities, binEntries } = await this.capabilityDetector.detectCapabilities(worktree.path);
|
|
58
55
|
logger.debug(`Detected capabilities: ${capabilities.join(", ")}`);
|
|
59
56
|
if (capabilities.includes("web")) {
|
|
60
|
-
await this.openWebBrowser(worktree
|
|
57
|
+
await this.openWebBrowser(worktree);
|
|
61
58
|
} else if (capabilities.includes("cli")) {
|
|
62
59
|
await this.runCLITool(worktree.path, binEntries, input.args ?? []);
|
|
63
60
|
} else {
|
|
@@ -181,10 +178,18 @@ var OpenCommand = class {
|
|
|
181
178
|
* Open web browser with workspace URL
|
|
182
179
|
* Auto-starts dev server if not already running
|
|
183
180
|
*/
|
|
184
|
-
async openWebBrowser(
|
|
185
|
-
|
|
181
|
+
async openWebBrowser(worktree) {
|
|
182
|
+
var _a, _b;
|
|
183
|
+
const cliOverrides = extractSettingsOverrides();
|
|
184
|
+
const settings = await this.settingsManager.loadSettings(void 0, cliOverrides);
|
|
185
|
+
const port = await getWorkspacePort({
|
|
186
|
+
worktreePath: worktree.path,
|
|
187
|
+
worktreeBranch: worktree.branch,
|
|
188
|
+
basePort: (_b = (_a = settings.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort,
|
|
189
|
+
checkEnvFile: true
|
|
190
|
+
});
|
|
186
191
|
const serverReady = await this.devServerManager.ensureServerRunning(
|
|
187
|
-
|
|
192
|
+
worktree.path,
|
|
188
193
|
port
|
|
189
194
|
);
|
|
190
195
|
if (!serverReady) {
|
|
@@ -197,62 +202,6 @@ var OpenCommand = class {
|
|
|
197
202
|
await openBrowser(url);
|
|
198
203
|
logger.success("Browser opened");
|
|
199
204
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Get port for workspace - reads from dotenv-flow files or calculates based on workspace type
|
|
202
|
-
*/
|
|
203
|
-
async getWorkspacePort(worktreePath) {
|
|
204
|
-
var _a, _b;
|
|
205
|
-
const cliOverrides = extractSettingsOverrides();
|
|
206
|
-
const settings = await this.settingsManager.loadSettings(void 0, cliOverrides);
|
|
207
|
-
const basePort = ((_b = (_a = settings.capabilities) == null ? void 0 : _a.web) == null ? void 0 : _b.basePort) ?? 3e3;
|
|
208
|
-
const envFile = await findEnvFileContainingVariable(
|
|
209
|
-
worktreePath,
|
|
210
|
-
"PORT",
|
|
211
|
-
async (p) => fs.pathExists(p),
|
|
212
|
-
async (p, varName) => {
|
|
213
|
-
const content = await fs.readFile(p, "utf8");
|
|
214
|
-
const envMap = parseEnvFile(content);
|
|
215
|
-
return envMap.get(varName) ?? null;
|
|
216
|
-
}
|
|
217
|
-
);
|
|
218
|
-
if (envFile) {
|
|
219
|
-
const envPath = path.join(worktreePath, envFile);
|
|
220
|
-
const envContent = await fs.readFile(envPath, "utf8");
|
|
221
|
-
const envMap = parseEnvFile(envContent);
|
|
222
|
-
const port2 = extractPort(envMap);
|
|
223
|
-
if (port2) {
|
|
224
|
-
logger.debug(`Using PORT from ${envFile}: ${port2}`);
|
|
225
|
-
return port2;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
logger.debug("PORT not found in any dotenv-flow file, calculating from workspace identifier");
|
|
229
|
-
const worktrees = await this.gitWorktreeManager.listWorktrees();
|
|
230
|
-
const worktree = worktrees.find((wt) => wt.path === worktreePath);
|
|
231
|
-
if (!worktree) {
|
|
232
|
-
throw new Error(`Could not find worktree for path: ${worktreePath}`);
|
|
233
|
-
}
|
|
234
|
-
const dirName = path.basename(worktreePath);
|
|
235
|
-
const prPattern = /_pr_(\d+)$/;
|
|
236
|
-
const prMatch = dirName.match(prPattern);
|
|
237
|
-
if (prMatch == null ? void 0 : prMatch[1]) {
|
|
238
|
-
const prNumber = parseInt(prMatch[1], 10);
|
|
239
|
-
const port2 = basePort + prNumber;
|
|
240
|
-
logger.debug(`Calculated PORT for PR #${prNumber}: ${port2}`);
|
|
241
|
-
return port2;
|
|
242
|
-
}
|
|
243
|
-
const issueId = extractIssueNumber(dirName) ?? extractIssueNumber(worktree.branch);
|
|
244
|
-
if (issueId !== null) {
|
|
245
|
-
const issueNumber = parseInt(issueId, 10);
|
|
246
|
-
if (!isNaN(issueNumber)) {
|
|
247
|
-
const port2 = basePort + issueNumber;
|
|
248
|
-
logger.debug(`Calculated PORT for issue #${issueId}: ${port2}`);
|
|
249
|
-
return port2;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const port = calculatePortForBranch(worktree.branch, basePort);
|
|
253
|
-
logger.debug(`Calculated PORT for branch "${worktree.branch}": ${port}`);
|
|
254
|
-
return port;
|
|
255
|
-
}
|
|
256
205
|
/**
|
|
257
206
|
* Run CLI tool directly from worktree bin path (NO SYMLINKS!)
|
|
258
207
|
*/
|
|
@@ -288,4 +237,4 @@ Make sure the project is built (run 'il start' first)`
|
|
|
288
237
|
export {
|
|
289
238
|
OpenCommand
|
|
290
239
|
};
|
|
291
|
-
//# sourceMappingURL=open-
|
|
240
|
+
//# sourceMappingURL=open-MCWQAPSZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/open.ts"],"sourcesContent":["import path from 'path'\nimport fs from 'fs-extra'\nimport { execa } from 'execa'\nimport { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { ProjectCapabilityDetector } from '../lib/ProjectCapabilityDetector.js'\nimport { DevServerManager } from '../lib/DevServerManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { IdentifierParser } from '../utils/IdentifierParser.js'\nimport { openBrowser } from '../utils/browser.js'\nimport { getWorkspacePort } from '../utils/port.js'\nimport { extractIssueNumber } from '../utils/git.js'\nimport { logger } from '../utils/logger.js'\nimport { extractSettingsOverrides } from '../utils/cli-overrides.js'\nimport type { GitWorktree } from '../types/worktree.js'\n\nexport interface OpenCommandInput {\n\tidentifier?: string\n\targs?: string[]\n}\n\ninterface ParsedOpenInput {\n\ttype: 'issue' | 'pr' | 'branch'\n\tnumber?: string | number // For issues and PRs\n\tbranchName?: string // For branches\n\toriginalInput: string\n\tautoDetected: boolean\n}\n\n/**\n * OpenCommand - Opens workspace in browser or runs CLI tool\n * Priority: Web first, CLI fallback\n */\nexport class OpenCommand {\n\tconstructor(\n\t\tprivate gitWorktreeManager = new GitWorktreeManager(),\n\t\tprivate capabilityDetector = new ProjectCapabilityDetector(),\n\t\tprivate identifierParser = new IdentifierParser(new GitWorktreeManager()),\n\t\tprivate devServerManager = new DevServerManager(),\n\t\tprivate settingsManager = new SettingsManager()\n\t) {}\n\n\tasync execute(input: OpenCommandInput): Promise<void> {\n\t\t// 1. Parse or auto-detect identifier\n\t\tconst parsed = input.identifier\n\t\t\t? await this.parseExplicitInput(input.identifier)\n\t\t\t: await this.autoDetectFromCurrentDirectory()\n\n\t\tlogger.debug(`Parsed input: ${JSON.stringify(parsed)}`)\n\n\t\t// 2. Find worktree path based on identifier\n\t\tconst worktree = await this.findWorktreeForIdentifier(parsed)\n\n\t\tlogger.info(`Found worktree at: ${worktree.path}`)\n\n\t\t// 3. Detect project capabilities\n\t\tconst { capabilities, binEntries } =\n\t\t\tawait this.capabilityDetector.detectCapabilities(worktree.path)\n\n\t\tlogger.debug(`Detected capabilities: ${capabilities.join(', ')}`)\n\n\t\t// 4. Execute based on capabilities (web first, CLI fallback)\n\t\tif (capabilities.includes('web')) {\n\t\t\tawait this.openWebBrowser(worktree)\n\t\t} else if (capabilities.includes('cli')) {\n\t\t\tawait this.runCLITool(worktree.path, binEntries, input.args ?? [])\n\t\t} else {\n\t\t\tthrow new Error(\n\t\t\t\t`No web or CLI capabilities detected for workspace at ${worktree.path}`\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Parse explicit identifier input\n\t */\n\tprivate async parseExplicitInput(identifier: string): Promise<ParsedOpenInput> {\n\t\tconst parsed = await this.identifierParser.parseForPatternDetection(identifier)\n\n\t\t// Description type should never reach open command (converted in start)\n\t\tif (parsed.type === 'description') {\n\t\t\tthrow new Error('Description input type is not supported in open command')\n\t\t}\n\n\t\tconst result: ParsedOpenInput = {\n\t\t\ttype: parsed.type,\n\t\t\toriginalInput: parsed.originalInput,\n\t\t\tautoDetected: false,\n\t\t}\n\n\t\tif (parsed.number !== undefined) {\n\t\t\tresult.number = parsed.number\n\t\t}\n\t\tif (parsed.branchName !== undefined) {\n\t\t\tresult.branchName = parsed.branchName\n\t\t}\n\n\t\treturn result\n\t}\n\n\t/**\n\t * Auto-detect identifier from current directory\n\t * Same logic as FinishCommand.autoDetectFromCurrentDirectory()\n\t */\n\tprivate async autoDetectFromCurrentDirectory(): Promise<ParsedOpenInput> {\n\t\tconst currentDir = path.basename(process.cwd())\n\n\t\t// Check for PR worktree pattern: _pr_N suffix\n\t\tconst prPattern = /_pr_(\\d+)$/\n\t\tconst prMatch = currentDir.match(prPattern)\n\n\t\tif (prMatch?.[1]) {\n\t\t\tconst prNumber = parseInt(prMatch[1], 10)\n\t\t\tlogger.debug(`Auto-detected PR #${prNumber} from directory: ${currentDir}`)\n\t\t\treturn {\n\t\t\t\ttype: 'pr',\n\t\t\t\tnumber: prNumber,\n\t\t\t\toriginalInput: currentDir,\n\t\t\t\tautoDetected: true,\n\t\t\t}\n\t\t}\n\n\t\t// Check for issue pattern in directory\n\t\tconst issueNumber = extractIssueNumber(currentDir)\n\n\t\tif (issueNumber !== null) {\n\t\t\tlogger.debug(`Auto-detected issue #${issueNumber} from directory: ${currentDir}`)\n\t\t\treturn {\n\t\t\t\ttype: 'issue',\n\t\t\t\tnumber: issueNumber,\n\t\t\t\toriginalInput: currentDir,\n\t\t\t\tautoDetected: true,\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: get current branch name\n\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\tconst currentBranch = repoInfo.currentBranch\n\n\t\tif (!currentBranch) {\n\t\t\tthrow new Error(\n\t\t\t\t'Could not auto-detect identifier. Please provide an issue number, PR number, or branch name.\\n' +\n\t\t\t\t\t'Expected directory pattern: feat/issue-XX-description OR worktree with _pr_N suffix'\n\t\t\t)\n\t\t}\n\n\t\t// Try to extract issue from branch name\n\t\tconst branchIssueNumber = extractIssueNumber(currentBranch)\n\t\tif (branchIssueNumber !== null) {\n\t\t\tlogger.debug(`Auto-detected issue #${branchIssueNumber} from branch: ${currentBranch}`)\n\t\t\treturn {\n\t\t\t\ttype: 'issue',\n\t\t\t\tnumber: branchIssueNumber,\n\t\t\t\toriginalInput: currentBranch,\n\t\t\t\tautoDetected: true,\n\t\t\t}\n\t\t}\n\n\t\t// Last resort: use branch name\n\t\treturn {\n\t\t\ttype: 'branch',\n\t\t\tbranchName: currentBranch,\n\t\t\toriginalInput: currentBranch,\n\t\t\tautoDetected: true,\n\t\t}\n\t}\n\n\t/**\n\t * Find worktree for the given identifier\n\t */\n\tprivate async findWorktreeForIdentifier(parsed: ParsedOpenInput): Promise<GitWorktree> {\n\t\tlet worktree: GitWorktree | null = null\n\n\t\tif (parsed.type === 'issue' && parsed.number !== undefined) {\n\t\t\tworktree = await this.gitWorktreeManager.findWorktreeForIssue(parsed.number)\n\t\t} else if (parsed.type === 'pr' && parsed.number !== undefined) {\n\t\t\t// For PRs, ensure the number is numeric (PRs are always numeric per GitHub)\n\t\t\tconst prNumber = typeof parsed.number === 'number' ? parsed.number : Number(parsed.number)\n\t\t\tif (isNaN(prNumber) || !isFinite(prNumber)) {\n\t\t\t\tthrow new Error(`Invalid PR number: ${parsed.number}. PR numbers must be numeric.`)\n\t\t\t}\n\t\t\t// Pass empty string for branch name since we don't know it yet\n\t\t\tworktree = await this.gitWorktreeManager.findWorktreeForPR(prNumber, '')\n\t\t} else if (parsed.type === 'branch' && parsed.branchName) {\n\t\t\tworktree = await this.gitWorktreeManager.findWorktreeForBranch(\n\t\t\t\tparsed.branchName\n\t\t\t)\n\t\t}\n\n\t\tif (!worktree) {\n\t\t\tthrow new Error(\n\t\t\t\t`No worktree found for ${this.formatParsedInput(parsed)}. ` +\n\t\t\t\t\t`Run 'il start ${parsed.originalInput}' to create one.`\n\t\t\t)\n\t\t}\n\n\t\treturn worktree\n\t}\n\n\t/**\n\t * Format parsed input for display\n\t */\n\tprivate formatParsedInput(parsed: ParsedOpenInput): string {\n\t\tconst autoLabel = parsed.autoDetected ? ' (auto-detected)' : ''\n\n\t\tif (parsed.type === 'issue') {\n\t\t\treturn `issue #${parsed.number}${autoLabel}`\n\t\t}\n\t\tif (parsed.type === 'pr') {\n\t\t\treturn `PR #${parsed.number}${autoLabel}`\n\t\t}\n\t\treturn `branch \"${parsed.branchName}\"${autoLabel}`\n\t}\n\n\t/**\n\t * Open web browser with workspace URL\n\t * Auto-starts dev server if not already running\n\t */\n\tprivate async openWebBrowser(worktree: GitWorktree): Promise<void> {\n\t\tconst cliOverrides = extractSettingsOverrides()\n\t\tconst settings = await this.settingsManager.loadSettings(undefined, cliOverrides)\n\t\tconst port = await getWorkspacePort({\n\t\t\tworktreePath: worktree.path,\n\t\t\tworktreeBranch: worktree.branch,\n\t\t\tbasePort: settings.capabilities?.web?.basePort,\n\t\t\tcheckEnvFile: true,\n\t\t})\n\n\t\t// Ensure dev server is running on the port\n\t\tconst serverReady = await this.devServerManager.ensureServerRunning(\n\t\t\tworktree.path,\n\t\t\tport\n\t\t)\n\n\t\tif (!serverReady) {\n\t\t\tlogger.warn(\n\t\t\t\t`Dev server failed to start on port ${port}. Opening browser anyway...`\n\t\t\t)\n\t\t}\n\n\t\t// Construct URL and open browser\n\t\tconst url = `http://localhost:${port}`\n\t\tlogger.info(`Opening browser: ${url}`)\n\t\tawait openBrowser(url)\n\t\tlogger.success('Browser opened')\n\t}\n\n\t/**\n\t * Run CLI tool directly from worktree bin path (NO SYMLINKS!)\n\t */\n\tprivate async runCLITool(\n\t\tworktreePath: string,\n\t\tbinEntries: Record<string, string>,\n\t\targs: string[]\n\t): Promise<void> {\n\t\t// Validate binEntries exist\n\t\tif (Object.keys(binEntries).length === 0) {\n\t\t\tthrow new Error('No bin entries found in package.json')\n\t\t}\n\n\t\t// Get first bin entry (deterministic)\n\t\tconst firstEntry = Object.entries(binEntries)[0]\n\t\tif (!firstEntry) {\n\t\t\tthrow new Error('No bin entries found in package.json')\n\t\t}\n\t\tconst [binName, binPath] = firstEntry\n\t\tlogger.debug(`Using bin entry: ${binName} -> ${binPath}`)\n\n\t\t// CRITICAL: Construct absolute path (NO SYMLINKS!)\n\t\tconst binFilePath = path.resolve(worktreePath, binPath)\n\t\tlogger.debug(`Resolved bin file path: ${binFilePath}`)\n\n\t\t// Verify file exists\n\t\tif (!(await fs.pathExists(binFilePath))) {\n\t\t\tthrow new Error(\n\t\t\t\t`CLI executable not found: ${binFilePath}\\n` +\n\t\t\t\t\t`Make sure the project is built (run 'il start' first)`\n\t\t\t)\n\t\t}\n\n\t\t// Execute with Node.js\n\t\tlogger.info(`Running CLI: node ${binFilePath} ${args.join(' ')}`)\n\t\tawait execa('node', [binFilePath, ...args], {\n\t\t\tstdio: 'inherit', // Allow interactive CLIs (prompts, colors, etc.)\n\t\t\tcwd: worktreePath, // Execute in worktree context\n\t\t\tenv: process.env, // Inherit environment\n\t\t})\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ProjectCapabilityDetector
|
|
4
|
+
} from "./chunk-3CDWFEGL.js";
|
|
5
|
+
import "./chunk-ITN64ENQ.js";
|
|
2
6
|
import {
|
|
3
7
|
MetadataManager
|
|
4
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VWGKGNJP.js";
|
|
5
9
|
import {
|
|
6
10
|
getLogger
|
|
7
11
|
} from "./chunk-6MLEBAYZ.js";
|
|
@@ -12,9 +16,10 @@ import path from "path";
|
|
|
12
16
|
import os from "os";
|
|
13
17
|
import fs from "fs-extra";
|
|
14
18
|
var ProjectsCommand = class {
|
|
15
|
-
constructor(metadataManager) {
|
|
19
|
+
constructor(metadataManager, capabilityDetector) {
|
|
16
20
|
this.projectsDir = path.join(os.homedir(), ".config", "iloom-ai", "projects");
|
|
17
21
|
this.metadataManager = metadataManager ?? new MetadataManager();
|
|
22
|
+
this.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector();
|
|
18
23
|
}
|
|
19
24
|
/**
|
|
20
25
|
* Execute the projects command
|
|
@@ -39,11 +44,13 @@ var ProjectsCommand = class {
|
|
|
39
44
|
if (!marker.projectPath || !marker.projectName) continue;
|
|
40
45
|
if (!await fs.pathExists(marker.projectPath)) continue;
|
|
41
46
|
const activeLooms = await this.countActiveLooms(marker.projectPath, allMetadata);
|
|
47
|
+
const { capabilities } = await this.capabilityDetector.detectCapabilities(marker.projectPath);
|
|
42
48
|
results.push({
|
|
43
49
|
configuredAt: marker.configuredAt,
|
|
44
50
|
projectPath: marker.projectPath,
|
|
45
51
|
projectName: marker.projectName,
|
|
46
|
-
activeLooms
|
|
52
|
+
activeLooms,
|
|
53
|
+
capabilities
|
|
47
54
|
});
|
|
48
55
|
} catch {
|
|
49
56
|
logger.debug(`Skipping invalid project file: ${file}`);
|
|
@@ -77,4 +84,4 @@ var ProjectsCommand = class {
|
|
|
77
84
|
export {
|
|
78
85
|
ProjectsCommand
|
|
79
86
|
};
|
|
80
|
-
//# sourceMappingURL=projects-
|
|
87
|
+
//# sourceMappingURL=projects-PQOTWUII.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/projects.ts"],"sourcesContent":["import path from 'path'\nimport os from 'os'\nimport fs from 'fs-extra'\nimport { getLogger } from '../utils/logger-context.js'\nimport { MetadataManager, type LoomMetadata } from '../lib/MetadataManager.js'\nimport { ProjectCapabilityDetector } from '../lib/ProjectCapabilityDetector.js'\nimport type { ProjectCapability } from '../types/loom.js'\n\n/**\n * Project marker file structure (from FirstRunManager)\n */\ninterface ProjectMarker {\n configuredAt: string\n projectPath: string\n projectName: string\n}\n\n/**\n * Output schema for project\n */\nexport interface ProjectOutput {\n configuredAt: string\n projectPath: string\n projectName: string\n activeLooms: number\n capabilities: ProjectCapability[]\n}\n\n/**\n * ProjectsCommand: List configured iloom projects\n *\n * Returns JSON array of configured projects from ~/.config/iloom-ai/projects/\n * Only includes projects where the directory still exists.\n * Each project includes an activeLooms count.\n */\nexport class ProjectsCommand {\n private readonly projectsDir: string\n private readonly metadataManager: MetadataManager\n private readonly capabilityDetector: ProjectCapabilityDetector\n\n constructor(metadataManager?: MetadataManager, capabilityDetector?: ProjectCapabilityDetector) {\n this.projectsDir = path.join(os.homedir(), '.config', 'iloom-ai', 'projects')\n this.metadataManager = metadataManager ?? new MetadataManager()\n this.capabilityDetector = capabilityDetector ?? new ProjectCapabilityDetector()\n }\n\n /**\n * Execute the projects command\n * @param _options - Options object (json flag accepted but ignored - always returns JSON)\n * @returns Array of project outputs\n */\n async execute(_options?: { json?: boolean }): Promise<ProjectOutput[]> {\n // Options.json is accepted but ignored - always returns structured data\n const results: ProjectOutput[] = []\n const logger = getLogger()\n\n try {\n // Check if projects directory exists\n if (!(await fs.pathExists(this.projectsDir))) {\n return results\n }\n\n // Read all files in projects directory\n const files = await fs.readdir(this.projectsDir)\n\n // Get all loom metadata for active looms lookup\n const allMetadata = await this.metadataManager.listAllMetadata()\n\n for (const file of files) {\n // Skip hidden files (like .DS_Store)\n if (file.startsWith('.')) continue\n\n try {\n const filePath = path.join(this.projectsDir, file)\n const content = await fs.readFile(filePath, 'utf8')\n const marker: ProjectMarker = JSON.parse(content)\n\n // Skip if required fields missing\n if (!marker.projectPath || !marker.projectName) continue\n\n // Filter: only include if directory exists\n if (!(await fs.pathExists(marker.projectPath))) continue\n\n // Count active looms for this project\n const activeLooms = await this.countActiveLooms(marker.projectPath, allMetadata)\n\n // Detect project capabilities\n const { capabilities } = await this.capabilityDetector.detectCapabilities(marker.projectPath)\n\n results.push({\n configuredAt: marker.configuredAt,\n projectPath: marker.projectPath,\n projectName: marker.projectName,\n activeLooms,\n capabilities,\n })\n } catch {\n // Skip invalid files (graceful degradation)\n logger.debug(`Skipping invalid project file: ${file}`)\n }\n }\n } catch (error) {\n // Graceful degradation on read errors\n logger.debug(`Failed to list projects: ${error instanceof Error ? error.message : 'Unknown'}`)\n }\n\n return results\n }\n\n /**\n * Count active looms for a project\n * Looms are counted if their worktreePath is in the project's -looms directory\n * or if they share the same parent directory as the project.\n * Only counts looms where the worktree is a valid git worktree (.git exists).\n */\n private async countActiveLooms(projectPath: string, allMetadata: LoomMetadata[]): Promise<number> {\n let count = 0\n for (const meta of allMetadata) {\n if (!meta.worktreePath) continue\n const parentDir = path.dirname(meta.worktreePath)\n const isProjectLoom =\n parentDir === path.dirname(projectPath) ||\n parentDir.startsWith(projectPath + '-looms')\n // Check for .git (file for worktrees, directory for main repo) to verify it's a valid git worktree\n const isValidWorktree = await fs.pathExists(path.join(meta.worktreePath, '.git'))\n if (isProjectLoom && isValidWorktree) {\n count++\n }\n }\n return count\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA,OAAO,UAAU;AACjB,OAAO,QAAQ;AACf,OAAO,QAAQ;AAiCR,IAAM,kBAAN,MAAsB;AAAA,EAK3B,YAAY,iBAAmC,oBAAgD;AAC7F,SAAK,cAAc,KAAK,KAAK,GAAG,QAAQ,GAAG,WAAW,YAAY,UAAU;AAC5E,SAAK,kBAAkB,mBAAmB,IAAI,gBAAgB;AAC9D,SAAK,qBAAqB,sBAAsB,IAAI,0BAA0B;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,UAAyD;AAErE,UAAM,UAA2B,CAAC;AAClC,UAAM,SAAS,UAAU;AAEzB,QAAI;AAEF,UAAI,CAAE,MAAM,GAAG,WAAW,KAAK,WAAW,GAAI;AAC5C,eAAO;AAAA,MACT;AAGA,YAAM,QAAQ,MAAM,GAAG,QAAQ,KAAK,WAAW;AAG/C,YAAM,cAAc,MAAM,KAAK,gBAAgB,gBAAgB;AAE/D,iBAAW,QAAQ,OAAO;AAExB,YAAI,KAAK,WAAW,GAAG,EAAG;AAE1B,YAAI;AACF,gBAAM,WAAW,KAAK,KAAK,KAAK,aAAa,IAAI;AACjD,gBAAM,UAAU,MAAM,GAAG,SAAS,UAAU,MAAM;AAClD,gBAAM,SAAwB,KAAK,MAAM,OAAO;AAGhD,cAAI,CAAC,OAAO,eAAe,CAAC,OAAO,YAAa;AAGhD,cAAI,CAAE,MAAM,GAAG,WAAW,OAAO,WAAW,EAAI;AAGhD,gBAAM,cAAc,MAAM,KAAK,iBAAiB,OAAO,aAAa,WAAW;AAG/E,gBAAM,EAAE,aAAa,IAAI,MAAM,KAAK,mBAAmB,mBAAmB,OAAO,WAAW;AAE5F,kBAAQ,KAAK;AAAA,YACX,cAAc,OAAO;AAAA,YACrB,aAAa,OAAO;AAAA,YACpB,aAAa,OAAO;AAAA,YACpB;AAAA,YACA;AAAA,UACF,CAAC;AAAA,QACH,QAAQ;AAEN,iBAAO,MAAM,kCAAkC,IAAI,EAAE;AAAA,QACvD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AAEd,aAAO,MAAM,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,SAAS,EAAE;AAAA,IAC/F;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,iBAAiB,aAAqB,aAA8C;AAChG,QAAI,QAAQ;AACZ,eAAW,QAAQ,aAAa;AAC9B,UAAI,CAAC,KAAK,aAAc;AACxB,YAAM,YAAY,KAAK,QAAQ,KAAK,YAAY;AAChD,YAAM,gBACJ,cAAc,KAAK,QAAQ,WAAW,KACtC,UAAU,WAAW,cAAc,QAAQ;AAE7C,YAAM,kBAAkB,MAAM,GAAG,WAAW,KAAK,KAAK,KAAK,cAAc,MAAM,CAAC;AAChF,UAAI,iBAAiB,iBAAiB;AACpC;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|