p-doom/crowd-code-dataset-0.2 huggingface dataset:
[content] src/extension.ts (typescript)
				margin: '0 0 0 3rem',
				textDecoration: `none; white-space: pre; content: "↳ Move Cursor Here"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative;`
[content] src/extension.ts (typescript)
				margin: '0',
				textDecoration: `none; display: block; white-space: pre; content: "↳ Move Cursor Here"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative;`
[content] src/extension.ts (typescript)
				margin: '0 0 0 2rem',
				textDecoration: `none; position: absolute; display: block; white-space: pre; content: "↳ Move Cursor Here"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; top: 0; z-index: 100;`
[content] src/extension.ts (typescript)
				margin: '0 0 0 2rem',
				textDecoration: `none; display: inline-block; white-space: pre; content: "↳ Move Cursor Here"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
[content] src/extension.ts (typescript)
function getDynamicMargin(editor: vscode.TextEditor, anchorLine: number, text: string): string {
	// Count lines in the preview text
	const lines = text.split(/
?
/);
	const height = lines.length;
	
	// We need to check the document lines that will be covered by this panel.
	// The panel starts at 'anchorLine' and extends downwards by 'height' lines.
	// However, visually, since it's 'after', it sits to the right of 'anchorLine',
	// and then flows down.
	// So we check document lines from anchorLine to anchorLine + height - 1.
	
	const doc = editor.document;
	let maxLen = 0;
	const startLine = anchorLine;
	const endLine = Math.min(doc.lineCount - 1, anchorLine + height - 1);
	
	for (let i = startLine; i <= endLine; i++) {
		const lineText = doc.lineAt(i).text;
		// Simple approximation: assume tabs are 4 spaces if we can't get config easily, 
		// or just treat them as 1 char (which might underestimate). 
		// Better to overestimate: treat tab as 4 chars.
		const len = lineText.replace(/	/g, '    ').length;
		if (len > maxLen) {
			maxLen = len;
		}
	}
	
	// Length of the anchor line itself
	const anchorLineText = doc.lineAt(anchorLine).text;
	const anchorLen = anchorLineText.replace(/	/g, '    ').length;
	
	// The offset needed is maxLen - anchorLen.
	// If maxLen <= anchorLen, offset is 0 (margin is just base padding).
	// If maxLen > anchorLen, we need to push right by (maxLen - anchorLen).
	
	const diff = Math.max(0, maxLen - anchorLen);
	// Base margin 2rem is roughly 4ch. Let's use ch units for everything to be consistent.
	// 1ch is width of '0'. In monospace, mostly consistent.
	// Add 3ch extra padding for safety/visual gap.
	const margin = diff + 4; 
	return `${margin}ch`;
}
[content] src/extension.ts (typescript)
				textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top; top: ${topOffset};`
[content] src/extension.ts (typescript)
		const docLineCount = editor.document.lineCount;
		// If inserting at EOF (or beyond), attach to the last line.
		// Otherwise, attach to the line AT the insertion point and shift visually UP into the gap.
		let anchorLine = posLine;
		let shiftUp = true;
		
		if (anchorLine >= docLineCount) {
			anchorLine = docLineCount - 1;
			shiftUp = false; // At EOF, we just append below or to the right
		}

		const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE); 
		
		// If shifting up, we overlap the line ABOVE the anchor.
		// If not shifting up (EOF), we overlap the anchor line itself (if we flow down? 
		// but we are inline-block to the right, so we overlap nothing? 
		// Actually getDynamicMargin checks lines downwards.
		// If shifting up, the panel starts visually at (AnchorLine - 1).
		// So we pass (AnchorLine - 1) to getDynamicMargin.
		const marginCheckLine = shiftUp ? Math.max(0, anchorLine - 1) : anchorLine;
		const margin = getDynamicMargin(editor, marginCheckLine, fullBlock);

		const topOffset = shiftUp ? '-1.2em' : '0';

		// Dashed line style (only if shifting up to indicate insertion point)
		// We use 'before' decoration for the line.
		// It needs to be absolute, full width (or enough to reach left), 
		// and aligned with the panel top.
		const beforeDecoration = shiftUp ? {
			contentText: '',
			textDecoration: `none; position: absolute; left: 0; width: 100vw; border-top: 1px dashed var(--vscode-charts-purple); top: ${topOffset}; height: 0; z-index: 99; pointer-events: none;`
		} : undefined;
[content] src/extension.ts (typescript)
				textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
[content] src/extension.ts (typescript)
		// "Next to" logic:
		// If inserting at line N > 0, we attach 'after' to line N-1.
		// If inserting at line 0, we attach 'after' to line 0 (best effort).
		const anchorLine = posLine > 0 ? posLine - 1 : 0;
		const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE); // End of anchor line
		const margin = getDynamicMargin(editor, anchorLine, fullBlock);
[content] src/extension.ts (typescript)
		const docLineCount = editor.document.lineCount;
		// If inserting at EOF (or beyond), attach to the last line.
		// Otherwise, attach to the line AT the insertion point and shift visually UP into the gap.
		let anchorLine = posLine;
		let shiftUp = true;
		
		if (anchorLine >= docLineCount) {
			anchorLine = docLineCount - 1;
			shiftUp = false; // At EOF, we just append below or to the right
		}

		const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE); 
		
		// We attach to the line AT the insertion point.
		// The panel floats to the right of this line.
		// The dashed line connects the start of this line to the panel.
		// This indicates that the new text will be inserted at this line position (pushing the current line down).
		const marginCheckLine = anchorLine;
		const margin = getDynamicMargin(editor, marginCheckLine, fullBlock);

		const topOffset = '0';

		// Dashed line style
		// We use 'before' decoration for the line.
		// It needs to be absolute, full width (or enough to reach left), 
		// and aligned with the panel top.
		const beforeDecoration = {
			contentText: '',
			textDecoration: `none; position: absolute; left: 0; width: 100vw; border-top: 1px dashed var(--vscode-charts-purple); top: 0; height: 0; z-index: 99; pointer-events: none;`
		};
[content] src/extension.ts (typescript)
		// Simple approximation: assume tabs are 4 spaces.
		let len = lineText.replace(/	/g, '    ').length;
		// Cursor displays 'command+L to chat...' ghost text on empty lines (approx 23 chars).
		// Ensure we clear this by treating empty lines as having length 23.
		if (len === 0) {
			len = 23;
		}
[content] src/extension.ts (typescript)
		// Simple approximation: assume tabs are 4 spaces if we can't get config easily, 
		// or just treat them as 1 char (which might underestimate). 
		// Better to overestimate: treat tab as 4 chars.
		const len = lineText.replace(/	/g, '    ').length;
[content] src/extension.ts (typescript)
				textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 9999; vertical-align: top; top: ${topOffset};`
[content] src/extension.ts (typescript)
		// Calculate margin based on the visual location.
		// If shifted up, visual start is (anchorLine - 1).
		const visualStartLine = shiftUp ? anchorLine - 1 : anchorLine;
		const margin = getDynamicMargin(editor, visualStartLine, fullBlock);
[content] src/extension.ts (typescript)
		let shiftUp = false;

		// WORKAROUND: Cursor displays a high-z-index overlay ('command+L...') on empty lines.
		// If we attach our panel to such a line, it gets covered.
		// To fix this, if the insertion target is an empty line, we attach the decoration 
		// to the NEXT line (which is structurally 'above' in the DOM stack) and visually 
		// shift it UP. This ensures our panel renders on top of the empty line's overlay.
		if (anchorLine < docLineCount && editor.document.lineAt(anchorLine).text.trim().length === 0) {
			// Only shift if there IS a next line to attach to
			if (anchorLine + 1 < docLineCount) {
				anchorLine = anchorLine + 1;
				shiftUp = true;
			}
		} else if (anchorLine >= docLineCount) {
			// EOF case
[content] src/extension.ts (typescript)
		// We attach to the line AT the insertion point.
		// The panel floats to the right of this line.
		// The dashed line connects the start of this line to the panel.
		// This indicates that the new text will be inserted at this line position (pushing the current line down).
		const marginCheckLine = anchorLine;
		const margin = getDynamicMargin(editor, marginCheckLine, fullBlock);
[content] src/extension.ts (typescript)
		if (isEmpty) {
			decorationOptions = {
				after: {
					contentText: '',
					color: new vscode.ThemeColor('charts.purple'),
					backgroundColor: new vscode.ThemeColor('editor.background'),
					fontStyle: 'italic',
					fontWeight: '600',
					// Standard indent, block display pushes it to next row
					margin: '0 0 0 2rem', 
					textDecoration: `none; display: block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100;`
				}
			};
		} else {
			const margin = getDynamicMargin(editor, anchorLine, fullBlock);
			decorationOptions = {
				before: {
					contentText: '',
					textDecoration: `none; position: absolute; left: 0; width: 100vw; border-top: 1px dashed var(--vscode-charts-purple); top: 0; height: 0; z-index: 99; pointer-events: none;`
				},
				after: {
					contentText: '',
					color: new vscode.ThemeColor('charts.purple'),
					backgroundColor: new vscode.ThemeColor('editor.background'),
					fontStyle: 'italic',
					fontWeight: '600',
					margin: `0 0 0 ${margin}`,
					textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
				}
			};
		}

		decorationReplaceBlockType = vscode.window.createTextEditorDecorationType(decorationOptions);
[content] src/extension.ts (typescript)
		const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE);
		const lineText = editor.document.lineAt(anchorLine).text;
		const isEmpty = lineText.trim().length === 0;

		// WORKAROUND: Cursor displays a high-z-index overlay ('command+L...') on empty lines that covers inline decorations.
		// To circumvent this, we use 'display: block' on empty lines, which forces the panel to render
		// on a new visual row BELOW the ghost text overlay, ensuring visibility.
		// For non-empty lines, we use 'display: inline-block' with dynamic margins and a dashed guideline.
		
		let decorationOptions: vscode.DecorationRenderOptions;
[content] src/extension.ts (typescript)
		// Dashed line style
		// We use 'before' decoration for the line.
		// It needs to be absolute, full width (or enough to reach left), 
		// and aligned with the panel top.
		const beforeDecoration = {
			contentText: '',
			textDecoration: `none; position: absolute; left: 0; width: 100vw; border-top: 1px dashed var(--vscode-charts-purple); top: 0; height: 0; z-index: 99; pointer-events: none;`
		};

		decorationReplaceBlockType = vscode.window.createTextEditorDecorationType({
			before: beforeDecoration,
			after: {
				contentText: '',
				color: new vscode.ThemeColor('charts.purple'),
				backgroundColor: new vscode.ThemeColor('editor.background'),
				fontStyle: 'italic',
				fontWeight: '600',
				margin: `0 0 0 ${margin}`,
				textDecoration: `none; display: inline-block; white-space: pre; content: "${cssContent}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top; top: ${topOffset};`
			}
		});
[content] src/extension.ts (typescript)
		const anchorPos = new vscode.Position(anchorLine, Number.MAX_VALUE); 
		
		// We attach to the line AT the insertion point.
		// The panel floats to the right of this line.
		// The dashed line connects the start of this line to the panel.
		// This indicates that the new text will be inserted at this line position (pushing the current line down).
		const marginCheckLine = anchorLine;
		const margin = getDynamicMargin(editor, marginCheckLine, fullBlock);

		const topOffset = '0';
[content] src/extension.ts (typescript)
		// We attach to 'finalAnchorLine'.
		// If visualShiftDown is true, we are physically on N-1 but visually on N.
		// getDynamicMargin calculates margins starting from the anchor line downwards.
		// This correctly accounts for the text on N-1 (anchor) and N (target) to avoid overlaps.
		const margin = getDynamicMargin(editor, finalAnchorLine, fullBlock);
[content] src/extension.ts (typescript)
		// Check if the target line is empty.
		// If so, the Cursor 'ghost text' overlay (command+L...) might obscure our panel.
		// Workaround: Attach to the PREVIOUS line and shift visually down.
		// This renders the decoration in the context of the previous line (which usually sits above the overlay),
		// preventing occlusion.
		const targetLineText = editor.document.lineAt(anchorLine).text;
		const isTargetEmpty = targetLineText.trim().length === 0;
		
		let finalAnchorLine = anchorLine;
		let visualShiftDown = false;

		if (isTargetEmpty && anchorLine > 0) {
			finalAnchorLine = anchorLine - 1;
			visualShiftDown = true;
		}

		const anchorPos = new vscode.Position(finalAnchorLine, Number.MAX_VALUE);
[content] src/extension.ts (typescript)
		// Check if the target position is visible
		const isVisible = editor.visibleRanges.some(r => r.contains(targetPos));
		
		let anchorPos = targetPos;
		let label = "↳ Move Cursor Here";

		if (!isVisible && editor.visibleRanges.length > 0) {
			const firstVisible = editor.visibleRanges[0].start;
			const lastVisible = editor.visibleRanges[editor.visibleRanges.length - 1].end;
			
			if (targetPos.isBefore(firstVisible)) {
				anchorPos = editor.document.lineAt(firstVisible.line).range.end;
			} else {
				anchorPos = editor.document.lineAt(lastVisible.line).range.end;
			}

			if (targetPos.line < anchorPos.line) {
				label = \`↑ Move Cursor to Line \${targetPos.line + 1}\`;
			} else {
				label = \`↓ Move Cursor to Line \${targetPos.line + 1}\`;
			}
		}

		const margin = getDynamicMargin(editor, anchorPos.line, label);
[content] src/extension.ts (typescript)
				textDecoration: `none; display: inline-block; white-space: pre; content: "${label}"; border: 1px solid var(--vscode-charts-purple); padding: 4px; border-radius: 4px; box-shadow: 0 4px 8px rgba(0,0,0,0.25); pointer-events: none; position: relative; z-index: 100; vertical-align: top;`
			}
		});
		editor.setDecorations(decorationReplaceBlockType, [{ range: new vscode.Range(anchorPos, anchorPos) }]);
[tab] package.json (json)
{
  "name": "crowd-pilot",
  "displayName": "crowd-pilot-extension",
  "description": "Teaching language models to code like humans.",
  "publisher": "p(doom)",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.99.3"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onStartupFinished"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "crowd-pilot.testRun",
        "title": "Test Run"
      },
      {
        "command": "crowd-pilot.hideUi",
        "title": "crowd-pilot: Hide Preview"
      },
      {
        "command": "crowd-pilot.sglangTest",
        "title": "crowd-pilot: Test SGLang"
      },
      {
        "command": "crowd-pilot.modelRun",
        "title": "crowd-pilot: Model Plan & Run"
      }
    ],
    "keybindings": [
      {
        "command": "crowd-pilot.modelRun",
        "key": "tab",
        "mac": "tab",
        "when": "editorTextFocus || terminalFocus"
      },
      {
        "command": "crowd-pilot.modelRun",
        "key": "tab",
        "mac": "tab",
        "when": "inQuickOpen && crowdPilot.uiVisible"
      },
      {
        "command": "crowd-pilot.hideUi",
        "key": "escape",
        "mac": "escape",
        "when": "crowdPilot.uiVisible"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src",
    "test": "vscode-test"
  },
  "devDependencies": {
    "@types/vscode": "^1.105.0",
    "@types/mocha": "^10.0.10",
    "@types/node": "22.x",
    "@typescript-eslint/eslint-plugin": "^8.45.0",
    "@typescript-eslint/parser": "^8.45.0",
    "eslint": "^9.36.0",
    "typescript": "^5.9.3",
    "@vscode/test-cli": "^0.0.11",
    "@vscode/test-electron": "^2.5.2"
  }
}
[tab] /Users/franzsrambical/Library/Application Support/Cursor/User/settings.json (jsonc)
{
    "git.autofetch": true,
    "vim.sneakUseIgnorecaseAndSmartcase": true,
    "diffEditor.codeLens": true,
    "python.analysis.typeCheckingMode": "standard",
    "workbench.editorAssociations": {
        "git-rebase-todo": "default"
    },
    "cursorpyright.analysis.typeCheckingMode": "standard",
    "remote.SSH.remotePlatform": {
        "juwels-cluster.fz-juelich.de": "linux",
        "hpc-submit02.scidom.de": "linux",
        "horeka.scc.kit.edu": "linux",
        "hpc-submit01.scidom.de": "linux",
        "login.haicore.berlin": "linux",
        "ssh -i ~/.ssh/id_ed25519 srambical2@juwels-booster.fz-juelich.de": "linux"
    },
    "workbench.preferredLightColorTheme": "Visual Studio Light",
    "workbench.colorTheme": "Default Light Modern",
    "remote.autoForwardPortsSource": "hybrid",
    "crowdCode.export.exportPath": "/tmp/franz/crowd-code/",
    "vim.easymotion": true,
    "vim.normalModeKeyBindings": [
        {
            "before": [
                "s"
            ],
            "after": [
                "<leader>",
                "<leader>",
                "2",
                "s"
            ]
        }
    ],
    "git.blame.editorDecoration.enabled": false,
    "terminal.integrated.commandsToSkipShell": [
        "crowd-pilot.testRun",
        "crowd-pilot.hideUi",
        "crowd-pilot.modelRun",
        "crowd-pilot.previewNoop"
    ],
    "editor.quickSuggestionsDelay": 0
}
[content] src/extension.ts (typescript)
	const testRun = vscode.commands.registerCommand('crowd-pilot.testRun', async () => {
		const editor = vscode.window.activeTextEditor;
		if (!editor) {
			return;
		}
		try {
			const plan = buildMockPlan(editor);
			if (!previewVisible) {
				showPreviewUI(plan);
				return;
			}
			const runPlan = currentPlan ?? plan;
			hidePreviewUI();
			await executePlan(runPlan);
			vscode.window.showInformationMessage('All actions emitted (mock)');
		} catch (err) {
			const errorMessage = err instanceof Error ? err.message : String(err);
			vscode.window.showErrorMessage(`Mock run failed: ${errorMessage}`);
		}
	});
[content] src/extension.ts (typescript)
	
	try {
		const plan = await requestModelActions(editor);
		if (plan && plan.length > 0) {
			showPreviewUI(plan);
		} else {
			hidePreviewUI();
		}
	} catch (err) {
		// Silent failure or log to console
		console.error('[crowd-pilot] Auto-preview model error:', err);
[content] src/extension.ts (typescript)
	// Step 0: Insert multiline content two lines below the cursor (start of target line)
	if (mockStep === 0) {
		const targetLine = clamp(cursor.line + 2, 0, Math.max(0, lineCount - 1));
		return {
			kind: 'editInsert',
			position: [targetLine, 0],
			text: '/* crowd-pilot: insert start */
line A
line B
/* crowd-pilot: insert end */
'
		};
	}
	// Step 1: Replace a two-line range three and four lines below the cursor
	if (mockStep === 1) {
		const startLine = clamp(cursor.line + 3, 0, Math.max(0, lineCount - 1));
		const endLine = clamp(startLine + 1, 0, Math.max(0, lineCount - 1));
		const endChar = doc.lineAt(endLine).range.end.character;
		const range = {
			start: [startLine, 0] as [number, number],
			end: [endLine, endChar] as [number, number]
		};
		const replacement = [
			'/* crowd-pilot: replacement */',
			'REPLACED LINE 1',
			'REPLACED LINE 2'
		].join('
');
		return { kind: 'editReplace', range, text: replacement };
	}
	// Step 2: Delete a three-line range six to eight lines below the cursor
	if (mockStep === 2) {
		const startLine = clamp(cursor.line + 6, 0, Math.max(0, lineCount - 1));
		const endLine = clamp(startLine + 2, 0, Math.max(0, lineCount - 1));
		
		// To fully delete the lines including the newline, we target the start of the next line.
		let endPosLine = endLine + 1;
		let endPosChar = 0;
		
		if (endPosLine >= lineCount) {
			// If deleting the last line(s), just go to the end of the document
			endPosLine = lineCount - 1;
			endPosChar = doc.lineAt(endPosLine).range.end.character;
		}

		const range = {
			start: [startLine, 0] as [number, number],
			end: [endPosLine, endPosChar] as [number, number]
		};
		return { kind: 'editDelete', range };
	}
	// Step 3: Execute in Terminal
	if (mockStep === 3) {
		return { kind: 'terminalSendText', text: 'echo "Hello World"' };
	}
	// Step 4: Move Cursor to End of File
	if (mockStep === 4) {
		const lastLine = doc.lineCount - 1;
		const lastChar = doc.lineAt(lastLine).range.end.character;
		return {
			kind: 'setSelections',
			selections: [{ start: [lastLine, lastChar], end: [lastLine, lastChar] }]
		};
	}
	return undefined;
}

function advanceMockStep(): void {
	mockStep = (mockStep + 1) % 5;
}

function autoShowNextAction(): void {
[content] src/extension.ts (typescript)
function getHardcodedNextAction(editor: vscode.TextEditor): PlannedAction | undefined {
	const cursor = editor.selection.active;
	const doc = editor.document;
	const lineCount = doc.lineCount;
	const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
[content] src/extension.ts (typescript)

	// Model-based (commented out / strictly optional switch):
	/*
	try {
		const plan = await requestModelActions(editor);
		if (plan && plan.length > 0) {
			showPreviewUI(plan);
		} else {
			hidePreviewUI();
		}
	} catch (err) {
		console.error('[crowd-pilot] Auto-preview model error:', err);
		hidePreviewUI();
	}
	*/
[content] src/extension.ts (typescript)
// -------------------- Auto-Preview Logic --------------------
// To switch to model-based, swap getHardcodedNextAction with requestModelActions call
async function autoShowNextAction(): Promise<void> {
[tab] package.json (json)
{
  "name": "crowd-pilot",
  "displayName": "crowd-pilot-extension",
  "description": "Teaching language models to code like humans.",
  "publisher": "p(doom)",
  "version": "0.0.1",
  "engines": {
    "vscode": "^1.99.3"
  },
  "categories": [
    "Other"
  ],
  "activationEvents": [
    "onStartupFinished"
  ],
  "main": "./out/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "crowd-pilot.testRun",
        "title": "Test Run"
      },
      {
        "command": "crowd-pilot.hideUi",
        "title": "crowd-pilot: Hide Preview"
      },
      {
        "command": "crowd-pilot.sglangTest",
        "title": "crowd-pilot: Test SGLang"
      },
      {
        "command": "crowd-pilot.modelRun",
        "title": "crowd-pilot: Model Plan & Run"
      }
    ],
    "keybindings": [
      {
        "command": "crowd-pilot.modelRun",
        "key": "tab",
        "mac": "tab",
        "when": "editorTextFocus && crowdPilot.uiVisible"
      },
      {
        "command": "crowd-pilot.modelRun",
        "key": "tab",
        "mac": "tab",
        "when": "inQuickOpen && crowdPilot.uiVisible"
      },
      {
        "command": "crowd-pilot.hideUi",
        "key": "escape",
        "mac": "escape",
        "when": "crowdPilot.uiVisible"
      }
    ]
  },
  "scripts": {
    "vscode:prepublish": "npm run compile",
    "compile": "tsc -p ./",
    "watch": "tsc -watch -p ./",
    "pretest": "npm run compile && npm run lint",
    "lint": "eslint src",
    "test": "vscode-test"
  },
  "devDependencies": {
    "@types/vscode": "^1.105.0",
    "@types/mocha": "^10.0.10",
    "@types/node": "22.x",
    "@typescript-eslint/eslint-plugin": "^8.45.0",
    "@typescript-eslint/parser": "^8.45.0",
    "eslint": "^9.36.0",
    "typescript": "^5.9.3",
    "@vscode/test-cli": "^0.0.11",
    "@vscode/test-electron": "^2.5.2"
  }
}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	const apiKey = config.get<string>('crowd-pilot.apiKey');
	if (!apiKey) {
		vscode.window.showErrorMessage('Crowd Pilot: Please set your Gemini API Key in settings (crowd-pilot.apiKey).');
		return;
	}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	const apiKey = config.get<string>('crowd-pilot.apiKey');
	if (!apiKey) {
		vscode.window.showErrorMessage('Crowd Pilot: Please set your Gemini API Key in settings (crowd-pilot.apiKey).');
		throw new Error('API key not set');
	}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	const apiKey = config.get<string>('crowd-pilot.apiKey');
	if (!apiKey) {
		vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
		throw new Error('API key not set');
	}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	const provider = config.get<string>('crowd-pilot.provider', 'gemini');
	
	let hostname: string;
	let port: number;
	let path: string;
	let useHttps = true;
	let modelName = MODEL_NAME;
	const headers: any = {
		'Content-Type': 'application/json'
	};

	if (provider === 'sglang') {
		const sglangUrl = config.get<string>('crowd-pilot.sglangUrl', 'http://localhost:30000/v1/chat/completions');
		const parsed = new URL(sglangUrl);
		hostname = parsed.hostname;
		port = parsed.port ? parseInt(parsed.port) : (parsed.protocol === 'https:' ? 443 : 80);
		path = parsed.pathname;
		useHttps = parsed.protocol === 'https:';
		modelName = config.get<string>('crowd-pilot.sglangModel', 'qwen/qwen2.5-0.5b-instruct');
		// SGLang often doesn't require auth, or allows any string.
		// If user sets an API key for sglang, we could use it, but for now we'll skip strict checks.
	} else {
		// Gemini (default)
		const apiKey = config.get<string>('crowd-pilot.apiKey');
		if (!apiKey) {
			vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
			throw new Error('API key not set');
		}
		hostname = HOSTNAME;
		port = PORT;
		path = BASE_PATH;
		headers['Authorization'] = `Bearer ${apiKey}`;
	}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	const provider = config.get<string>('crowd-pilot.provider', 'gemini');
	
	let hostname: string;
	let port: number;
	let path: string;
	let useHttps = true;
	let modelName: string;
	const headers: any = {
		'Content-Type': 'application/json'
	};

	if (provider === 'sglang') {
		const sglangUrl = config.get<string>('crowd-pilot.sglangUrl', 'http://localhost:30000/v1/chat/completions');
		const parsed = new URL(sglangUrl);
		hostname = parsed.hostname;
		port = parsed.port ? parseInt(parsed.port) : (parsed.protocol === 'https:' ? 443 : 80);
		path = parsed.pathname;
		useHttps = parsed.protocol === 'https:';
		modelName = SGLANG_MODEL_NAME;
	} else {
		// Gemini (default)
		const apiKey = config.get<string>('crowd-pilot.apiKey');
		if (!apiKey) {
			vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
			throw new Error('API key not set');
		}
		hostname = GEMINI HOSTNAME;
		port = GEMINI PORT;
		path = GEMINI BASE_PATH;
		headers['Authorization'] = `Bearer ${apiKey}`;
	}
[content] src/extension.ts (typescript)
	const config = vscode.workspace.getConfiguration();
	
	let hostname: string;
	let port: number;
	let path: string;
	let useHttps = true;
	let modelName: string;
	const headers: any = {
		'Content-Type': 'application/json'
	};

	if (!USE_GEMINI) {
		// SGLang
		hostname = SGLANG_HOSTNAME;
		port = SGLANG_PORT;
		path = SGLANG_BASE_PATH;
		useHttps = false; // SGLang usually http
		modelName = SGLANG_MODEL_NAME;
	} else {
		// Gemini (default)
		const apiKey = config.get<string>('crowd-pilot.apiKey');
		if (!apiKey) {
			vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
			throw new Error('API key not set');
		}
		hostname = GEMINI_HOSTNAME;
		port = GEMINI_PORT;
		path = GEMINI_BASE_PATH;
		headers['Authorization'] = `Bearer ${apiKey}`;
		modelName = GEMINI_MODEL_NAME;
	}
[content] src/extension.ts (typescript)
	
	let hostname: string;
	let port: number;
	let path: string;
	let useHttps = true;
	let modelName: string;
	const headers: any = {
		'Content-Type': 'application/json'
	};

	if (!USE_GEMINI) {
		// SGLang
		hostname = SGLANG_HOSTNAME;
		port = SGLANG_PORT;
		path = SGLANG_BASE_PATH;
		useHttps = false; 
		modelName = SGLANG_MODEL_NAME;
	} else {
		// Gemini
		const apiKey = config.get<string>('crowd-pilot.apiKey');
		if (!apiKey) {
			vscode.window.showErrorMessage('Crowd Pilot: Please set your API Key in settings (crowd-pilot.apiKey).');
			return;
		}
		hostname = GEMINI_HOSTNAME;
		port = GEMINI_PORT;
		path = GEMINI_BASE_PATH;
		headers['Authorization'] = `Bearer ${apiKey}`;
		modelName = GEMINI_MODEL_NAME;
	}