import * as plugins from './plugins.js'; import * as aiDocsClasses from './aidocs_classes/index.js'; export class AiDoc { private openaiToken: string; public npmextraKV: plugins.npmextra.KeyValueStore; public qenvInstance: plugins.qenv.Qenv; public aidocInteract: plugins.smartinteract.SmartInteract; public smartAiInstance: plugins.smartai.SmartAi; argvArg: any; constructor(argvArg?: any) { this.argvArg = argvArg; } private printSanitizedToken() { // Check if the token length is greater than the sum of startLength and endLength let printToken: string; if (this.openaiToken.length > 6) { // Extract the beginning and end parts of the token const start = this.openaiToken.substring(0, 3); const end = this.openaiToken.substring(this.openaiToken.length - 3); printToken = `${start}...${end}`; } else { // If the token is not long enough, return it as is printToken = this.openaiToken; } console.log(`OpenAI Token on record: ${printToken}`); } public async start() { // lets care about prerequisites this.aidocInteract = new plugins.smartinteract.SmartInteract(); this.qenvInstance = new plugins.qenv.Qenv(); if (!(await this.qenvInstance.getEnvVarOnDemand('OPENAI_TOKEN'))) { // Migrate old KV store path to new path if needed const homeDir = plugins.smartpath.get.home(); const oldKvPath = plugins.path.join(homeDir, '.npmextra/kv/tsdoc.json'); const newKvDir = plugins.path.join(homeDir, '.npmextra/kv/@git.zone'); const newKvPath = plugins.path.join(newKvDir, 'tsdoc.json'); if ( await plugins.fsInstance.file(oldKvPath).exists() && !(await plugins.fsInstance.file(newKvPath).exists()) ) { console.log('Migrating tsdoc KeyValueStore to @git.zone/tsdoc...'); await plugins.fsInstance.directory(newKvDir).recursive().create(); await plugins.fsInstance.file(oldKvPath).copy(newKvPath); await plugins.fsInstance.file(oldKvPath).delete(); console.log('Migration complete: tsdoc.json -> @git.zone/tsdoc.json'); } this.npmextraKV = new plugins.npmextra.KeyValueStore({ typeArg: 'userHomeDir', identityArg: '@git.zone/tsdoc', mandatoryKeys: ['OPENAI_TOKEN'], }); const missingKeys = await this.npmextraKV.getMissingMandatoryKeys(); if (missingKeys.length > 0) { // lets try argv if (this.argvArg?.OPENAI_TOKEN) { this.openaiToken = this.argvArg.OPENAI_TOKEN; } else { // lets try smartinteract // wait for a second until OpenAI fixes punycode problem... await plugins.smartdelay.delayFor(1000); const answerObject = await this.aidocInteract.askQuestion({ type: 'input', message: `Please provide your OpenAI token. This will be persisted in your home directory.`, name: 'OPENAI_TOKEN', default: '', }); this.openaiToken = answerObject.value; } this.printSanitizedToken(); await this.npmextraKV.writeKey('OPENAI_TOKEN', this.openaiToken); } } if (!this.openaiToken && this.npmextraKV) { this.openaiToken = await this.npmextraKV.readKey('OPENAI_TOKEN'); } // lets assume we have an OPENAI_Token now this.smartAiInstance = new plugins.smartai.SmartAi({ openaiToken: this.openaiToken, }); await this.smartAiInstance.start(); } public async stop() { if (this.smartAiInstance) { await this.smartAiInstance.stop(); } // No explicit cleanup needed for npmextraKV or aidocInteract // They don't keep event loop alive } /** * Get the OpenAI provider for direct chat calls * This is a convenience getter to access the provider from SmartAi */ public get openaiProvider(): plugins.smartai.OpenAiProvider { return this.smartAiInstance.openaiProvider; } public getOpenaiToken(): string { return this.openaiToken; } public async buildReadme(projectDirArg: string) { const readmeInstance = new aiDocsClasses.Readme(this, projectDirArg); return await readmeInstance.build(); } public async buildDescription(projectDirArg: string) { const descriptionInstance = new aiDocsClasses.Description(this, projectDirArg); return await descriptionInstance.build(); } public async buildNextCommitObject(projectDirArg: string) { const commitInstance = new aiDocsClasses.Commit(this, projectDirArg); return await commitInstance.buildNextCommitObject(); } public async getProjectContext(projectDirArg: string) { const projectContextInstance = new aiDocsClasses.ProjectContext(projectDirArg); return await projectContextInstance.gatherFiles(); } /** * Get the context with token count information * @param projectDirArg The path to the project directory * @returns An object containing the context string and its token count */ public async getProjectContextWithTokenCount(projectDirArg: string) { const projectContextInstance = new aiDocsClasses.ProjectContext(projectDirArg); await projectContextInstance.update(); return projectContextInstance.getContextWithTokenCount(); } /** * Get just the token count for a project's context * @param projectDirArg The path to the project directory * @returns The number of tokens in the project context */ public async getProjectContextTokenCount(projectDirArg: string) { const projectContextInstance = new aiDocsClasses.ProjectContext(projectDirArg); await projectContextInstance.update(); return projectContextInstance.getTokenCount(); } /** * Estimate token count in a text string * @param text The text to estimate tokens for * @returns Estimated number of tokens */ public countTokens(text: string): number { const projectContextInstance = new aiDocsClasses.ProjectContext(''); return projectContextInstance.countTokens(text); } }