11const fs = require ( 'fs' ) ;
2+ const path = require ( 'path' ) ;
23const express = require ( 'express' ) ;
34const jwt = require ( 'jsonwebtoken' ) ;
45const vscode = require ( 'vscode' ) ;
56
67class SocketServer {
7- constructor ( config , packagePath ) {
8+ constructor ( config , packagePath , outputChannel ) {
89 this . pkgInfo = JSON . parse ( fs . readFileSync ( packagePath , 'utf8' ) ) ;
910
1011 this . cfg = {
@@ -18,34 +19,20 @@ class SocketServer {
1819
1920 this . app = express ( ) ;
2021 this . app . use ( express . json ( ) ) ;
22+ this . outputChannel = outputChannel ;
2123
2224 this . app . get ( '/healthz' , ( req , res ) => res . json ( { ok : true } ) ) ;
2325 this . app . get ( '/version' , ( req , res ) => res . json ( { name : this . pkgInfo . name , version : this . pkgInfo . version } ) ) ;
2426
25- this . app . post ( '/command' , async ( req , res ) => {
26- try {
27- const token = req . body ?. token ;
28- if ( ! token ) throw new Error ( 'missing token' ) ;
29-
30- const pubKey = fs . readFileSync ( this . cfg . publicKeyPath , 'utf8' ) ;
31- const payload = jwt . verify ( token , pubKey , {
32- algorithms : [ 'RS256' , 'ES256' ] ,
33- audience : this . cfg . allowedAud
34- } ) ;
35-
36- const cmd = payload . cmd ;
37- if ( ! cmd || typeof cmd !== 'string' ) throw new Error ( 'missing cmd claim' ) ;
38- if ( this . cfg . allowedCommandPattern && ! new RegExp ( this . cfg . allowedCommandPattern ) . test ( cmd ) ) {
39- throw new Error ( 'command rejected by allowedCommandPattern' ) ;
40- }
41-
42- const term = await this . #ensureTerminal( payload . cwd , payload . terminalId ) ;
43- term . sendText ( cmd , true ) ;
44- res . json ( { ok : true , ran : cmd } ) ;
45- } catch ( err ) {
46- res . status ( 400 ) . json ( { ok : false , error : err ?. message || String ( err ) } ) ;
47- }
48- } ) ;
27+ this . app . post ( '/command' , this . #onCommandRequest. bind ( this ) ) ;
28+
29+ /**
30+ * Open a file in the editor, optionally at a specific line.
31+ * Expects JSON body with:
32+ * - filePath: string (relative to workspace root)
33+ * - line: integer (optional, 1-based line number)
34+ */
35+ this . app . post ( "/open" , this . #onFileOpenRequest. bind ( this ) ) ;
4936 }
5037
5138 start ( ) {
@@ -66,6 +53,62 @@ class SocketServer {
6653 try { if ( fs . existsSync ( this . cfg . socketPath ) ) fs . unlinkSync ( this . cfg . socketPath ) ; } catch { }
6754 }
6855
56+ async #onCommandRequest ( req , res ) {
57+ try {
58+ const token = req . body ?. token ;
59+ if ( ! token ) throw new Error ( 'missing token' ) ;
60+
61+ const pubKey = fs . readFileSync ( this . cfg . publicKeyPath , 'utf8' ) ;
62+ const payload = jwt . verify ( token , pubKey , {
63+ algorithms : [ 'RS256' , 'ES256' ] ,
64+ audience : this . cfg . allowedAud
65+ } ) ;
66+
67+ const cmd = payload . cmd ;
68+ if ( ! cmd || typeof cmd !== 'string' ) throw new Error ( 'missing cmd claim' ) ;
69+ if ( this . cfg . allowedCommandPattern && ! new RegExp ( this . cfg . allowedCommandPattern ) . test ( cmd ) ) {
70+ throw new Error ( 'command rejected by allowedCommandPattern' ) ;
71+ }
72+
73+ const term = await this . #ensureTerminal( payload . cwd , payload . terminalId ) ;
74+ term . sendText ( cmd , true ) ;
75+ res . json ( { ok : true , ran : cmd } ) ;
76+ } catch ( err ) {
77+ res . status ( 400 ) . json ( { ok : false , error : err ?. message || String ( err ) } ) ;
78+ }
79+ }
80+
81+ async #onFileOpenRequest ( req , res ) {
82+ this . outputChannel . appendLine ( `/open request: ${ JSON . stringify ( req . body ) } ` ) ;
83+
84+ const { filePath, line } = req . body || { } ;
85+ if ( ! filePath ) {
86+ return res . status ( 400 ) . json ( { ok : false , error : 'Missing required filePath' } ) ;
87+ }
88+
89+ try {
90+ const absolutePath = path . join ( vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri . fsPath || '/' , filePath ) ;
91+ if ( ! fs . existsSync ( absolutePath ) ) {
92+ return res . status ( 404 ) . json ( { ok : false , error : `Cannot open file ${ absolutePath } , as it does not exist` } ) ;
93+ }
94+
95+ const fileUri = vscode . Uri . file ( absolutePath ) ;
96+
97+ const doc = await vscode . workspace . openTextDocument ( fileUri ) ;
98+ const editor = await vscode . window . showTextDocument ( doc , { preview : false } ) ;
99+ if ( line && Number . isInteger ( line ) && line > 0 && line <= doc . lineCount ) {
100+ const lineIndex = line - 1 ;
101+ const range = new vscode . Range ( lineIndex , 0 , lineIndex , 0 ) ;
102+ editor . revealRange ( range , vscode . TextEditorRevealType . InCenter ) ;
103+ editor . selection = new vscode . Selection ( range . start , range . start ) ;
104+ }
105+ res . json ( { success : true } ) ;
106+ } catch ( err ) {
107+ vscode . window . showErrorMessage ( `Failed to open file: ${ err ?. message || String ( err ) } ` ) ;
108+ return res . status ( 500 ) . json ( { ok : false , error : 'failed to open file' } ) ;
109+ }
110+ }
111+
69112 async #ensureTerminal( cwd , requestedTerminalName = null ) {
70113 if ( requestedTerminalName ) {
71114 const existing = vscode . window . terminals . find ( t => t . name === requestedTerminalName ) ;
0 commit comments