githits 0.9.1 → 0.9.2

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/index.js CHANGED
@@ -1 +1 @@
1
- import{version}from"./shared/chunk-mz63e2sv.js";export{version};
1
+ import{version}from"./shared/chunk-dqq6yv70.js";export{version};
@@ -1,4 +1,4 @@
1
- import{__require,version}from"./chunk-mz63e2sv.js";import{createHash,randomBytes}from"node:crypto";function generateCodeVerifier(){return randomBytes(32).toString("base64url")}function generateCodeChallenge(verifier){return createHash("sha256").update(verifier).digest("base64url")}function generateState(){return randomBytes(32).toString("hex")}var CLIENT_UPDATE_REQUIRED_REASON="Backend protocol changed";class ClientUpdateRequiredError extends Error{reason;currentVersion;constructor(message=`Update required: ${CLIENT_UPDATE_REQUIRED_REASON}`,reason=CLIENT_UPDATE_REQUIRED_REASON,currentVersion=undefined){super(message);this.reason=reason;this.currentVersion=currentVersion;this.name="ClientUpdateRequiredError"}}function isClientUpdateRequiredGraphQLError(input){return input.code==="CLIENT_UPDATE_REQUIRED"}function isGraphQLSchemaMismatchError(input){if(!isGraphQLSchemaMismatchMessage(input.message))return false;return!input.code||input.code==="GRAPHQL_VALIDATION_FAILED"||input.code==="BAD_USER_INPUT"}function isGraphQLSchemaMismatchMessage(message){return/Cannot query field|Field .* does not exist|Unknown argument|Unknown type|Unknown field/i.test(message)}import{z as z2}from"zod";function debugLog(area,payload){if(!isAreaEnabled(area))return;const line={ts:new Date().toISOString(),area,...payload};let text;try{text=JSON.stringify(line)}catch{text=JSON.stringify({ts:line.ts,area,error:"debug-log payload not serialisable"})}process.stderr.write(`${text}
1
+ import{__require,version}from"./chunk-dqq6yv70.js";import{createHash,randomBytes}from"node:crypto";function generateCodeVerifier(){return randomBytes(32).toString("base64url")}function generateCodeChallenge(verifier){return createHash("sha256").update(verifier).digest("base64url")}function generateState(){return randomBytes(32).toString("hex")}var CLIENT_UPDATE_REQUIRED_REASON="Backend protocol changed";class ClientUpdateRequiredError extends Error{reason;currentVersion;constructor(message=`Update required: ${CLIENT_UPDATE_REQUIRED_REASON}`,reason=CLIENT_UPDATE_REQUIRED_REASON,currentVersion=undefined){super(message);this.reason=reason;this.currentVersion=currentVersion;this.name="ClientUpdateRequiredError"}}function isClientUpdateRequiredGraphQLError(input){return input.code==="CLIENT_UPDATE_REQUIRED"}function isGraphQLSchemaMismatchError(input){if(!isGraphQLSchemaMismatchMessage(input.message))return false;return!input.code||input.code==="GRAPHQL_VALIDATION_FAILED"||input.code==="BAD_USER_INPUT"}function isGraphQLSchemaMismatchMessage(message){return/Cannot query field|Field .* does not exist|Unknown argument|Unknown type|Unknown field/i.test(message)}import{z as z2}from"zod";function debugLog(area,payload){if(!isAreaEnabled(area))return;const line={ts:new Date().toISOString(),area,...payload};let text;try{text=JSON.stringify(line)}catch{text=JSON.stringify({ts:line.ts,area,error:"debug-log payload not serialisable"})}process.stderr.write(`${text}
2
2
  `)}function isDebugAreaEnabled(area){return isAreaEnabled(area)}function isAreaEnabled(area){const raw=process.env.GITHITS_DEBUG;if(!raw||raw==="")return false;const scopes=raw.split(",").map((s)=>s.trim()).filter(Boolean);if(scopes.includes(area))return true;if(isExplicitOnlyArea(area))return false;return scopes.includes("*")}function isExplicitOnlyArea(area){return area==="code-nav-wire"}var DEFAULT_FETCH_TIMEOUT_MS=120000;class FetchTimeoutError extends Error{timeoutMs;constructor(timeoutMs,options){super(`Request timed out after ${timeoutMs}ms.`,options);this.name="FetchTimeoutError";this.timeoutMs=timeoutMs}}async function fetchWithTimeout(input,init={},options={}){const timeoutMs=options.timeoutMs??DEFAULT_FETCH_TIMEOUT_MS;const timeoutSignal=AbortSignal.timeout(timeoutMs);const signal=init.signal?AbortSignal.any([init.signal,timeoutSignal]):timeoutSignal;const fetchFn=options.fetchFn??globalThis.fetch;let timeoutId;const timeout=new Promise((_,reject)=>{timeoutId=setTimeout(()=>{reject(new FetchTimeoutError(timeoutMs))},timeoutMs)});try{return await Promise.race([fetchFn(input,{...init,signal}),timeout])}catch(cause){if(cause instanceof FetchTimeoutError)throw cause;if(timeoutSignal.aborted&&!init.signal?.aborted){throw new FetchTimeoutError(timeoutMs,{cause})}throw cause}finally{if(timeoutId)clearTimeout(timeoutId)}}function isFetchTimeoutError(error){return error instanceof FetchTimeoutError}var DEFAULT_MCP_URL="https://mcp.githits.com";var DEFAULT_API_URL="https://api.githits.com";var DEFAULT_CODE_NAV_URL="https://pkgseer.dev";class ServiceUrlConfigError extends Error{constructor(message){super(message);this.name="ServiceUrlConfigError"}}function getMcpUrl(){return resolveServiceUrl("GITHITS_MCP_URL",DEFAULT_MCP_URL)}function getMcpStorageKeyUrl(){return process.env.GITHITS_MCP_URL??DEFAULT_MCP_URL}function getApiUrl(){return resolveServiceUrl("GITHITS_API_URL",DEFAULT_API_URL)}function getCodeNavigationUrl(){if(process.env.GITHITS_CODE_NAV_URL!==undefined){return validateServiceUrl(process.env.GITHITS_CODE_NAV_URL,"GITHITS_CODE_NAV_URL")}if(process.env.PKGSEER_URL!==undefined){return validateServiceUrl(process.env.PKGSEER_URL,"PKGSEER_URL")}return DEFAULT_CODE_NAV_URL}function validateServiceUrl(value,source){let parsed;try{parsed=new URL(value)}catch{throw new ServiceUrlConfigError(`Invalid ${source}: expected an HTTPS URL or an HTTP loopback URL.`)}if(parsed.protocol==="https:")return value;const hostname=parsed.hostname.replace(/^\[|\]$/g,"");const isLoopback=hostname==="localhost"||hostname==="127.0.0.1"||hostname==="::1";if(parsed.protocol==="http:"&&isLoopback)return value;throw new ServiceUrlConfigError(`Invalid ${source}: use HTTPS. Plain HTTP is allowed only for localhost, 127.0.0.1, or [::1].`)}function resolveServiceUrl(envName,defaultUrl){const override=process.env[envName];return override===undefined?defaultUrl:validateServiceUrl(override,envName)}function getEnvApiToken(){return process.env.GITHITS_API_TOKEN}var TERMS_ACCEPTANCE_REQUIRED_CODE="TERMS_ACCEPTANCE_REQUIRED";var TERMS_URL="https://githits.com/legal/terms-of-service/";var TERMS_ACCEPTANCE_URL="https://app.githits.com/settings/privacy";class TermsAcceptanceRequiredError extends Error{code=TERMS_ACCEPTANCE_REQUIRED_CODE;termsUrl;acceptanceUrl;constructor(remediation={}){super("Terms acceptance required. Run `githits settings terms accept`, then retry.");this.name="TermsAcceptanceRequiredError";this.termsUrl=remediation.termsUrl??TERMS_URL;this.acceptanceUrl=remediation.acceptanceUrl??TERMS_ACCEPTANCE_URL}}function createTermsAcceptanceError(payload){const record=parseErrorRecord(payload);if(!record)return;const contract=record.code===TERMS_ACCEPTANCE_REQUIRED_CODE?record:firstGraphQLErrorExtensions(record);if(contract?.code!==TERMS_ACCEPTANCE_REQUIRED_CODE)return;return new TermsAcceptanceRequiredError({termsUrl:stringField(contract,"terms_url"),acceptanceUrl:stringField(contract,"acceptance_url")})}function throwIfTermsAcceptanceRequired(payload){const error=createTermsAcceptanceError(payload);if(error)throw error}function parseErrorRecord(payload){if(typeof payload==="string"){try{return parseErrorRecord(JSON.parse(payload))}catch{return}}return payload&&typeof payload==="object"?payload:undefined}function firstGraphQLErrorExtensions(record){const firstError=Array.isArray(record.errors)?record.errors[0]:undefined;if(!firstError||typeof firstError!=="object")return;const extensions=firstError.extensions;return extensions&&typeof extensions==="object"?extensions:undefined}function stringField(record,field){return typeof record[field]==="string"?record[field]:undefined}class PkgseerTransportError extends Error{constructor(message,options){super(message,options);this.name="PkgseerTransportError"}}function baseUrl(endpointUrl){return endpointUrl.replace(/\/+$/,"")}async function postPkgseerGraphql(request){const userAgent=request.userAgent??"githits-cli";const timeoutMs=request.timeoutMs??DEFAULT_FETCH_TIMEOUT_MS;const endpointUrl=validateServiceUrl(request.endpointUrl,"package/source service URL");let response;try{response=await fetchWithTimeout(`${baseUrl(endpointUrl)}/api/graphql`,{method:"POST",headers:{...request.clientHeaders?.(),Authorization:`Bearer ${request.token}`,"Content-Type":"application/json","User-Agent":userAgent},body:JSON.stringify({query:request.query,variables:request.variables})},{fetchFn:request.fetchFn,timeoutMs})}catch(cause){debugLog("pkg-graphql",{event:"transport-error",errorName:cause instanceof Error?cause.name:typeof cause,hasCause:true});throw new PkgseerTransportError("Network request failed before a response was received. Caller should re-wrap with a domain-specific message.",{cause})}const responseBody=await response.text().catch(()=>"");const parsedBody=parseJsonOrNull(responseBody);throwIfTermsAcceptanceRequired(parsedBody);return{status:response.status,responseBody,parsedBody}}function parseJsonOrNull(body){if(!body)return null;try{return JSON.parse(body)}catch{return null}}import{z}from"zod";var MAX_ERROR_DETAIL_LENGTH=500;function parseHttpErrorDetail(body,fields){if(!body)return;let parsed;try{parsed=JSON.parse(body)}catch{return}if(!isRecord(parsed))return;for(const field of fields){const value=parsed[field];if(typeof value!=="string")continue;const normalized=normalizeSingleLineText(value);if(!normalized)continue;if(normalized.length<=MAX_ERROR_DETAIL_LENGTH)return normalized;return`${normalized.slice(0,MAX_ERROR_DETAIL_LENGTH-3)}...`}return}function normalizeSingleLineText(value){const withoutControlCharacters=Array.from(value,(character)=>{const codePoint=character.codePointAt(0)??0;return codePoint<=31||codePoint===127?" ":character}).join("");return withoutControlCharacters.replace(/\s+/g," ").trim()}function isRecord(value){return value!==null&&typeof value==="object"&&!Array.isArray(value)}import{writeSync}from"node:fs";var ENABLED_VALUES=new Set(["1","true","yes","on"]);function isTelemetryEnabled(env=process.env){const raw=env.GITHITS_TELEMETRY?.trim().toLowerCase();if(!raw)return false;return ENABLED_VALUES.has(raw)}class TelemetryCollector{enabled;now;write;sessionStartMs;spans=[];activeSpans=new Map;nextId=1;flushed=false;constructor(options={}){this.enabled=isTelemetryEnabled(options.env);this.now=options.now??(()=>globalThis.performance.now());this.write=options.write??((text)=>writeSync(process.stderr.fd,text));this.sessionStartMs=this.now()}isEnabled(){return this.enabled}startSpan(name,attributes){if(!this.enabled)return;const span={id:this.nextId++,name,startMs:this.now(),attributes:sanitiseAttributes(attributes)};this.spans.push(span);this.activeSpans.set(span.id,span);return{id:span.id}}endSpan(handle,attributes){if(!this.enabled||!handle)return;const span=this.activeSpans.get(handle.id);if(!span||span.endMs!==undefined)return;span.endMs=this.now();span.attributes=mergeAttributes(span.attributes,attributes);this.activeSpans.delete(handle.id)}flush(exitCode=0){if(!this.enabled||this.flushed)return;const nowMs=this.now();for(const span of this.activeSpans.values()){if(span.endMs!==undefined)continue;span.endMs=nowMs;span.endedAtExit=true}this.activeSpans.clear();this.write(formatTelemetryReport(this.spans,this.sessionStartMs,nowMs,exitCode));this.flushed=true}}async function withTelemetrySpan(name,operation,attributes){const handle=telemetryCollector.startSpan(name,attributes);try{const result=await operation();telemetryCollector.endSpan(handle);return result}catch(error){telemetryCollector.endSpan(handle,{error:true});throw error}}function startTelemetrySpan(name,attributes){return telemetryCollector.startSpan(name,attributes)}function endTelemetrySpan(handle,attributes){telemetryCollector.endSpan(handle,attributes)}function flushTelemetry(exitCode=0){telemetryCollector.flush(exitCode)}var telemetryCollector=new TelemetryCollector;function sanitiseAttributes(attributes){if(!attributes)return;const entries=Object.entries(attributes).filter(([,value])=>value!==undefined);if(entries.length===0)return;return Object.fromEntries(entries)}function mergeAttributes(initial,extra){if(!initial&&!extra)return;return sanitiseAttributes({...initial??{},...extra??{}})}function formatTelemetryReport(spans,sessionStartMs,sessionEndMs,exitCode){const lines=["[githits telemetry]",`exit: ${exitCode}`,`total: ${formatMs(sessionEndMs-sessionStartMs)}`];const orderedSpans=[...spans].sort((left,right)=>{if(left.startMs!==right.startMs){return left.startMs-right.startMs}return left.id-right.id});for(const span of orderedSpans){const endMs=span.endMs??sessionEndMs;const details=[`start +${formatMs(span.startMs-sessionStartMs)}`];if(span.endedAtExit){details.push("ended-at-exit")}const attrs=formatAttributes(span.attributes);if(attrs){details.push(attrs)}lines.push(`- ${span.name}: ${formatMs(endMs-span.startMs)} (${details.join(", ")})`)}return`${lines.join(`
3
3
  `)}
4
4
  `}function formatAttributes(attributes){if(!attributes)return"";return Object.entries(attributes).map(([key,value])=>`${key}=${String(value)}`).join(" ")}function formatMs(value){return`${value.toFixed(1)}ms`}var DEFAULT_EXAMPLE_REQUEST_TIMEOUT_MS=240000;var AUTHENTICATION_REQUIRED_MESSAGE="Authentication required.";var LOCAL_AUTHENTICATION_MISSING_MESSAGE="No local GitHits authentication token found.";var SERVER_AUTHENTICATION_REJECTED_MESSAGE="GitHits could not accept the authentication token.";class AuthenticationError extends Error{source;constructor(message=AUTHENTICATION_REQUIRED_MESSAGE,source="local"){super(message);this.name="AuthenticationError";this.source=source}}function isTokenRefreshableError(error){return error instanceof AuthenticationError||error instanceof TermsAcceptanceRequiredError}class ApiRateLimitError extends Error{status=429;retryAfterSeconds;constructor(message="Request rate limited.",retryAfterSeconds){super(message);this.name="ApiRateLimitError";this.retryAfterSeconds=retryAfterSeconds}}function parseRetryAfterSeconds(value,nowMs){const normalized=value?.trim();if(!normalized)return;if(/^\d+$/.test(normalized)){const delaySeconds2=Number(normalized);return Number.isSafeInteger(delaySeconds2)?delaySeconds2:undefined}if(/^[+-]?\d+(?:\.\d+)?$/.test(normalized))return;const isHttpDate=/^[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT$/.test(normalized)||/^[A-Z][a-z]+, \d{2}-[A-Z][a-z]{2}-\d{2} \d{2}:\d{2}:\d{2} GMT$/.test(normalized)||/^[A-Z][a-z]{2} [A-Z][a-z]{2} [ \d]\d \d{2}:\d{2}:\d{2} \d{4}$/.test(normalized);if(!isHttpDate)return;const retryAtMs=Date.parse(normalized);if(!Number.isFinite(retryAtMs))return;const delayMs=retryAtMs-nowMs;if(delayMs<0)return;const delaySeconds=Math.ceil(delayMs/1000);return Number.isSafeInteger(delaySeconds)?delaySeconds:undefined}var LANGUAGE_SCHEMA=z.object({id:z.string(),name:z.string(),display_name:z.string(),aliases:z.array(z.string()),search_priority:z.number().optional()});var LANGUAGES_SCHEMA=z.array(LANGUAGE_SCHEMA);class GitHitsServiceImpl{apiUrl;token;fetchFn;fetchTimeoutMs;runtime;constructor(apiUrl,token,fetchFn,fetchTimeoutMs=undefined,runtime={}){this.apiUrl=apiUrl;this.token=token;this.fetchFn=fetchFn;this.fetchTimeoutMs=fetchTimeoutMs;this.runtime=runtime}async search(params){return withTelemetrySpan("githits.search.request",async()=>{const response=await this.request("/search",{method:"POST",headers:this.headers(),body:JSON.stringify({query:params.query,language:params.language,license_mode:params.licenseMode??"strict",include_explanation:params.includeExplanation??false})},this.runtime.exampleRequestTimeoutMs??DEFAULT_EXAMPLE_REQUEST_TIMEOUT_MS);if(!response.ok){throw await this.createError(response)}return response.text()})}async getLanguages(){return withTelemetrySpan("githits.languages.request",async()=>{const response=await this.request("/languages",{headers:this.headers()});if(!response.ok){throw await this.createError(response)}return this.parseLanguages(response)})}async searchLanguages(query,limit=5){return withTelemetrySpan("githits.languages.search.request",async()=>{const params=new URLSearchParams({query,limit:String(limit)});const response=await this.request(`/languages?${params.toString()}`,{headers:this.headers()});if(!response.ok){throw await this.createError(response)}return this.parseLanguages(response)})}async submitFeedback(params){return withTelemetrySpan("githits.feedback.request",async()=>{const response=await this.request("/feedbacks",{method:"POST",headers:this.headers(),body:JSON.stringify({...params.exampleId!==undefined&&{example_id:params.exampleId},...params.solutionId!==undefined&&{solution_id:params.solutionId},accepted:params.accepted,feedback_text:params.feedbackText??null,...params.toolName!==undefined&&{tool_name:params.toolName}})});if(!response.ok){throw await this.createError(response)}return{success:true,message:"Feedback submitted successfully"}})}headers(){return{...this.runtime.clientHeaders?.(),Authorization:`Bearer ${this.token}`,"Content-Type":"application/json","User-Agent":this.runtime.userAgent??"githits-cli"}}fetchOptions(defaultTimeoutMs=DEFAULT_FETCH_TIMEOUT_MS){return{fetchFn:this.fetchFn,timeoutMs:this.fetchTimeoutMs??defaultTimeoutMs}}async request(path,init,defaultTimeoutMs=DEFAULT_FETCH_TIMEOUT_MS){const apiUrl=validateServiceUrl(this.apiUrl,"GITHITS_API_URL");const fetchOptions=this.fetchOptions(defaultTimeoutMs);try{return await fetchWithTimeout(`${apiUrl.replace(/\/+$/,"")}${path}`,init,fetchOptions)}catch(cause){if(isFetchTimeoutError(cause)||isAbortError(cause)){throw new GitHitsRequestTimeoutError(fetchOptions.timeoutMs,cause)}if(cause instanceof TypeError){throw new Error("Could not connect to GitHits. Check your connection and GITHITS_API_URL, then try again.",{cause})}throw cause}}async parseLanguages(response){let data;try{data=await response.json()}catch(cause){throw new Error("GitHits returned an invalid languages response.",{cause})}const parsed=LANGUAGES_SCHEMA.safeParse(data);if(!parsed.success){throw new Error("GitHits returned an invalid languages response.",{cause:parsed.error})}return parsed.data}async createError(response){const status=response.status;const body=await response.text().catch(()=>"");const detail=parseHttpErrorDetail(body,["detail"]);throwIfTermsAcceptanceRequired(body);switch(status){case 401:return new AuthenticationError(SERVER_AUTHENTICATION_REJECTED_MESSAGE,"server");case 403:return new Error("Access denied.");case 404:return new Error(detail||"Resource not found.");case 429:return new ApiRateLimitError(undefined,parseRetryAfterSeconds(response.headers.get("Retry-After"),Date.now()));default:{if(status>=500){return new Error(`Server error (${status}). Try again shortly.${detail?` ${detail}`:""}`)}return new Error(`Request failed with status ${status}.${detail?` ${detail}`:""}`)}}}}class GitHitsRequestTimeoutError extends FetchTimeoutError{constructor(timeoutMs,cause){super(timeoutMs,{cause});this.name="GitHitsRequestTimeoutError";this.message="Request to GitHits timed out. Try again."}}function isAbortError(error){return error instanceof Error&&error.name==="AbortError"}async function executeWithTokenRefresh(options){const token=await options.getToken();if(!token){throw new AuthenticationError(LOCAL_AUTHENTICATION_MISSING_MESSAGE,"local")}try{return await options.executeWithToken(token)}catch(error){if(token.startsWith("ghi-")||!options.shouldRefresh(error)){throw error}const refreshedToken=await options.forceRefresh();if(!refreshedToken){throw error}return options.executeWithToken(refreshedToken)}}var INDEXING_WAIT_HINT="Wait until ready with CLI `--wait 60000` or MCP `wait_timeout_ms: 60000`.";class CodeNavigationAccessError extends Error{constructor(message){super(message);this.name="CodeNavigationAccessError"}}class CodeNavigationGraphQLError extends Error{code;constructor(message,code){super(message);this.code=code;this.name="CodeNavigationGraphQLError"}}class CodeNavigationIndexingError extends Error{indexingRef;availableVersions;availableRefs;targetResolution;indexingEstimate;hint;constructor(message,indexingRef,availableVersions,availableRefs,targetResolution=undefined,indexingEstimate=undefined,hint=undefined){super(message);this.indexingRef=indexingRef;this.availableVersions=availableVersions;this.availableRefs=availableRefs;this.targetResolution=targetResolution;this.indexingEstimate=indexingEstimate;this.hint=hint;this.name="CodeNavigationIndexingError"}}class CodeNavigationUnresolvableError extends Error{constructor(message){super(message);this.name="CodeNavigationUnresolvableError"}}class MalformedCodeNavigationResponseError extends Error{constructor(message){super(message);this.name="MalformedCodeNavigationResponseError"}}class CodeNavigationTargetNotFoundError extends Error{availableVersions;repoUrl;requestedRef;metadata;constructor(message,availableVersions,repoUrl,requestedRef,metadata=undefined){super(message);this.availableVersions=availableVersions;this.repoUrl=repoUrl;this.requestedRef=requestedRef;this.metadata=metadata;this.name="CodeNavigationTargetNotFoundError"}}class CodeNavigationFileNotFoundError extends Error{filePath;constructor(message,filePath){super(message);this.filePath=filePath;this.name="CodeNavigationFileNotFoundError"}}class CodeNavigationVersionNotFoundError extends Error{packageName;requestedVersion;latestIndexed;availableVersions;metadata;constructor(message,packageName,requestedVersion,latestIndexed,availableVersions,metadata=undefined){super(message);this.packageName=packageName;this.requestedVersion=requestedVersion;this.latestIndexed=latestIndexed;this.availableVersions=availableVersions;this.metadata=metadata;this.name="CodeNavigationVersionNotFoundError"}}class CodeNavigationRefNotFoundError extends Error{repoUrl;requestedRef;availableRefs;suggestedRefs;metadata;constructor(message,repoUrl,requestedRef,availableRefs,suggestedRefs,metadata=undefined){super(message);this.repoUrl=repoUrl;this.requestedRef=requestedRef;this.availableRefs=availableRefs;this.suggestedRefs=suggestedRefs;this.metadata=metadata;this.name="CodeNavigationRefNotFoundError"}}class CodeNavigationValidationError extends Error{constructor(message){super(message);this.name="CodeNavigationValidationError"}}class CodeNavigationFeatureFlagRequiredError extends Error{constructor(message){super(message);this.name="CodeNavigationFeatureFlagRequiredError"}}class CodeNavigationNetworkError extends Error{constructor(message,options){super(message,options);this.name="CodeNavigationNetworkError"}}class CodeNavigationBackendError extends Error{status;graphqlCode;retryable;metadata;constructor(message,status,graphqlCode,retryable,metadata=undefined){super(message);this.status=status;this.graphqlCode=graphqlCode;this.retryable=retryable;this.metadata=metadata;this.name="CodeNavigationBackendError"}}var TARGET_RESOLUTION_AVAILABLE_REFS_SELECTION=`
@@ -1592,7 +1592,7 @@ fragment ResolveTargetJsonFields on TargetResolutionCandidate {
1592
1592
  });
1593
1593
  }
1594
1594
  })();
1595
- </script>`;var RETRY_CTA=ctaBlock("To try again, run these commands in your terminal:",["npx githits@latest logout","npx githits@latest login"]);var HELP_CTA=ctaBlock("Explore available commands with:",["npx githits@latest --help"]);function escapeHtml(text){return text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}var METADATA_FILE="metadata.json";var DIR_MODE3=448;var FILE_MODE3=384;class AuthSessionMetadataStorage{fs;configDir;metadataPath;constructor(fs,configDir){this.fs=fs;this.configDir=configDir??getAuthFileStorageDir(fs);this.metadataPath=fs.joinPath(this.configDir,METADATA_FILE)}async load(baseUrl2){const stored=await this.loadFile();if(!stored)return null;const metadata=stored.sessions[normalizeBaseUrl(baseUrl2)]??null;return isAuthSessionMetadata(metadata)?metadata:null}async saveFromTokens(baseUrl2,tokens){const stored=await this.loadFile()??{version:1,sessions:{}};stored.sessions[normalizeBaseUrl(baseUrl2)]={createdAt:tokens.createdAt,expiresAt:tokens.expiresAt,updatedAt:new Date().toISOString()};await this.fs.ensureDir(this.configDir,DIR_MODE3);await this.fs.atomicWriteFile(this.metadataPath,JSON.stringify(stored,null,2),FILE_MODE3)}async clear(baseUrl2){const stored=await this.loadFile();if(!stored)return;delete stored.sessions[normalizeBaseUrl(baseUrl2)];if(Object.keys(stored.sessions).length===0){await this.fs.deleteFile(this.metadataPath);return}await this.fs.atomicWriteFile(this.metadataPath,JSON.stringify(stored,null,2),FILE_MODE3)}async loadFile(){if(!await this.fs.exists(this.metadataPath))return null;try{const content=await this.fs.readFile(this.metadataPath);const data=JSON.parse(content);if(data.version!==1||!data.sessions)return null;return data}catch{return null}}}function isAuthSessionMetadata(value){if(value===null||typeof value!=="object")return false;return typeof value.createdAt==="string"&&value.createdAt.length>0&&typeof value.updatedAt==="string"&&value.updatedAt.length>0&&(value.expiresAt===null||typeof value.expiresAt==="string"&&value.expiresAt.length>0)}import open from"open";class BrowserServiceImpl{async open(url){await open(url)}}var WINDOWS_MAX_ENTRY_SIZE=1200;var CHUNKED_PREFIX="CHUNKED:";var MAX_CHUNK_COUNT=100;function chunkKey(account,writeId,index){return`${account}:chunk:${writeId}:${index}`}function parseChunkedSentinel(value){if(!value.startsWith(CHUNKED_PREFIX))return null;const rest=value.slice(CHUNKED_PREFIX.length);const colonIndex=rest.indexOf(":");if(colonIndex===-1)return null;const writeId=rest.slice(0,colonIndex);if(writeId.length===0)return null;const countStr=rest.slice(colonIndex+1);const count=Number(countStr);if(!Number.isInteger(count)||count<=0)return null;return{writeId,count}}function splitIntoChunks(value,maxSize){if(value.length===0)return[""];const chunks=[];for(let offset=0;offset<value.length;offset+=maxSize){chunks.push(value.slice(offset,offset+maxSize))}return chunks}function generateWriteId(){let id;do{id=Math.random().toString(36).slice(2,8)}while(id.length<6);return id}class ChunkingKeyringService{inner;maxEntrySize;constructor(inner,maxEntrySize=WINDOWS_MAX_ENTRY_SIZE){this.inner=inner;this.maxEntrySize=maxEntrySize}getPassword(service,account){const value=this.inner.getPassword(service,account);if(value===null)return null;if(!value.startsWith(CHUNKED_PREFIX))return value;const sentinel=parseChunkedSentinel(value);if(sentinel===null)return null;const chunks=[];for(let i=0;i<sentinel.count;i++){const chunk=this.inner.getPassword(service,chunkKey(account,sentinel.writeId,i));if(chunk===null){console.error(`Warning: Incomplete chunked keychain entry for "${account}" (missing chunk ${i} of ${sentinel.count}). Treating as missing.`);return null}chunks.push(chunk)}return chunks.join("")}setPassword(service,account,password){const oldValue=this.readOldSentinel(service,account);if(password.length<=this.maxEntrySize){this.inner.setPassword(service,account,password)}else{const chunks=splitIntoChunks(password,this.maxEntrySize);if(chunks.length>MAX_CHUNK_COUNT){throw new Error(`Value requires ${chunks.length} chunks, exceeding maximum of ${MAX_CHUNK_COUNT}. `+`This likely indicates a bug — credential data should not be this large.`)}const writeId=generateWriteId();for(const[i,chunk]of chunks.entries()){this.inner.setPassword(service,chunkKey(account,writeId,i),chunk)}this.inner.setPassword(service,account,`${CHUNKED_PREFIX}${writeId}:${chunks.length}`)}if(oldValue!==null){this.deleteChunkEntries(service,account,oldValue)}}deletePassword(service,account){const oldValue=this.readOldSentinel(service,account);if(oldValue!==null){this.deleteChunkEntries(service,account,oldValue)}return this.inner.deletePassword(service,account)}readOldSentinel(service,account){try{const value=this.inner.getPassword(service,account);if(value===null)return null;return parseChunkedSentinel(value)}catch{return null}}deleteChunkEntries(service,account,sentinel){for(let i=0;i<sentinel.count;i++){try{this.inner.deletePassword(service,chunkKey(account,sentinel.writeId,i))}catch{}}}}import{randomUUID as randomUUID2}from"node:crypto";import{mkdir,readdir,readFile,rename,rmdir,stat,unlink,writeFile}from"node:fs/promises";import{homedir}from"node:os";import{dirname,join}from"node:path";class FileSystemServiceImpl{async readFile(path){return readFile(path,"utf-8")}async writeFile(path,contents,mode){await writeFile(path,contents,{mode})}async deleteFile(path){try{await unlink(path)}catch(error){if(error.code!=="ENOENT"){throw error}}}async deleteDirIfEmpty(path){try{await rmdir(path)}catch(error){const code=error.code;if(code!=="ENOENT"&&code!=="ENOTEMPTY"&&code!=="EEXIST"){throw error}}}async exists(path){try{await stat(path);return true}catch{return false}}async ensureDir(path,mode){await mkdir(path,{recursive:true,mode})}getHomeDir(){return homedir()}joinPath(...segments){return join(...segments)}getCwd(){return process.cwd()}getDirname(path){return dirname(path)}async readdir(path){return readdir(path)}async isDirectory(path){try{const stats=await stat(path);return stats.isDirectory()}catch{return false}}async atomicWriteFile(path,contents,maximumMode){const tmpPath=`${path}.${process.pid}.${randomUUID2()}.tmp`;const normalizedMaximum=maximumMode===undefined?undefined:maximumMode&511;let mode=normalizedMaximum??384;try{const existing=await stat(path);const existingMode=existing.mode&511;mode=normalizedMaximum===undefined?existingMode:existingMode&normalizedMaximum}catch{}try{await writeFile(tmpPath,contents,{mode});await rename(tmpPath,path)}catch(error){try{await unlink(tmpPath)}catch{}throw error}}}var SERVICE_NAME="githits";var TOKEN_PREFIX="v1:tokens:";var CLIENT_PREFIX="v1:client:";function parseJsonOrNull2(json){if(json===null)return null;try{const parsed=JSON.parse(json);if(typeof parsed!=="object"||parsed===null)return null;return parsed}catch{return null}}function isValidTokenData(data){if(typeof data!=="object"||data===null)return false;const d=data;return typeof d.accessToken==="string"&&d.accessToken.length>0&&typeof d.refreshToken==="string"&&d.refreshToken.length>0&&typeof d.createdAt==="string"&&d.createdAt.length>0&&(d.expiresAt===null||typeof d.expiresAt==="string"&&d.expiresAt.length>0)}function isValidClientRegistration(data){if(typeof data!=="object"||data===null)return false;const d=data;return typeof d.clientId==="string"&&d.clientId.length>0&&typeof d.clientSecret==="string"&&d.clientSecret.length>0&&typeof d.redirectUri==="string"&&d.redirectUri.length>0&&typeof d.registeredAt==="string"&&d.registeredAt.length>0}class KeychainAuthStorage{keyring;constructor(keyring){this.keyring=keyring}async loadTokens(baseUrl2){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;const json=this.keyring.getPassword(SERVICE_NAME,key);const data=parseJsonOrNull2(json);if(data!==null&&!isValidTokenData(data))return null;return data}async saveTokens(baseUrl2,data){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.setPassword(SERVICE_NAME,key,JSON.stringify(data))}async saveTokensIfUnchanged(baseUrl2,expected,data){const current=await this.loadTokens(baseUrl2);if(!sameTokenData(current,expected))return false;await this.saveTokens(baseUrl2,data);return true}async clearTokens(baseUrl2){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.deletePassword(SERVICE_NAME,key)}async clearTokensIfUnchanged(baseUrl2,expected){const current=await this.loadTokens(baseUrl2);if(!sameTokenData(current,expected))return false;await this.clearTokens(baseUrl2);return true}clearActiveTokensIfUnchanged(baseUrl2,expected){return this.clearTokensIfUnchanged(baseUrl2,expected)}async loadClient(baseUrl2){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;const json=this.keyring.getPassword(SERVICE_NAME,key);const data=parseJsonOrNull2(json);if(data!==null&&!isValidClientRegistration(data))return null;return data}async saveClient(baseUrl2,data){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.setPassword(SERVICE_NAME,key,JSON.stringify(data))}async clearClient(baseUrl2){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.deletePassword(SERVICE_NAME,key)}clearActiveClient(baseUrl2){return this.clearClient(baseUrl2)}async saveAuthSession(baseUrl2,client,tokens){await this.saveClient(baseUrl2,client);await this.saveTokens(baseUrl2,tokens)}async clearAuthSession(baseUrl2){await clearAuthSessionBestEffort(()=>this.clearTokens(baseUrl2),()=>this.clearClient(baseUrl2))}getStorageLocation(){switch(process.platform){case"darwin":return"macOS Keychain (githits)";case"win32":return"Windows Credential Manager (githits)";default:return"System keychain (githits)"}}}import{Entry}from"@napi-rs/keyring";class KeychainUnavailableError extends Error{constructor(message,cause){super(message);this.name="KeychainUnavailableError";this.cause=cause}}function wrapKeyringError(error){const message=error instanceof Error?error.message:String(error);throw new KeychainUnavailableError(`System keychain unavailable: ${message}`,error)}class KeyringServiceImpl{getPassword(service,account){try{return new Entry(service,account).getPassword()}catch(error){wrapKeyringError(error)}}setPassword(service,account,password){try{new Entry(service,account).setPassword(password)}catch(error){wrapKeyringError(error)}}deletePassword(service,account){try{return new Entry(service,account).deleteCredential()}catch(error){wrapKeyringError(error)}}}import{AsyncLocalStorage}from"node:async_hooks";import{execFile}from"node:child_process";import{randomUUID as randomUUID3}from"node:crypto";import{mkdir as mkdir2,readFile as readFile2,rm,writeFile as writeFile2}from"node:fs/promises";import{dirname as dirname2}from"node:path";import{promisify}from"node:util";var LOCK_DIR="auth.lock";var LOCK_TIMEOUT_MS=DEFAULT_FETCH_TIMEOUT_MS*2+1e4;var LOCK_RETRY_MS=25;var ORPHANED_LOCK_MS=5000;var OWNER_FILE="owner.json";var execFileAsync=promisify(execFile);class AuthStorageLockTimeoutError extends Error{constructor(message){super(message);this.name="AuthStorageLockTimeoutError"}}function withAuthStorageLock(storage,fn){return storage.withAuthStorageLock(fn)}class LockedAuthStorage{storage;lockPath;lockTimeoutMs;isOwnerAlive;processStartedAtLookup;currentProcessStartedAtPromise;lockContext=new AsyncLocalStorage;currentOwner=null;lockLoads;constructor(storage,fileSystemService,options={}){this.storage=storage;this.lockTimeoutMs=options.lockTimeoutMs??LOCK_TIMEOUT_MS;this.processStartedAtLookup=options.getProcessStartedAt??getProcessStartedAt;this.isOwnerAlive=options.isOwnerAlive??((pid,processStartedAt)=>isOriginalProcessAlive(pid,processStartedAt,pid===process.pid?()=>this.getCurrentProcessStartedAt():this.processStartedAtLookup));this.lockLoads=storage.requiresLoadLock===true;this.lockPath=fileSystemService.joinPath(getAuthLockDir(fileSystemService),LOCK_DIR)}loadTokens(baseUrl2){return this.lockLoads?this.withAuthStorageLock(()=>this.storage.loadTokens(baseUrl2)):this.storage.loadTokens(baseUrl2)}saveTokens(baseUrl2,data){return this.withAuthStorageLock(()=>this.storage.saveTokens(baseUrl2,data))}saveTokensIfUnchanged(baseUrl2,expected,data){return this.withAuthStorageLock(()=>this.storage.saveTokensIfUnchanged(baseUrl2,expected,data))}clearTokens(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearTokens(baseUrl2))}clearTokensIfUnchanged(baseUrl2,expected){return this.withAuthStorageLock(()=>this.storage.clearTokensIfUnchanged(baseUrl2,expected))}clearActiveTokensIfUnchanged(baseUrl2,expected){return this.withAuthStorageLock(()=>this.storage.clearActiveTokensIfUnchanged(baseUrl2,expected))}loadClient(baseUrl2){return this.lockLoads?this.withAuthStorageLock(()=>this.storage.loadClient(baseUrl2)):this.storage.loadClient(baseUrl2)}saveClient(baseUrl2,data){return this.withAuthStorageLock(()=>this.storage.saveClient(baseUrl2,data))}clearClient(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearClient(baseUrl2))}clearActiveClient(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearActiveClient(baseUrl2))}saveAuthSession(baseUrl2,client,tokens){return this.withAuthStorageLock(()=>this.storage.saveAuthSession(baseUrl2,client,tokens))}clearAuthSession(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearAuthSession(baseUrl2))}getStorageLocation(){return this.storage.getStorageLocation()}async withAuthStorageLock(fn){const ownerId=this.lockContext.getStore();if(ownerId&&this.currentOwner?.id===ownerId){return fn()}await this.acquireLock();const acquiredOwnerId=this.currentOwner?.id;try{return await this.lockContext.run(acquiredOwnerId??"",fn)}finally{await this.releaseLock()}}async acquireLock(){const processStartedAt=await this.getCurrentProcessStartedAt();const startedAt=Date.now();await mkdir2(dirname2(this.lockPath),{recursive:true,mode:448});while(true){try{await mkdir2(this.lockPath,{recursive:false,mode:448});try{await this.writeOwner(processStartedAt)}catch(error){this.currentOwner=null;const code=error.code;if(code==="EEXIST"||code==="ENOENT"){if(Date.now()-startedAt>=this.lockTimeoutMs){throw this.createLockTimeoutError()}await sleep(LOCK_RETRY_MS);continue}await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return});throw error}return}catch(error){if(error.code!=="EEXIST")throw error;await this.reclaimStaleLock();if(Date.now()-startedAt>=this.lockTimeoutMs){throw this.createLockTimeoutError()}await sleep(LOCK_RETRY_MS)}}}createLockTimeoutError(){return new AuthStorageLockTimeoutError(`Timed out waiting for GitHits auth storage lock at ${this.lockPath}. If no githits process is running, remove this directory and retry.`)}getCurrentProcessStartedAt(){if(!this.currentProcessStartedAtPromise){const lookup=this.processStartedAtLookup(process.pid);this.currentProcessStartedAtPromise=lookup;lookup.then((startedAt)=>{if(startedAt===null&&this.currentProcessStartedAtPromise===lookup){this.currentProcessStartedAtPromise=undefined}},()=>{if(this.currentProcessStartedAtPromise===lookup){this.currentProcessStartedAtPromise=undefined}})}return this.currentProcessStartedAtPromise}async writeOwner(processStartedAt){const owner={id:randomUUID3(),pid:process.pid,createdAt:new Date().toISOString(),processStartedAt};await writeFile2(this.ownerPath(),JSON.stringify(owner),{mode:384,flag:"wx"});this.currentOwner=owner}async reclaimStaleLock(){const owner=await this.readOwner();if(!owner){await this.reclaimOldOwnerlessLock();return}const ownerDead=!await this.isOwnerAlive(owner.pid,owner.processStartedAt);if(!ownerDead)return;const currentOwner=await this.readOwner();if(!currentOwner||currentOwner.id!==owner.id)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}async reclaimOldOwnerlessLock(){const createdAtMs=await lockCreatedAtMs(this.lockPath);if(Date.now()-createdAtMs<ORPHANED_LOCK_MS)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}async readOwner(){try{const raw=await readFile2(this.ownerPath(),"utf8");const parsed=JSON.parse(raw);if(typeof parsed.id!=="string"||typeof parsed.pid!=="number"||typeof parsed.createdAt!=="string"||!(typeof parsed.processStartedAt==="string"||parsed.processStartedAt===null)){return null}return{id:parsed.id,pid:parsed.pid,createdAt:parsed.createdAt,processStartedAt:parsed.processStartedAt}}catch{return null}}async releaseLock(){const owner=this.currentOwner;this.currentOwner=null;if(!owner)return;const currentOwner=await this.readOwner();if(!currentOwner||currentOwner.id!==owner.id)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}ownerPath(){return`${this.lockPath}/${OWNER_FILE}`}}function sleep(ms){return new Promise((resolve)=>setTimeout(resolve,ms))}async function isOriginalProcessAlive(pid,processStartedAt,getStartedAt){if(!isProcessAlive(pid))return false;if(!processStartedAt)return true;return await getStartedAt(pid)===processStartedAt}function isProcessAlive(pid){if(pid<=0)return false;try{process.kill(pid,0);return true}catch(error){const code=error.code;return code==="EPERM"}}async function getProcessStartedAt(pid){try{if(process.platform==="win32"){const{stdout:stdout2}=await execFileAsync("powershell.exe",["-NoProfile","-Command",`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString('o')`]);return stdout2.trim()||null}const{stdout}=await execFileAsync("ps",["-p",String(pid),"-o","lstart="]);const parsed=Date.parse(stdout.trim());return Number.isNaN(parsed)?null:new Date(parsed).toISOString()}catch{return null}}async function lockCreatedAtMs(path){try{const{stat:stat2}=await import("node:fs/promises");return(await stat2(path)).mtimeMs}catch{return 0}}class AuthStoragePolicyError extends Error{constructor(message){super(message);this.name="AuthStoragePolicyError"}}function createFileAuthStorageGuidance(configPath){return`OAuth credentials were not saved to plaintext file storage.
1595
+ </script>`;var RETRY_CTA=ctaBlock("To try again, run these commands in your terminal:",["npx githits@latest logout","npx githits@latest login"]);var HELP_CTA=ctaBlock("Explore available commands with:",["npx githits@latest --help"]);function escapeHtml(text){return text.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}var METADATA_FILE="metadata.json";var DIR_MODE3=448;var FILE_MODE3=384;class AuthSessionMetadataStorage{fs;configDir;metadataPath;constructor(fs,configDir){this.fs=fs;this.configDir=configDir??getAuthFileStorageDir(fs);this.metadataPath=fs.joinPath(this.configDir,METADATA_FILE)}async load(baseUrl2){const stored=await this.loadFile();if(!stored)return null;const metadata=stored.sessions[normalizeBaseUrl(baseUrl2)]??null;return isAuthSessionMetadata(metadata)?metadata:null}async saveFromTokens(baseUrl2,tokens){const stored=await this.loadFile()??{version:1,sessions:{}};stored.sessions[normalizeBaseUrl(baseUrl2)]={createdAt:tokens.createdAt,expiresAt:tokens.expiresAt,updatedAt:new Date().toISOString()};await this.fs.ensureDir(this.configDir,DIR_MODE3);await this.fs.atomicWriteFile(this.metadataPath,JSON.stringify(stored,null,2),FILE_MODE3)}async clear(baseUrl2){const stored=await this.loadFile();if(!stored)return;delete stored.sessions[normalizeBaseUrl(baseUrl2)];if(Object.keys(stored.sessions).length===0){await this.fs.deleteFile(this.metadataPath);return}await this.fs.atomicWriteFile(this.metadataPath,JSON.stringify(stored,null,2),FILE_MODE3)}async loadFile(){if(!await this.fs.exists(this.metadataPath))return null;try{const content=await this.fs.readFile(this.metadataPath);const data=JSON.parse(content);if(data.version!==1||!data.sessions)return null;return data}catch{return null}}}function isAuthSessionMetadata(value){if(value===null||typeof value!=="object")return false;return typeof value.createdAt==="string"&&value.createdAt.length>0&&typeof value.updatedAt==="string"&&value.updatedAt.length>0&&(value.expiresAt===null||typeof value.expiresAt==="string"&&value.expiresAt.length>0)}import open from"open";class BrowserServiceImpl{async open(url){await open(url)}}var WINDOWS_MAX_ENTRY_SIZE=1200;var CHUNKED_PREFIX="CHUNKED:";var MAX_CHUNK_COUNT=100;function chunkKey(account,writeId,index){return`${account}:chunk:${writeId}:${index}`}function parseChunkedSentinel(value){if(!value.startsWith(CHUNKED_PREFIX))return null;const rest=value.slice(CHUNKED_PREFIX.length);const colonIndex=rest.indexOf(":");if(colonIndex===-1)return null;const writeId=rest.slice(0,colonIndex);if(writeId.length===0)return null;const countStr=rest.slice(colonIndex+1);const count=Number(countStr);if(!Number.isInteger(count)||count<=0)return null;return{writeId,count}}function splitIntoChunks(value,maxSize){if(value.length===0)return[""];const chunks=[];for(let offset=0;offset<value.length;offset+=maxSize){chunks.push(value.slice(offset,offset+maxSize))}return chunks}function generateWriteId(){let id;do{id=Math.random().toString(36).slice(2,8)}while(id.length<6);return id}class ChunkingKeyringService{inner;maxEntrySize;constructor(inner,maxEntrySize=WINDOWS_MAX_ENTRY_SIZE){this.inner=inner;this.maxEntrySize=maxEntrySize}getPassword(service,account){const value=this.inner.getPassword(service,account);if(value===null)return null;if(!value.startsWith(CHUNKED_PREFIX))return value;const sentinel=parseChunkedSentinel(value);if(sentinel===null)return null;const chunks=[];for(let i=0;i<sentinel.count;i++){const chunk=this.inner.getPassword(service,chunkKey(account,sentinel.writeId,i));if(chunk===null){console.error(`Warning: Incomplete chunked keychain entry for "${account}" (missing chunk ${i} of ${sentinel.count}). Treating as missing.`);return null}chunks.push(chunk)}return chunks.join("")}setPassword(service,account,password){const oldValue=this.readOldSentinel(service,account);if(password.length<=this.maxEntrySize){this.inner.setPassword(service,account,password)}else{const chunks=splitIntoChunks(password,this.maxEntrySize);if(chunks.length>MAX_CHUNK_COUNT){throw new Error(`Value requires ${chunks.length} chunks, exceeding maximum of ${MAX_CHUNK_COUNT}. `+`This likely indicates a bug — credential data should not be this large.`)}const writeId=generateWriteId();for(const[i,chunk]of chunks.entries()){this.inner.setPassword(service,chunkKey(account,writeId,i),chunk)}this.inner.setPassword(service,account,`${CHUNKED_PREFIX}${writeId}:${chunks.length}`)}if(oldValue!==null){this.deleteChunkEntries(service,account,oldValue)}}deletePassword(service,account){const oldValue=this.readOldSentinel(service,account);if(oldValue!==null){this.deleteChunkEntries(service,account,oldValue)}return this.inner.deletePassword(service,account)}readOldSentinel(service,account){try{const value=this.inner.getPassword(service,account);if(value===null)return null;return parseChunkedSentinel(value)}catch{return null}}deleteChunkEntries(service,account,sentinel){for(let i=0;i<sentinel.count;i++){try{this.inner.deletePassword(service,chunkKey(account,sentinel.writeId,i))}catch{}}}}import{randomUUID as randomUUID2}from"node:crypto";import{mkdir,mkdtemp,readdir,readFile,rename,rmdir,stat,unlink,writeFile}from"node:fs/promises";import{homedir,tmpdir}from"node:os";import{dirname,join}from"node:path";class FileSystemServiceImpl{async readFile(path){return readFile(path,"utf-8")}async writeFile(path,contents,mode){await writeFile(path,contents,{mode})}async deleteFile(path){try{await unlink(path)}catch(error){if(error.code!=="ENOENT"){throw error}}}async deleteDirIfEmpty(path){try{await rmdir(path)}catch(error){const code=error.code;if(code!=="ENOENT"&&code!=="ENOTEMPTY"&&code!=="EEXIST"){throw error}}}async exists(path){try{await stat(path);return true}catch{return false}}async ensureDir(path,mode){await mkdir(path,{recursive:true,mode})}async createTempDir(prefix){return mkdtemp(join(tmpdir(),prefix))}getHomeDir(){return homedir()}joinPath(...segments){return join(...segments)}getCwd(){return process.cwd()}getDirname(path){return dirname(path)}async readdir(path){return readdir(path)}async isDirectory(path){try{const stats=await stat(path);return stats.isDirectory()}catch{return false}}async atomicWriteFile(path,contents,maximumMode){const tmpPath=`${path}.${process.pid}.${randomUUID2()}.tmp`;const normalizedMaximum=maximumMode===undefined?undefined:maximumMode&511;let mode=normalizedMaximum??384;try{const existing=await stat(path);const existingMode=existing.mode&511;mode=normalizedMaximum===undefined?existingMode:existingMode&normalizedMaximum}catch{}try{await writeFile(tmpPath,contents,{mode});await rename(tmpPath,path)}catch(error){try{await unlink(tmpPath)}catch{}throw error}}}var SERVICE_NAME="githits";var TOKEN_PREFIX="v1:tokens:";var CLIENT_PREFIX="v1:client:";function parseJsonOrNull2(json){if(json===null)return null;try{const parsed=JSON.parse(json);if(typeof parsed!=="object"||parsed===null)return null;return parsed}catch{return null}}function isValidTokenData(data){if(typeof data!=="object"||data===null)return false;const d=data;return typeof d.accessToken==="string"&&d.accessToken.length>0&&typeof d.refreshToken==="string"&&d.refreshToken.length>0&&typeof d.createdAt==="string"&&d.createdAt.length>0&&(d.expiresAt===null||typeof d.expiresAt==="string"&&d.expiresAt.length>0)}function isValidClientRegistration(data){if(typeof data!=="object"||data===null)return false;const d=data;return typeof d.clientId==="string"&&d.clientId.length>0&&typeof d.clientSecret==="string"&&d.clientSecret.length>0&&typeof d.redirectUri==="string"&&d.redirectUri.length>0&&typeof d.registeredAt==="string"&&d.registeredAt.length>0}class KeychainAuthStorage{keyring;constructor(keyring){this.keyring=keyring}async loadTokens(baseUrl2){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;const json=this.keyring.getPassword(SERVICE_NAME,key);const data=parseJsonOrNull2(json);if(data!==null&&!isValidTokenData(data))return null;return data}async saveTokens(baseUrl2,data){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.setPassword(SERVICE_NAME,key,JSON.stringify(data))}async saveTokensIfUnchanged(baseUrl2,expected,data){const current=await this.loadTokens(baseUrl2);if(!sameTokenData(current,expected))return false;await this.saveTokens(baseUrl2,data);return true}async clearTokens(baseUrl2){const key=`${TOKEN_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.deletePassword(SERVICE_NAME,key)}async clearTokensIfUnchanged(baseUrl2,expected){const current=await this.loadTokens(baseUrl2);if(!sameTokenData(current,expected))return false;await this.clearTokens(baseUrl2);return true}clearActiveTokensIfUnchanged(baseUrl2,expected){return this.clearTokensIfUnchanged(baseUrl2,expected)}async loadClient(baseUrl2){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;const json=this.keyring.getPassword(SERVICE_NAME,key);const data=parseJsonOrNull2(json);if(data!==null&&!isValidClientRegistration(data))return null;return data}async saveClient(baseUrl2,data){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.setPassword(SERVICE_NAME,key,JSON.stringify(data))}async clearClient(baseUrl2){const key=`${CLIENT_PREFIX}${normalizeBaseUrl(baseUrl2)}`;this.keyring.deletePassword(SERVICE_NAME,key)}clearActiveClient(baseUrl2){return this.clearClient(baseUrl2)}async saveAuthSession(baseUrl2,client,tokens){await this.saveClient(baseUrl2,client);await this.saveTokens(baseUrl2,tokens)}async clearAuthSession(baseUrl2){await clearAuthSessionBestEffort(()=>this.clearTokens(baseUrl2),()=>this.clearClient(baseUrl2))}getStorageLocation(){switch(process.platform){case"darwin":return"macOS Keychain (githits)";case"win32":return"Windows Credential Manager (githits)";default:return"System keychain (githits)"}}}import{Entry}from"@napi-rs/keyring";class KeychainUnavailableError extends Error{constructor(message,cause){super(message);this.name="KeychainUnavailableError";this.cause=cause}}function wrapKeyringError(error){const message=error instanceof Error?error.message:String(error);throw new KeychainUnavailableError(`System keychain unavailable: ${message}`,error)}class KeyringServiceImpl{getPassword(service,account){try{return new Entry(service,account).getPassword()}catch(error){wrapKeyringError(error)}}setPassword(service,account,password){try{new Entry(service,account).setPassword(password)}catch(error){wrapKeyringError(error)}}deletePassword(service,account){try{return new Entry(service,account).deleteCredential()}catch(error){wrapKeyringError(error)}}}import{AsyncLocalStorage}from"node:async_hooks";import{execFile}from"node:child_process";import{randomUUID as randomUUID3}from"node:crypto";import{mkdir as mkdir2,readFile as readFile2,rm,writeFile as writeFile2}from"node:fs/promises";import{dirname as dirname2}from"node:path";import{promisify}from"node:util";var LOCK_DIR="auth.lock";var LOCK_TIMEOUT_MS=DEFAULT_FETCH_TIMEOUT_MS*2+1e4;var LOCK_RETRY_MS=25;var ORPHANED_LOCK_MS=5000;var OWNER_FILE="owner.json";var execFileAsync=promisify(execFile);class AuthStorageLockTimeoutError extends Error{constructor(message){super(message);this.name="AuthStorageLockTimeoutError"}}function withAuthStorageLock(storage,fn){return storage.withAuthStorageLock(fn)}class LockedAuthStorage{storage;lockPath;lockTimeoutMs;isOwnerAlive;processStartedAtLookup;currentProcessStartedAtPromise;lockContext=new AsyncLocalStorage;currentOwner=null;lockLoads;constructor(storage,fileSystemService,options={}){this.storage=storage;this.lockTimeoutMs=options.lockTimeoutMs??LOCK_TIMEOUT_MS;this.processStartedAtLookup=options.getProcessStartedAt??getProcessStartedAt;this.isOwnerAlive=options.isOwnerAlive??((pid,processStartedAt)=>isOriginalProcessAlive(pid,processStartedAt,pid===process.pid?()=>this.getCurrentProcessStartedAt():this.processStartedAtLookup));this.lockLoads=storage.requiresLoadLock===true;this.lockPath=fileSystemService.joinPath(getAuthLockDir(fileSystemService),LOCK_DIR)}loadTokens(baseUrl2){return this.lockLoads?this.withAuthStorageLock(()=>this.storage.loadTokens(baseUrl2)):this.storage.loadTokens(baseUrl2)}saveTokens(baseUrl2,data){return this.withAuthStorageLock(()=>this.storage.saveTokens(baseUrl2,data))}saveTokensIfUnchanged(baseUrl2,expected,data){return this.withAuthStorageLock(()=>this.storage.saveTokensIfUnchanged(baseUrl2,expected,data))}clearTokens(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearTokens(baseUrl2))}clearTokensIfUnchanged(baseUrl2,expected){return this.withAuthStorageLock(()=>this.storage.clearTokensIfUnchanged(baseUrl2,expected))}clearActiveTokensIfUnchanged(baseUrl2,expected){return this.withAuthStorageLock(()=>this.storage.clearActiveTokensIfUnchanged(baseUrl2,expected))}loadClient(baseUrl2){return this.lockLoads?this.withAuthStorageLock(()=>this.storage.loadClient(baseUrl2)):this.storage.loadClient(baseUrl2)}saveClient(baseUrl2,data){return this.withAuthStorageLock(()=>this.storage.saveClient(baseUrl2,data))}clearClient(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearClient(baseUrl2))}clearActiveClient(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearActiveClient(baseUrl2))}saveAuthSession(baseUrl2,client,tokens){return this.withAuthStorageLock(()=>this.storage.saveAuthSession(baseUrl2,client,tokens))}clearAuthSession(baseUrl2){return this.withAuthStorageLock(()=>this.storage.clearAuthSession(baseUrl2))}getStorageLocation(){return this.storage.getStorageLocation()}async withAuthStorageLock(fn){const ownerId=this.lockContext.getStore();if(ownerId&&this.currentOwner?.id===ownerId){return fn()}await this.acquireLock();const acquiredOwnerId=this.currentOwner?.id;try{return await this.lockContext.run(acquiredOwnerId??"",fn)}finally{await this.releaseLock()}}async acquireLock(){const processStartedAt=await this.getCurrentProcessStartedAt();const startedAt=Date.now();await mkdir2(dirname2(this.lockPath),{recursive:true,mode:448});while(true){try{await mkdir2(this.lockPath,{recursive:false,mode:448});try{await this.writeOwner(processStartedAt)}catch(error){this.currentOwner=null;const code=error.code;if(code==="EEXIST"||code==="ENOENT"){if(Date.now()-startedAt>=this.lockTimeoutMs){throw this.createLockTimeoutError()}await sleep(LOCK_RETRY_MS);continue}await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return});throw error}return}catch(error){if(error.code!=="EEXIST")throw error;await this.reclaimStaleLock();if(Date.now()-startedAt>=this.lockTimeoutMs){throw this.createLockTimeoutError()}await sleep(LOCK_RETRY_MS)}}}createLockTimeoutError(){return new AuthStorageLockTimeoutError(`Timed out waiting for GitHits auth storage lock at ${this.lockPath}. If no githits process is running, remove this directory and retry.`)}getCurrentProcessStartedAt(){if(!this.currentProcessStartedAtPromise){const lookup=this.processStartedAtLookup(process.pid);this.currentProcessStartedAtPromise=lookup;lookup.then((startedAt)=>{if(startedAt===null&&this.currentProcessStartedAtPromise===lookup){this.currentProcessStartedAtPromise=undefined}},()=>{if(this.currentProcessStartedAtPromise===lookup){this.currentProcessStartedAtPromise=undefined}})}return this.currentProcessStartedAtPromise}async writeOwner(processStartedAt){const owner={id:randomUUID3(),pid:process.pid,createdAt:new Date().toISOString(),processStartedAt};await writeFile2(this.ownerPath(),JSON.stringify(owner),{mode:384,flag:"wx"});this.currentOwner=owner}async reclaimStaleLock(){const owner=await this.readOwner();if(!owner){await this.reclaimOldOwnerlessLock();return}const ownerDead=!await this.isOwnerAlive(owner.pid,owner.processStartedAt);if(!ownerDead)return;const currentOwner=await this.readOwner();if(!currentOwner||currentOwner.id!==owner.id)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}async reclaimOldOwnerlessLock(){const createdAtMs=await lockCreatedAtMs(this.lockPath);if(Date.now()-createdAtMs<ORPHANED_LOCK_MS)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}async readOwner(){try{const raw=await readFile2(this.ownerPath(),"utf8");const parsed=JSON.parse(raw);if(typeof parsed.id!=="string"||typeof parsed.pid!=="number"||typeof parsed.createdAt!=="string"||!(typeof parsed.processStartedAt==="string"||parsed.processStartedAt===null)){return null}return{id:parsed.id,pid:parsed.pid,createdAt:parsed.createdAt,processStartedAt:parsed.processStartedAt}}catch{return null}}async releaseLock(){const owner=this.currentOwner;this.currentOwner=null;if(!owner)return;const currentOwner=await this.readOwner();if(!currentOwner||currentOwner.id!==owner.id)return;await rm(this.lockPath,{recursive:true,force:true}).catch(()=>{return})}ownerPath(){return`${this.lockPath}/${OWNER_FILE}`}}function sleep(ms){return new Promise((resolve)=>setTimeout(resolve,ms))}async function isOriginalProcessAlive(pid,processStartedAt,getStartedAt){if(!isProcessAlive(pid))return false;if(!processStartedAt)return true;return await getStartedAt(pid)===processStartedAt}function isProcessAlive(pid){if(pid<=0)return false;try{process.kill(pid,0);return true}catch(error){const code=error.code;return code==="EPERM"}}async function getProcessStartedAt(pid){try{if(process.platform==="win32"){const{stdout:stdout2}=await execFileAsync("powershell.exe",["-NoProfile","-Command",`(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString('o')`]);return stdout2.trim()||null}const{stdout}=await execFileAsync("ps",["-p",String(pid),"-o","lstart="]);const parsed=Date.parse(stdout.trim());return Number.isNaN(parsed)?null:new Date(parsed).toISOString()}catch{return null}}async function lockCreatedAtMs(path){try{const{stat:stat2}=await import("node:fs/promises");return(await stat2(path)).mtimeMs}catch{return 0}}class AuthStoragePolicyError extends Error{constructor(message){super(message);this.name="AuthStoragePolicyError"}}function createFileAuthStorageGuidance(configPath){return`OAuth credentials were not saved to plaintext file storage.
1596
1596
 
1597
1597
  Options:
1598
1598
  1. Unlock or fix your system keychain.
@@ -1 +1 @@
1
- import{clearAutoLoginAuthSessionMetadata,createAuthCommandDependencies,createAuthStatusDependencies,createContainer,loadAutoLoginAuthSessionMetadata,recordAuthFingerprint}from"./chunk-mm18f1hm.js";import"./chunk-mz63e2sv.js";export{recordAuthFingerprint,loadAutoLoginAuthSessionMetadata,createContainer,createAuthStatusDependencies,createAuthCommandDependencies,clearAutoLoginAuthSessionMetadata};
1
+ import{clearAutoLoginAuthSessionMetadata,createAuthCommandDependencies,createAuthStatusDependencies,createContainer,loadAutoLoginAuthSessionMetadata,recordAuthFingerprint}from"./chunk-89dydy8a.js";import"./chunk-dqq6yv70.js";export{recordAuthFingerprint,loadAutoLoginAuthSessionMetadata,createContainer,createAuthStatusDependencies,createAuthCommandDependencies,clearAutoLoginAuthSessionMetadata};
@@ -1,2 +1,2 @@
1
- import{createRequire}from"node:module";var __require=createRequire(import.meta.url);var description="The code context layer for AI coding agents";var version="0.9.1";
1
+ import{createRequire}from"node:module";var __require=createRequire(import.meta.url);var description="The code context layer for AI coding agents";var version="0.9.2";
2
2
  export{__require,description,version};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "githits",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "The code context layer for AI coding agents",
5
5
  "mcpServers": {
6
6
  "githits": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "githits",
3
3
  "description": "The code context layer for AI coding agents",
4
- "version": "0.9.1",
4
+ "version": "0.9.2",
5
5
  "mcpName": "com.githits/githits",
6
6
  "type": "module",
7
7
  "workspaces": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "githits",
4
- "version": "0.9.1",
4
+ "version": "0.9.2",
5
5
  "description": "The code context layer for AI coding agents",
6
6
  "author": {
7
7
  "name": "GitHits"
package/server.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "source": "github",
17
17
  "id": "1165453165"
18
18
  },
19
- "version": "0.9.1",
19
+ "version": "0.9.2",
20
20
  "remotes": [
21
21
  {
22
22
  "type": "streamable-http",
@@ -28,7 +28,7 @@
28
28
  "registryType": "npm",
29
29
  "registryBaseUrl": "https://registry.npmjs.org",
30
30
  "identifier": "githits",
31
- "version": "0.9.1",
31
+ "version": "0.9.2",
32
32
  "runtimeHint": "npx",
33
33
  "transport": {
34
34
  "type": "stdio"