claude-code-session-manager 0.11.1 → 0.12.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/dist/assets/{TiptapBody-CRmtVlnv.js → TiptapBody-DS6BmNu7.js} +1 -1
- package/dist/assets/{cssMode-jKB9FuI1.js → cssMode-Bz8SFtEY.js} +1 -1
- package/dist/assets/{freemarker2-BPjwx9LW.js → freemarker2-DgDtRkId.js} +1 -1
- package/dist/assets/{handlebars-BLoV3dqv.js → handlebars-Dihbt_Mk.js} +1 -1
- package/dist/assets/{html-D-6YItp_.js → html-YEK2Ukg4.js} +1 -1
- package/dist/assets/{htmlMode-Dtc92cqk.js → htmlMode-Dg-MiMFK.js} +1 -1
- package/dist/assets/{index-DG1rozxP.js → index-3WBXI5kq.js} +872 -872
- package/dist/assets/{index-4x5C_duH.css → index-DJuzPa27.css} +1 -1
- package/dist/assets/{javascript-BQcvFYdn.js → javascript-CQXKrXCl.js} +1 -1
- package/dist/assets/{jsonMode-CnFmKTUr.js → jsonMode-IRogHtiE.js} +1 -1
- package/dist/assets/{liquid-ogD2CQU1.js → liquid-BMmGufls.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DKVkL-Ro.js → lspLanguageFeatures-CjGavzGi.js} +1 -1
- package/dist/assets/{mdx-BHbTIp7G.js → mdx-BBqtWGs6.js} +1 -1
- package/dist/assets/{python-fa-8uToX.js → python-Bqt0Xd-X.js} +1 -1
- package/dist/assets/{razor-D4OJJrW7.js → razor-6-o8bJo5.js} +1 -1
- package/dist/assets/{tsMode-J9ZwPyiC.js → tsMode-Cg0nI_Eq.js} +1 -1
- package/dist/assets/{typescript-1H3_b_hk.js → typescript-DiEzfesU.js} +1 -1
- package/dist/assets/{xml-Djys1Yq_.js → xml--sJWdh5o.js} +1 -1
- package/dist/assets/{yaml-CAZ0iMcZ.js → yaml-D3hKgUit.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/main/index.cjs +4 -0
- package/src/main/ipcSchemas.cjs +5 -0
- package/src/main/repoAnalyzer.cjs +346 -0
- package/src/main/search.cjs +337 -0
- package/src/preload/api.d.ts +48 -0
- package/src/preload/index.cjs +7 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as M,f as w,U as F,a as P,M as v,l as f,t as K,R as O}from"./index-
|
|
1
|
+
import{d as M,f as w,U as F,a as P,M as v,l as f,t as K,R as O}from"./index-3WBXI5kq.js";class E{constructor(e,t){this._modeId=e,this._defaults=t,this._worker=null,this._client=null,this._configChangeListener=this._defaults.onDidChange(()=>this._stopWorker()),this._updateExtraLibsToken=0,this._extraLibsChangeListener=this._defaults.onDidExtraLibsChange(()=>this._updateExtraLibs())}dispose(){this._configChangeListener.dispose(),this._extraLibsChangeListener.dispose(),this._stopWorker()}_stopWorker(){this._worker&&(this._worker.dispose(),this._worker=null),this._client=null}async _updateExtraLibs(){if(!this._worker)return;const e=++this._updateExtraLibsToken,t=await this._worker.getProxy();this._updateExtraLibsToken===e&&t.updateExtraLibs(this._defaults.getExtraLibs())}_getClient(){return this._client||(this._client=(async()=>(this._worker=M({moduleId:"vs/language/typescript/tsWorker",createWorker:()=>new Worker(new URL(""+new URL("ts.worker-59MjiAqk.js",import.meta.url).href,import.meta.url),{type:"module"}),label:this._modeId,keepIdleModels:!0,createData:{compilerOptions:this._defaults.getCompilerOptions(),extraLibs:this._defaults.getExtraLibs(),customWorkerPath:this._defaults.workerOptions.customWorkerPath,inlayHintsOptions:this._defaults.inlayHintsOptions}}),this._defaults.getEagerModelSync()?await this._worker.withSyncedResources(w.getModels().filter(e=>e.getLanguageId()===this._modeId).map(e=>e.uri)):await this._worker.getProxy()))()),this._client}async getLanguageServiceWorker(...e){const t=await this._getClient();return this._worker&&await this._worker.withSyncedResources(e),t}}const s={};s["lib.d.ts"]=!0;s["lib.decorators.d.ts"]=!0;s["lib.decorators.legacy.d.ts"]=!0;s["lib.dom.asynciterable.d.ts"]=!0;s["lib.dom.d.ts"]=!0;s["lib.dom.iterable.d.ts"]=!0;s["lib.es2015.collection.d.ts"]=!0;s["lib.es2015.core.d.ts"]=!0;s["lib.es2015.d.ts"]=!0;s["lib.es2015.generator.d.ts"]=!0;s["lib.es2015.iterable.d.ts"]=!0;s["lib.es2015.promise.d.ts"]=!0;s["lib.es2015.proxy.d.ts"]=!0;s["lib.es2015.reflect.d.ts"]=!0;s["lib.es2015.symbol.d.ts"]=!0;s["lib.es2015.symbol.wellknown.d.ts"]=!0;s["lib.es2016.array.include.d.ts"]=!0;s["lib.es2016.d.ts"]=!0;s["lib.es2016.full.d.ts"]=!0;s["lib.es2016.intl.d.ts"]=!0;s["lib.es2017.arraybuffer.d.ts"]=!0;s["lib.es2017.d.ts"]=!0;s["lib.es2017.date.d.ts"]=!0;s["lib.es2017.full.d.ts"]=!0;s["lib.es2017.intl.d.ts"]=!0;s["lib.es2017.object.d.ts"]=!0;s["lib.es2017.sharedmemory.d.ts"]=!0;s["lib.es2017.string.d.ts"]=!0;s["lib.es2017.typedarrays.d.ts"]=!0;s["lib.es2018.asyncgenerator.d.ts"]=!0;s["lib.es2018.asynciterable.d.ts"]=!0;s["lib.es2018.d.ts"]=!0;s["lib.es2018.full.d.ts"]=!0;s["lib.es2018.intl.d.ts"]=!0;s["lib.es2018.promise.d.ts"]=!0;s["lib.es2018.regexp.d.ts"]=!0;s["lib.es2019.array.d.ts"]=!0;s["lib.es2019.d.ts"]=!0;s["lib.es2019.full.d.ts"]=!0;s["lib.es2019.intl.d.ts"]=!0;s["lib.es2019.object.d.ts"]=!0;s["lib.es2019.string.d.ts"]=!0;s["lib.es2019.symbol.d.ts"]=!0;s["lib.es2020.bigint.d.ts"]=!0;s["lib.es2020.d.ts"]=!0;s["lib.es2020.date.d.ts"]=!0;s["lib.es2020.full.d.ts"]=!0;s["lib.es2020.intl.d.ts"]=!0;s["lib.es2020.number.d.ts"]=!0;s["lib.es2020.promise.d.ts"]=!0;s["lib.es2020.sharedmemory.d.ts"]=!0;s["lib.es2020.string.d.ts"]=!0;s["lib.es2020.symbol.wellknown.d.ts"]=!0;s["lib.es2021.d.ts"]=!0;s["lib.es2021.full.d.ts"]=!0;s["lib.es2021.intl.d.ts"]=!0;s["lib.es2021.promise.d.ts"]=!0;s["lib.es2021.string.d.ts"]=!0;s["lib.es2021.weakref.d.ts"]=!0;s["lib.es2022.array.d.ts"]=!0;s["lib.es2022.d.ts"]=!0;s["lib.es2022.error.d.ts"]=!0;s["lib.es2022.full.d.ts"]=!0;s["lib.es2022.intl.d.ts"]=!0;s["lib.es2022.object.d.ts"]=!0;s["lib.es2022.regexp.d.ts"]=!0;s["lib.es2022.string.d.ts"]=!0;s["lib.es2023.array.d.ts"]=!0;s["lib.es2023.collection.d.ts"]=!0;s["lib.es2023.d.ts"]=!0;s["lib.es2023.full.d.ts"]=!0;s["lib.es2023.intl.d.ts"]=!0;s["lib.es2024.arraybuffer.d.ts"]=!0;s["lib.es2024.collection.d.ts"]=!0;s["lib.es2024.d.ts"]=!0;s["lib.es2024.full.d.ts"]=!0;s["lib.es2024.object.d.ts"]=!0;s["lib.es2024.promise.d.ts"]=!0;s["lib.es2024.regexp.d.ts"]=!0;s["lib.es2024.sharedmemory.d.ts"]=!0;s["lib.es2024.string.d.ts"]=!0;s["lib.es5.d.ts"]=!0;s["lib.es6.d.ts"]=!0;s["lib.esnext.array.d.ts"]=!0;s["lib.esnext.collection.d.ts"]=!0;s["lib.esnext.d.ts"]=!0;s["lib.esnext.decorators.d.ts"]=!0;s["lib.esnext.disposable.d.ts"]=!0;s["lib.esnext.error.d.ts"]=!0;s["lib.esnext.float16.d.ts"]=!0;s["lib.esnext.full.d.ts"]=!0;s["lib.esnext.intl.d.ts"]=!0;s["lib.esnext.iterator.d.ts"]=!0;s["lib.esnext.promise.d.ts"]=!0;s["lib.esnext.sharedmemory.d.ts"]=!0;s["lib.scripthost.d.ts"]=!0;s["lib.webworker.asynciterable.d.ts"]=!0;s["lib.webworker.d.ts"]=!0;s["lib.webworker.importscripts.d.ts"]=!0;s["lib.webworker.iterable.d.ts"]=!0;function A(c,e,t=0){if(typeof c=="string")return c;if(c===void 0)return"";let o="";if(t){o+=e;for(let i=0;i<t;i++)o+=" "}if(o+=c.messageText,t++,c.next)for(const i of c.next)o+=A(i,e,t);return o}function _(c){return c?c.map(e=>e.text).join(""):""}class y{constructor(e){this._worker=e}_textSpanToRange(e,t){let o=e.getPositionAt(t.start),i=e.getPositionAt(t.start+t.length),{lineNumber:l,column:p}=o,{lineNumber:n,column:r}=i;return{startLineNumber:l,startColumn:p,endLineNumber:n,endColumn:r}}}class H{constructor(e){this._worker=e,this._libFiles={},this._hasFetchedLibFiles=!1,this._fetchLibFilesPromise=null}isLibFile(e){return e&&e.path.indexOf("/lib.")===0?!!s[e.path.slice(1)]:!1}getOrCreateModel(e){const t=F.parse(e),o=w.getModel(t);if(o)return o;if(this.isLibFile(t)&&this._hasFetchedLibFiles)return w.createModel(this._libFiles[t.path.slice(1)],"typescript",t);const i=K.getExtraLibs()[e];return i?w.createModel(i.content,"typescript",t):null}_containsLibFile(e){for(let t of e)if(this.isLibFile(t))return!0;return!1}async fetchLibFilesIfNecessary(e){this._containsLibFile(e)&&await this._fetchLibFiles()}_fetchLibFiles(){return this._fetchLibFilesPromise||(this._fetchLibFilesPromise=this._worker().then(e=>e.getLibFiles()).then(e=>{this._hasFetchedLibFiles=!0,this._libFiles=e})),this._fetchLibFilesPromise}}class W extends y{constructor(e,t,o,i){super(i),this._libFiles=e,this._defaults=t,this._selector=o,this._disposables=[],this._listener=Object.create(null);const l=r=>{if(r.getLanguageId()!==o)return;const u=()=>{const{onlyVisible:h}=this._defaults.getDiagnosticsOptions();h?r.isAttachedToEditor()&&this._doValidate(r):this._doValidate(r)};let d;const g=r.onDidChangeContent(()=>{clearTimeout(d),d=window.setTimeout(u,500)}),b=r.onDidChangeAttached(()=>{const{onlyVisible:h}=this._defaults.getDiagnosticsOptions();h&&(r.isAttachedToEditor()?u():w.setModelMarkers(r,this._selector,[]))});this._listener[r.uri.toString()]={dispose(){g.dispose(),b.dispose(),clearTimeout(d)}},u()},p=r=>{w.setModelMarkers(r,this._selector,[]);const u=r.uri.toString();this._listener[u]&&(this._listener[u].dispose(),delete this._listener[u])};this._disposables.push(w.onDidCreateModel(r=>l(r))),this._disposables.push(w.onWillDisposeModel(p)),this._disposables.push(w.onDidChangeModelLanguage(r=>{p(r.model),l(r.model)})),this._disposables.push({dispose(){for(const r of w.getModels())p(r)}});const n=()=>{for(const r of w.getModels())p(r),l(r)};this._disposables.push(this._defaults.onDidChange(n)),this._disposables.push(this._defaults.onDidExtraLibsChange(n)),w.getModels().forEach(r=>l(r))}dispose(){this._disposables.forEach(e=>e&&e.dispose()),this._disposables=[]}async _doValidate(e){const t=await this._worker(e.uri);if(e.isDisposed())return;const o=[],{noSyntaxValidation:i,noSemanticValidation:l,noSuggestionDiagnostics:p}=this._defaults.getDiagnosticsOptions();i||o.push(t.getSyntacticDiagnostics(e.uri.toString())),l||o.push(t.getSemanticDiagnostics(e.uri.toString())),p||o.push(t.getSuggestionDiagnostics(e.uri.toString()));const n=await Promise.all(o);if(!n||e.isDisposed())return;const r=n.reduce((d,g)=>g.concat(d),[]).filter(d=>(this._defaults.getDiagnosticsOptions().diagnosticCodesToIgnore||[]).indexOf(d.code)===-1),u=r.map(d=>d.relatedInformation||[]).reduce((d,g)=>g.concat(d),[]).map(d=>d.file?F.parse(d.file.fileName):null);await this._libFiles.fetchLibFilesIfNecessary(u),!e.isDisposed()&&w.setModelMarkers(e,this._selector,r.map(d=>this._convertDiagnostics(e,d)))}_convertDiagnostics(e,t){const o=t.start||0,i=t.length||1,{lineNumber:l,column:p}=e.getPositionAt(o),{lineNumber:n,column:r}=e.getPositionAt(o+i),u=[];return t.reportsUnnecessary&&u.push(P.Unnecessary),t.reportsDeprecated&&u.push(P.Deprecated),{severity:this._tsDiagnosticCategoryToMarkerSeverity(t.category),startLineNumber:l,startColumn:p,endLineNumber:n,endColumn:r,message:A(t.messageText,`
|
|
2
2
|
`),code:t.code.toString(),tags:u,relatedInformation:this._convertRelatedInformation(e,t.relatedInformation)}}_convertRelatedInformation(e,t){if(!t)return[];const o=[];return t.forEach(i=>{let l=e;if(i.file&&(l=this._libFiles.getOrCreateModel(i.file.fileName)),!l)return;const p=i.start||0,n=i.length||1,{lineNumber:r,column:u}=l.getPositionAt(p),{lineNumber:d,column:g}=l.getPositionAt(p+n);o.push({resource:l.uri,startLineNumber:r,startColumn:u,endLineNumber:d,endColumn:g,message:A(i.messageText,`
|
|
3
3
|
`)})}),o}_tsDiagnosticCategoryToMarkerSeverity(e){switch(e){case 1:return v.Error;case 3:return v.Info;case 0:return v.Warning;case 2:return v.Hint}return v.Info}}class C extends y{get triggerCharacters(){return["."]}async provideCompletionItems(e,t,o,i){const l=e.getWordUntilPosition(t),p=new O(t.lineNumber,l.startColumn,t.lineNumber,l.endColumn),n=e.uri,r=e.getOffsetAt(t),u=await this._worker(n);if(e.isDisposed())return;const d=await u.getCompletionsAtPosition(n.toString(),r);return!d||e.isDisposed()?void 0:{suggestions:d.entries.map(b=>{let h=p;if(b.replacementSpan){const x=e.getPositionAt(b.replacementSpan.start),D=e.getPositionAt(b.replacementSpan.start+b.replacementSpan.length);h=new O(x.lineNumber,x.column,D.lineNumber,D.column)}const k=[];return b.kindModifiers!==void 0&&b.kindModifiers.indexOf("deprecated")!==-1&&k.push(f.CompletionItemTag.Deprecated),{uri:n,position:t,offset:r,range:h,label:b.name,insertText:b.name,sortText:b.sortText,kind:C.convertKind(b.kind),tags:k}})}}async resolveCompletionItem(e,t){const o=e,i=o.uri,l=o.position,p=o.offset,r=await(await this._worker(i)).getCompletionEntryDetails(i.toString(),p,o.label);return r?{uri:i,position:l,label:r.name,kind:C.convertKind(r.kind),detail:_(r.displayParts),documentation:{value:C.createDocumentationString(r)}}:o}static convertKind(e){switch(e){case a.primitiveType:case a.keyword:return f.CompletionItemKind.Keyword;case a.variable:case a.localVariable:return f.CompletionItemKind.Variable;case a.memberVariable:case a.memberGetAccessor:case a.memberSetAccessor:return f.CompletionItemKind.Field;case a.function:case a.memberFunction:case a.constructSignature:case a.callSignature:case a.indexSignature:return f.CompletionItemKind.Function;case a.enum:return f.CompletionItemKind.Enum;case a.module:return f.CompletionItemKind.Module;case a.class:return f.CompletionItemKind.Class;case a.interface:return f.CompletionItemKind.Interface;case a.warning:return f.CompletionItemKind.File}return f.CompletionItemKind.Property}static createDocumentationString(e){let t=_(e.documentation);if(e.tags)for(const o of e.tags)t+=`
|
|
4
4
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-3WBXI5kq.js";const n={wordPattern:/(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g,comments:{lineComment:"//",blockComment:["/*","*/"]},brackets:[["{","}"],["[","]"],["(",")"]],onEnterRules:[{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,afterText:/^\s*\*\/$/,action:{indentAction:e.IndentAction.IndentOutdent,appendText:" * "}},{beforeText:/^\s*\/\*\*(?!\/)([^\*]|\*(?!\/))*$/,action:{indentAction:e.IndentAction.None,appendText:" * "}},{beforeText:/^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/,action:{indentAction:e.IndentAction.None,appendText:"* "}},{beforeText:/^(\t|(\ \ ))*\ \*\/\s*$/,action:{indentAction:e.IndentAction.None,removeText:1}}],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"',notIn:["string"]},{open:"'",close:"'",notIn:["string","comment"]},{open:"`",close:"`",notIn:["string","comment"]},{open:"/**",close:" */",notIn:["string"]}],folding:{markers:{start:new RegExp("^\\s*//\\s*#?region\\b"),end:new RegExp("^\\s*//\\s*#?endregion\\b")}}},o={defaultToken:"invalid",tokenPostfix:".ts",keywords:["abstract","any","as","asserts","bigint","boolean","break","case","catch","class","continue","const","constructor","debugger","declare","default","delete","do","else","enum","export","extends","false","finally","for","from","function","get","if","implements","import","in","infer","instanceof","interface","is","keyof","let","module","namespace","never","new","null","number","object","out","package","private","protected","public","override","readonly","require","global","return","satisfies","set","static","string","super","switch","symbol","this","throw","true","try","type","typeof","undefined","unique","unknown","var","void","while","with","yield","async","await","of"],operators:["<=",">=","==","!=","===","!==","=>","+","-","**","*","/","%","++","--","<<","</",">>",">>>","&","|","^","!","~","&&","||","??","?",":","=","+=","-=","*=","**=","/=","%=","<<=",">>=",">>>=","&=","|=","^=","@"],symbols:/[=><!~?:&|+\-*\/\^%]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,digits:/\d+(_+\d+)*/,octaldigits:/[0-7]+(_+[0-7]+)*/,binarydigits:/[0-1]+(_+[0-1]+)*/,hexdigits:/[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,regexpctl:/[(){}\[\]\$\^|\-*+?\.]/,regexpesc:/\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,tokenizer:{root:[[/[{}]/,"delimiter.bracket"],{include:"common"}],common:[[/#?[a-z_$][\w$]*/,{cases:{"@keywords":"keyword","@default":"identifier"}}],[/[A-Z][\w\$]*/,"type.identifier"],{include:"@whitespace"},[/\/(?=([^\\\/]|\\.)+\/([dgimsuy]*)(\s*)(\.|;|,|\)|\]|\}|$))/,{token:"regexp",bracket:"@open",next:"@regexp"}],[/[()\[\]]/,"@brackets"],[/[<>](?!@symbols)/,"@brackets"],[/!(?=([^=]|$))/,"delimiter"],[/@symbols/,{cases:{"@operators":"delimiter","@default":""}}],[/(@digits)[eE]([\-+]?(@digits))?/,"number.float"],[/(@digits)\.(@digits)([eE][\-+]?(@digits))?/,"number.float"],[/0[xX](@hexdigits)n?/,"number.hex"],[/0[oO]?(@octaldigits)n?/,"number.octal"],[/0[bB](@binarydigits)n?/,"number.binary"],[/(@digits)n?/,"number"],[/[;,.]/,"delimiter"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string","@string_double"],[/'/,"string","@string_single"],[/`/,"string","@string_backtick"]],whitespace:[[/[ \t\r\n]+/,""],[/\/\*\*(?!\/)/,"comment.doc","@jsdoc"],[/\/\*/,"comment","@comment"],[/\/\/.*$/,"comment"]],comment:[[/[^\/*]+/,"comment"],[/\*\//,"comment","@pop"],[/[\/*]/,"comment"]],jsdoc:[[/[^\/*]+/,"comment.doc"],[/\*\//,"comment.doc","@pop"],[/[\/*]/,"comment.doc"]],regexp:[[/(\{)(\d+(?:,\d*)?)(\})/,["regexp.escape.control","regexp.escape.control","regexp.escape.control"]],[/(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,["regexp.escape.control",{token:"regexp.escape.control",next:"@regexrange"}]],[/(\()(\?:|\?=|\?!)/,["regexp.escape.control","regexp.escape.control"]],[/[()]/,"regexp.escape.control"],[/@regexpctl/,"regexp.escape.control"],[/[^\\\/]/,"regexp"],[/@regexpesc/,"regexp.escape"],[/\\\./,"regexp.invalid"],[/(\/)([dgimsuy]*)/,[{token:"regexp",bracket:"@close",next:"@pop"},"keyword.other"]]],regexrange:[[/-/,"regexp.escape.control"],[/\^/,"regexp.invalid"],[/@regexpesc/,"regexp.escape"],[/[^\]]/,"regexp"],[/\]/,{token:"regexp.escape.control",next:"@pop",bracket:"@close"}]],string_double:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],string_single:[[/[^\\']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/'/,"string","@pop"]],string_backtick:[[/\$\{/,{token:"delimiter.bracket",next:"@bracketCounting"}],[/[^\\`$]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/`/,"string","@pop"]],bracketCounting:[[/\{/,"delimiter.bracket","@bracketCounting"],[/\}/,"delimiter.bracket","@pop"],{include:"common"}]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-3WBXI5kq.js";const n={comments:{blockComment:["<!--","-->"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],onEnterRules:[{beforeText:new RegExp("<([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$","i"),afterText:/^<\/([_:\w][_:\w-.\d]*)\s*>$/i,action:{indentAction:e.IndentAction.IndentOutdent}},{beforeText:new RegExp("<(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$","i"),action:{indentAction:e.IndentAction.Indent}}]},o={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}};export{n as conf,o as language};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{l as e}from"./index-
|
|
1
|
+
import{l as e}from"./index-3WBXI5kq.js";const t={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:e.IndentAction.Indent}}]},o={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};export{t as conf,o as language};
|
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Claude Session Manager</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-3WBXI5kq.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DJuzPa27.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body class="bg-bg text-fg font-mono antialiased">
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-session-manager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main/index.cjs",
|
package/src/main/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ const git = require('./git.cjs');
|
|
|
30
30
|
const superagent = require('./superagent.cjs');
|
|
31
31
|
const { registerProjectSkillsHandlers } = require('./projectSkills.cjs');
|
|
32
32
|
const filesIpc = require('./files.cjs');
|
|
33
|
+
const searchIpc = require('./search.cjs');
|
|
34
|
+
const repoAnalyzer = require('./repoAnalyzer.cjs');
|
|
33
35
|
const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
|
|
34
36
|
const { assertCwdInsideHome } = require('./lib/insideHome.cjs');
|
|
35
37
|
|
|
@@ -642,6 +644,8 @@ git.register(ipcMain);
|
|
|
642
644
|
superagent.registerSuperAgentHandlers();
|
|
643
645
|
registerProjectSkillsHandlers();
|
|
644
646
|
filesIpc.registerFilesHandlers();
|
|
647
|
+
searchIpc.registerSearchHandlers();
|
|
648
|
+
repoAnalyzer.register(ipcMain);
|
|
645
649
|
|
|
646
650
|
// OTEL telemetry export (opt-in via ~/.config/session-manager/otel.json).
|
|
647
651
|
ipcMain.handle('otel:get-config', async () => otelSettings.load());
|
package/src/main/ipcSchemas.cjs
CHANGED
|
@@ -323,6 +323,10 @@ const gitFileStatus = z.object({
|
|
|
323
323
|
cwd: z.string().min(1).max(4096),
|
|
324
324
|
}).passthrough();
|
|
325
325
|
|
|
326
|
+
const repoAnalyze = z.object({
|
|
327
|
+
cwd: z.string().min(1).max(4096),
|
|
328
|
+
}).passthrough();
|
|
329
|
+
|
|
326
330
|
// Plugin install: mirrors pluginInstall.cjs SLUG_RE + length cap. Defense in
|
|
327
331
|
// depth — install() re-checks; the schema rejects earlier.
|
|
328
332
|
const PLUGIN_SLUG_RE = /^[a-z0-9\-/]+$/;
|
|
@@ -398,6 +402,7 @@ module.exports = {
|
|
|
398
402
|
appGitBranch,
|
|
399
403
|
gitStatus,
|
|
400
404
|
gitFileStatus,
|
|
405
|
+
repoAnalyze,
|
|
401
406
|
pluginsInstall,
|
|
402
407
|
superagentStart,
|
|
403
408
|
superagentTabId,
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repo:analyze(cwd) — walk a project tree and return a compact stats snapshot
|
|
3
|
+
* for the RepoVisualizationModal.
|
|
4
|
+
*
|
|
5
|
+
* Ported in spirit from ClaudeCodeUnleashed's src/main/ipc/repoAnalyzer.ts, but
|
|
6
|
+
* stripped down to what we actually surface (no dependency listing, no full
|
|
7
|
+
* tree return, no file-content fetch). The renderer renders:
|
|
8
|
+
* - language breakdown bar (extension → file count, line count)
|
|
9
|
+
* - top directories treemap
|
|
10
|
+
* - uncommitted-file badge from git:status
|
|
11
|
+
* so the IPC return shape stays in lockstep with what the modal needs.
|
|
12
|
+
*
|
|
13
|
+
* Security:
|
|
14
|
+
* - cwd is validatePath'd (allowedRoots = home). Never follows symlinks
|
|
15
|
+
* (lstat-based; dirent.isSymbolicLink() check on every entry).
|
|
16
|
+
* - Hard ceilings: 30s wall-clock, 5000 files, 100KB per-file line counting.
|
|
17
|
+
* - Reads only — no writes, no spawn.
|
|
18
|
+
*
|
|
19
|
+
* Complexity: O(N) entries where N = files+dirs visited (bounded by
|
|
20
|
+
* MAX_FILES_TO_ANALYZE). Tail-recursion stack depth is bounded by
|
|
21
|
+
* MAX_DEPTH (10) so no stack hazards on pathologically nested trees.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fsp = require('node:fs/promises');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const { validatePath } = require('./config.cjs');
|
|
27
|
+
const { schemas } = require('./ipcSchemas.cjs');
|
|
28
|
+
const gitMod = require('./git.cjs');
|
|
29
|
+
|
|
30
|
+
// Directories to skip — `.git`, common build / dep / cache dirs.
|
|
31
|
+
const IGNORE_DIRS = new Set([
|
|
32
|
+
'.git', 'node_modules', 'dist', 'build', 'out', '.next', '.nuxt',
|
|
33
|
+
'coverage', '.cache', '.vscode', '.idea', '__pycache__', 'venv',
|
|
34
|
+
'.env', 'target', 'vendor', 'bower_components',
|
|
35
|
+
'.turbo', '.parcel-cache', '.svelte-kit', '.angular',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// Filenames to skip outright (lockfiles, OS detritus).
|
|
39
|
+
const IGNORE_FILES = new Set([
|
|
40
|
+
'.DS_Store', 'Thumbs.db',
|
|
41
|
+
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
// Per-extension display name. Anything not in here lumps under 'other'.
|
|
45
|
+
const LANG_LABELS = {
|
|
46
|
+
'.ts': 'TypeScript',
|
|
47
|
+
'.tsx': 'TSX',
|
|
48
|
+
'.js': 'JavaScript',
|
|
49
|
+
'.jsx': 'JSX',
|
|
50
|
+
'.cjs': 'CommonJS',
|
|
51
|
+
'.mjs': 'ES Module',
|
|
52
|
+
'.json': 'JSON',
|
|
53
|
+
'.css': 'CSS',
|
|
54
|
+
'.scss': 'SCSS',
|
|
55
|
+
'.less': 'Less',
|
|
56
|
+
'.html': 'HTML',
|
|
57
|
+
'.md': 'Markdown',
|
|
58
|
+
'.mdx': 'MDX',
|
|
59
|
+
'.py': 'Python',
|
|
60
|
+
'.go': 'Go',
|
|
61
|
+
'.rs': 'Rust',
|
|
62
|
+
'.java': 'Java',
|
|
63
|
+
'.kt': 'Kotlin',
|
|
64
|
+
'.c': 'C',
|
|
65
|
+
'.cpp': 'C++',
|
|
66
|
+
'.cc': 'C++',
|
|
67
|
+
'.h': 'C/C++ Header',
|
|
68
|
+
'.hpp': 'C++ Header',
|
|
69
|
+
'.swift': 'Swift',
|
|
70
|
+
'.rb': 'Ruby',
|
|
71
|
+
'.php': 'PHP',
|
|
72
|
+
'.sql': 'SQL',
|
|
73
|
+
'.sh': 'Shell',
|
|
74
|
+
'.bash': 'Shell',
|
|
75
|
+
'.zsh': 'Shell',
|
|
76
|
+
'.yaml': 'YAML',
|
|
77
|
+
'.yml': 'YAML',
|
|
78
|
+
'.toml': 'TOML',
|
|
79
|
+
'.xml': 'XML',
|
|
80
|
+
'.svg': 'SVG',
|
|
81
|
+
'.vue': 'Vue',
|
|
82
|
+
'.svelte': 'Svelte',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Caps. Wall-clock is enforced by an AbortController-style deadline; the
|
|
86
|
+
// per-file line cap stops giant generated files (e.g. 5 MB package-lock that
|
|
87
|
+
// somehow escapes the ignore list) from blocking the event loop.
|
|
88
|
+
const MAX_DEPTH = 12;
|
|
89
|
+
const MAX_FILES_TO_ANALYZE = 5000;
|
|
90
|
+
const MAX_FILE_SIZE_FOR_LINE_COUNT = 100 * 1024; // 100 KiB
|
|
91
|
+
const DEADLINE_MS = 30_000;
|
|
92
|
+
const TOP_DIRS_LIMIT = 10;
|
|
93
|
+
|
|
94
|
+
/** Count newlines without loading entire file into memory beyond the cap. */
|
|
95
|
+
async function countLines(filePath, sizeHint) {
|
|
96
|
+
if (sizeHint > MAX_FILE_SIZE_FOR_LINE_COUNT) return 0;
|
|
97
|
+
try {
|
|
98
|
+
const buf = await fsp.readFile(filePath);
|
|
99
|
+
if (buf.length === 0) return 0;
|
|
100
|
+
let n = 1;
|
|
101
|
+
for (let i = 0; i < buf.length; i++) {
|
|
102
|
+
if (buf[i] === 0x0a) n++;
|
|
103
|
+
}
|
|
104
|
+
// Don't count a final empty trailing line.
|
|
105
|
+
if (buf[buf.length - 1] === 0x0a) n--;
|
|
106
|
+
return n;
|
|
107
|
+
} catch {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function languageKey(ext) {
|
|
113
|
+
if (!ext) return 'other';
|
|
114
|
+
return LANG_LABELS[ext] ? ext.slice(1) : 'other';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Recursive walk. Mutates `ctx` (single object per top-level call) so we
|
|
119
|
+
* don't allocate a fresh stats frame per directory — keeps the hot path
|
|
120
|
+
* contiguous (CLAUDE.md performance guidance). Stops early when deadline is
|
|
121
|
+
* past or the file ceiling is hit.
|
|
122
|
+
*
|
|
123
|
+
* Returns the per-immediate-subtree { fileCount, totalLines } so the caller
|
|
124
|
+
* (one level up) can attribute it to the top-level directory bucket.
|
|
125
|
+
*/
|
|
126
|
+
async function walk(absDir, relDir, depth, ctx) {
|
|
127
|
+
if (ctx.stopped || Date.now() > ctx.deadline) {
|
|
128
|
+
ctx.stopped = true;
|
|
129
|
+
return { fileCount: 0, totalLines: 0 };
|
|
130
|
+
}
|
|
131
|
+
if (depth > MAX_DEPTH) return { fileCount: 0, totalLines: 0 };
|
|
132
|
+
|
|
133
|
+
let entries;
|
|
134
|
+
try {
|
|
135
|
+
entries = await fsp.readdir(absDir, { withFileTypes: true });
|
|
136
|
+
} catch {
|
|
137
|
+
return { fileCount: 0, totalLines: 0 };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let subFiles = 0;
|
|
141
|
+
let subLines = 0;
|
|
142
|
+
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (ctx.stopped) break;
|
|
145
|
+
if (Date.now() > ctx.deadline) {
|
|
146
|
+
ctx.stopped = true;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Never follow symlinks (security). withFileTypes gives us
|
|
151
|
+
// isSymbolicLink() directly — no extra stat needed.
|
|
152
|
+
if (entry.isSymbolicLink()) continue;
|
|
153
|
+
|
|
154
|
+
const name = entry.name;
|
|
155
|
+
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
if (IGNORE_DIRS.has(name)) continue;
|
|
158
|
+
const childAbs = path.join(absDir, name);
|
|
159
|
+
const childRel = relDir ? path.join(relDir, name) : name;
|
|
160
|
+
const result = await walk(childAbs, childRel, depth + 1, ctx);
|
|
161
|
+
subFiles += result.fileCount;
|
|
162
|
+
subLines += result.totalLines;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!entry.isFile()) continue;
|
|
167
|
+
if (IGNORE_FILES.has(name)) continue;
|
|
168
|
+
if (ctx.fileCount >= MAX_FILES_TO_ANALYZE) {
|
|
169
|
+
ctx.stopped = true;
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
ctx.fileCount++;
|
|
174
|
+
subFiles++;
|
|
175
|
+
ctx.totalFiles++;
|
|
176
|
+
|
|
177
|
+
const ext = path.extname(name).toLowerCase();
|
|
178
|
+
let size = 0;
|
|
179
|
+
let lines = 0;
|
|
180
|
+
try {
|
|
181
|
+
const st = await fsp.stat(path.join(absDir, name));
|
|
182
|
+
size = st.size;
|
|
183
|
+
} catch {
|
|
184
|
+
// unreadable — count the file but skip lines
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Only line-count "interesting" files. JSON / SVG / XML / YAML are noisy
|
|
188
|
+
// and don't really represent "code"; skipping them keeps the LOC number
|
|
189
|
+
// closer to what a developer expects.
|
|
190
|
+
if (LANG_LABELS[ext] && ext !== '.svg' && ext !== '.xml' && ext !== '.json') {
|
|
191
|
+
lines = await countLines(path.join(absDir, name), size);
|
|
192
|
+
}
|
|
193
|
+
subLines += lines;
|
|
194
|
+
ctx.totalLines += lines;
|
|
195
|
+
|
|
196
|
+
// Language breakdown bucket.
|
|
197
|
+
const key = languageKey(ext);
|
|
198
|
+
const langBucket = ctx.languages[key] ?? (ctx.languages[key] = { files: 0, lines: 0 });
|
|
199
|
+
langBucket.files++;
|
|
200
|
+
langBucket.lines += lines;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Top-dir attribution happens in analyze() based on the return value, not
|
|
204
|
+
// here — avoids double-pushing when walk recurses into nested dirs.
|
|
205
|
+
return { fileCount: subFiles, totalLines: subLines };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* repo:analyze entry — runs the walk, then asks git.cjs for status (cached,
|
|
210
|
+
* so this is cheap when called repeatedly). On any path-validation or fs
|
|
211
|
+
* failure returns a `{ ok:false, error }` shape so the renderer can decide
|
|
212
|
+
* whether to show an error state or just empty stats.
|
|
213
|
+
*/
|
|
214
|
+
async function analyze(cwd) {
|
|
215
|
+
let realCwd;
|
|
216
|
+
try {
|
|
217
|
+
realCwd = validatePath(cwd);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return { ok: false, error: (e && e.message) || 'invalid cwd' };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const startMs = Date.now();
|
|
223
|
+
const ctx = {
|
|
224
|
+
deadline: startMs + DEADLINE_MS,
|
|
225
|
+
stopped: false,
|
|
226
|
+
fileCount: 0,
|
|
227
|
+
totalFiles: 0,
|
|
228
|
+
totalLines: 0,
|
|
229
|
+
languages: Object.create(null),
|
|
230
|
+
topDirs: [],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Walk the top-level dir manually (we want depth-1 directories listed in
|
|
234
|
+
// topDirs but not "." itself).
|
|
235
|
+
let entries;
|
|
236
|
+
try {
|
|
237
|
+
entries = await fsp.readdir(realCwd, { withFileTypes: true });
|
|
238
|
+
} catch (e) {
|
|
239
|
+
return { ok: false, error: (e && e.message) || 'readdir failed' };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// First pass: count files directly in the root, group root-level loose
|
|
243
|
+
// files under a synthetic "(root)" bucket so they're visible in the treemap.
|
|
244
|
+
let rootLooseFiles = 0;
|
|
245
|
+
let rootLooseLines = 0;
|
|
246
|
+
|
|
247
|
+
// Process directories at depth 1, files at depth 0 (root). We follow the
|
|
248
|
+
// pattern: lstat-equivalent (entry.isSymbolicLink()) → no follow.
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
if (ctx.stopped) break;
|
|
251
|
+
if (Date.now() > ctx.deadline) { ctx.stopped = true; break; }
|
|
252
|
+
if (entry.isSymbolicLink()) continue;
|
|
253
|
+
const name = entry.name;
|
|
254
|
+
|
|
255
|
+
if (entry.isDirectory()) {
|
|
256
|
+
if (IGNORE_DIRS.has(name)) continue;
|
|
257
|
+
const result = await walk(path.join(realCwd, name), name, 1, ctx);
|
|
258
|
+
ctx.topDirs.push({ path: name, fileCount: result.fileCount, totalLines: result.totalLines });
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!entry.isFile()) continue;
|
|
263
|
+
if (IGNORE_FILES.has(name)) continue;
|
|
264
|
+
if (ctx.fileCount >= MAX_FILES_TO_ANALYZE) { ctx.stopped = true; break; }
|
|
265
|
+
|
|
266
|
+
ctx.fileCount++;
|
|
267
|
+
ctx.totalFiles++;
|
|
268
|
+
rootLooseFiles++;
|
|
269
|
+
|
|
270
|
+
const ext = path.extname(name).toLowerCase();
|
|
271
|
+
let size = 0;
|
|
272
|
+
let lines = 0;
|
|
273
|
+
try {
|
|
274
|
+
const st = await fsp.stat(path.join(realCwd, name));
|
|
275
|
+
size = st.size;
|
|
276
|
+
} catch { /* */ }
|
|
277
|
+
if (LANG_LABELS[ext] && ext !== '.svg' && ext !== '.xml' && ext !== '.json') {
|
|
278
|
+
lines = await countLines(path.join(realCwd, name), size);
|
|
279
|
+
}
|
|
280
|
+
rootLooseLines += lines;
|
|
281
|
+
ctx.totalLines += lines;
|
|
282
|
+
const key = languageKey(ext);
|
|
283
|
+
const langBucket = ctx.languages[key] ?? (ctx.languages[key] = { files: 0, lines: 0 });
|
|
284
|
+
langBucket.files++;
|
|
285
|
+
langBucket.lines += lines;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (rootLooseFiles > 0) {
|
|
289
|
+
ctx.topDirs.push({ path: '(root)', fileCount: rootLooseFiles, totalLines: rootLooseLines });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Sort top dirs by file count desc, then trim. Tie-break by name for stability.
|
|
293
|
+
ctx.topDirs.sort((a, b) => {
|
|
294
|
+
if (b.fileCount !== a.fileCount) return b.fileCount - a.fileCount;
|
|
295
|
+
return a.path.localeCompare(b.path);
|
|
296
|
+
});
|
|
297
|
+
const topDirectories = ctx.topDirs.slice(0, TOP_DIRS_LIMIT);
|
|
298
|
+
|
|
299
|
+
// Materialize the languages map sorted by file count desc.
|
|
300
|
+
const languageBreakdown = Object.create(null);
|
|
301
|
+
const langEntries = Object.entries(ctx.languages).sort((a, b) => b[1].files - a[1].files);
|
|
302
|
+
for (const [k, v] of langEntries) languageBreakdown[k] = v;
|
|
303
|
+
|
|
304
|
+
// Git status — best-effort. Failure (not a git repo / git missing / timeout)
|
|
305
|
+
// returns null which we surface as { uncommitted: 0, branch: null }.
|
|
306
|
+
let gitStatus = { uncommitted: 0, branch: null };
|
|
307
|
+
try {
|
|
308
|
+
const s = await gitMod.getStatus(realCwd);
|
|
309
|
+
if (s) {
|
|
310
|
+
gitStatus = {
|
|
311
|
+
uncommitted: s.uncommittedCount || 0,
|
|
312
|
+
branch: s.branch || null,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
} catch { /* gitMod swallows errors but be defensive */ }
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
ok: true,
|
|
319
|
+
cwd: realCwd,
|
|
320
|
+
totalFiles: ctx.totalFiles,
|
|
321
|
+
totalLines: ctx.totalLines,
|
|
322
|
+
languageBreakdown,
|
|
323
|
+
topDirectories,
|
|
324
|
+
gitStatus,
|
|
325
|
+
truncated: ctx.stopped,
|
|
326
|
+
durationMs: Date.now() - startMs,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function register(ipcMain) {
|
|
331
|
+
ipcMain.handle('repo:analyze', async (_e, payload) => {
|
|
332
|
+
const parsed = schemas.repoAnalyze.safeParse(payload);
|
|
333
|
+
if (!parsed.success) {
|
|
334
|
+
return { ok: false, error: 'invalid payload' };
|
|
335
|
+
}
|
|
336
|
+
return analyze(parsed.data.cwd);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
module.exports = {
|
|
341
|
+
register,
|
|
342
|
+
analyze,
|
|
343
|
+
// Exported for tests.
|
|
344
|
+
LANG_LABELS,
|
|
345
|
+
IGNORE_DIRS,
|
|
346
|
+
};
|