diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 53c2a7c..da161fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,6 +10,7 @@ jobs: build: permissions: contents: read + packages: write strategy: fail-fast: false matrix: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 511d68e..204488b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: create-release: permissions: contents: write - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 outputs: release_id: ${{ steps.create-release.outputs.result }} @@ -48,7 +48,7 @@ jobs: - platform: 'macos-latest' args: '--target x86_64-apple-darwin' name: 'macOS (Intel)' - - platform: 'ubuntu-20.04' + - platform: 'ubuntu-22.04' args: '' name: 'Linux' - platform: 'windows-latest' @@ -61,7 +61,7 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies (ubuntu only) - if: matrix.platform == 'ubuntu-20.04' + if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev pkg-config @@ -87,7 +87,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' - name: Enable Corepack run: corepack enable @@ -119,7 +119,7 @@ jobs: publish-release: permissions: contents: write - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 needs: [create-release, build-tauri] steps: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 787c150..868b919 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -6,11 +6,14 @@ on: pull_request: branches: [ main ] schedule: - - cron: '0 0 * * 1' # Weekly on Mondays + - cron: '0 0 * * 1' jobs: audit: runs-on: ubuntu-latest + permissions: + security-events: write + contents: read steps: - uses: actions/checkout@v4 @@ -29,21 +32,19 @@ jobs: - name: Audit npm packages run: yarn audit --audit-level moderate - continue-on-error: true - name: Audit Rust dependencies run: | cd src-tauri cargo audit - continue-on-error: true - name: Security scan with Semgrep uses: returntocorp/semgrep-action@v1 with: config: >- - p/security-audit - p/secrets - p/owasp-top-ten + p/ci + p/rust + p/javascript - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master diff --git a/components/ApiKeyDetail.tsx b/components/ApiKeyDetail.tsx index 804012d..8be7e79 100644 --- a/components/ApiKeyDetail.tsx +++ b/components/ApiKeyDetail.tsx @@ -23,7 +23,6 @@ import { useAppStore } from '../lib/store' import type { ApiKey } from '../lib/store' import ProjectPathDisplay from './ProjectPathDisplay' import { TauriAPI } from '../lib/tauri-api' -import { nativeStorageService } from '../lib/services/nativeStorageService' import ConfigGenerationModal from './modals/ConfigGenerationModal' import ApiDocumentationSection from './docs/ApiDocumentationSection' diff --git a/components/BottomSheet.tsx b/components/BottomSheet.tsx index 1fb4c1c..d18baf1 100644 --- a/components/BottomSheet.tsx +++ b/components/BottomSheet.tsx @@ -62,6 +62,15 @@ export const BottomSheet: React.FC = ({
{ + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + handleToggleCollapse() + } + }} /> {isCollapsed ? ( diff --git a/components/Sidebar.tsx b/components/Sidebar.tsx index 82daf39..20077c7 100644 --- a/components/Sidebar.tsx +++ b/components/Sidebar.tsx @@ -9,8 +9,7 @@ import { ChevronLeft, ChevronRight, Folder, - Book, - BookOpen + Book } from 'lucide-react' import { useAppStore } from '../lib/store' import { save } from '@tauri-apps/plugin-dialog' @@ -175,7 +174,7 @@ export default function Sidebar() {
{project.name} - {project.settings.default_environment} + {project.settings?.defaultEnvironment ?? 'development'}
)) diff --git a/components/UnlockVaultScreen.tsx b/components/UnlockVaultScreen.tsx index b1a1077..383bd13 100644 --- a/components/UnlockVaultScreen.tsx +++ b/components/UnlockVaultScreen.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { Key, Shield, Eye, EyeOff, Lock } from 'lucide-react' import { useAppStore } from '../lib/store' -import KeyringService from '../lib/services/keyringService' export default function UnlockVaultScreen() { const [password, setPassword] = useState('') diff --git a/components/modals/AddApiKeyModal.tsx b/components/modals/AddApiKeyModal.tsx index 51fde35..583eddf 100644 --- a/components/modals/AddApiKeyModal.tsx +++ b/components/modals/AddApiKeyModal.tsx @@ -161,10 +161,10 @@ export default function AddApiKeyModal() { diff --git a/components/modals/EditApiKeyModal.tsx b/components/modals/EditApiKeyModal.tsx index 1fe55ee..bd93944 100644 --- a/components/modals/EditApiKeyModal.tsx +++ b/components/modals/EditApiKeyModal.tsx @@ -187,10 +187,10 @@ export default function EditApiKeyModal() { diff --git a/extensions/vscode/package.json b/extensions/vscode/package.json index 2ff338d..a2e7efb 100644 --- a/extensions/vscode/package.json +++ b/extensions/vscode/package.json @@ -1,9 +1,9 @@ { - "name": "keykeeper-vscode", + "name": "cadcamfun.keykeeper-vscode", "displayName": "KeyKeeper Enterprise", "description": "Enterprise-grade Secure API Key Management with .env Drag & Drop and Auto-Activation", "version": "0.1.0", - "publisher": "nikoemme", + "publisher": "cadcamfun", "repository": { "type": "git", "url": "https://github.com/nikomatt69/keykeeper.git" @@ -14,7 +14,7 @@ }, "license": "MIT", "engines": { - "vscode": "^1.74.0" + "vscode": "^1.85.0" }, "categories": [ "Other", @@ -478,10 +478,11 @@ }, "devDependencies": { "@types/node": "16.x", - "@types/vscode": "^1.74.0", + "@types/vscode": "^1.85.0", "@types/ws": "^8.18.1", "ts-loader": "^9.5.2", "typescript": "^4.9.4", + "vsce": "^2.15.0", "webpack": "^5.100.2", "webpack-cli": "^6.0.1" }, diff --git a/lib/services/apiProviderService.ts b/lib/services/apiProviderService.ts index b191802..59bf277 100644 --- a/lib/services/apiProviderService.ts +++ b/lib/services/apiProviderService.ts @@ -585,7 +585,7 @@ export class ApiProviderService { return { files, - dependencies: [...Array.from(new Set(dependencies[0]))], // Remove duplicates + dependencies: [...new Set(dependencies)], setupInstructions, nextSteps }; diff --git a/lib/services/integrationService.ts b/lib/services/integrationService.ts index a8bca14..1454f44 100644 --- a/lib/services/integrationService.ts +++ b/lib/services/integrationService.ts @@ -30,6 +30,9 @@ class IntegrationService { private backupInProgress = false private lastBackup: Date | null = null private initialized = false + private vscodeCheckInterval: ReturnType | null = null + private backupCheckInterval: ReturnType | null = null + private autoLockTimeout: ReturnType | null = null // VSCode Integration async checkVSCodeConnection(): Promise { @@ -218,7 +221,10 @@ class IntegrationService { // Security Functions async triggerAutoLock(timeoutMinutes: number): Promise { - setTimeout(() => { + if (this.autoLockTimeout) { + clearTimeout(this.autoLockTimeout) + } + this.autoLockTimeout = setTimeout(() => { const { lockVault } = useAppStore.getState() lockVault() @@ -307,31 +313,25 @@ class IntegrationService { // Initialize service async initialize(settings: EnterpriseSettings): Promise { - // Prevent duplicate initialization if (this.initialized) { return } this.initialized = true - // Start VSCode integration if enabled if (settings.integrations.vscode.enabled) { await this.enableVSCodeIntegration(true) - - // Check connection periodically - setInterval(() => { + this.vscodeCheckInterval = setInterval(() => { this.checkVSCodeConnection() - }, 30000) // Every 30 seconds + }, 30000) } - // Schedule auto backup if (settings.backup.autoBackup) { - setInterval(() => { + this.backupCheckInterval = setInterval(() => { this.scheduleAutoBackup(settings) - }, 60000) // Check every minute + }, 60000) } - // Setup auto-lock if (settings.security.autoLockTimeout > 0) { this.triggerAutoLock(settings.security.autoLockTimeout) } @@ -351,6 +351,18 @@ class IntegrationService { lastHeartbeat: null, connectionCount: 0 } + if (this.vscodeCheckInterval) { + clearInterval(this.vscodeCheckInterval) + this.vscodeCheckInterval = null + } + if (this.backupCheckInterval) { + clearInterval(this.backupCheckInterval) + this.backupCheckInterval = null + } + if (this.autoLockTimeout) { + clearTimeout(this.autoLockTimeout) + this.autoLockTimeout = null + } } } diff --git a/lib/services/llmProxyService.ts b/lib/services/llmProxyService.ts index 8a3009b..b832ebd 100644 --- a/lib/services/llmProxyService.ts +++ b/lib/services/llmProxyService.ts @@ -36,15 +36,17 @@ interface CachedResponse { } export class LLMProxyService { - private static instance: LLMProxyService; + private static instance: LLMProxyService | null = null; + private static initPromise: Promise | null = null; private config: LLMProxyConfig; private cache = new Map(); - private cacheDuration = 24 * 60 * 60 * 1000; // 24 hours + private cacheDuration = 24 * 60 * 60 * 1000; + private maxCacheSize = 1000; private constructor(config: Partial = {}) { this.config = { - provider: 'openai', // Default provider - model: 'gpt-4o-mini', // Default model + provider: 'openai', + model: 'gpt-4o-mini', temperature: 0.2, maxTokens: 2000, ...config @@ -86,6 +88,7 @@ export class LLMProxyService { try { const mergedConfig = { ...this.config, ...options }; + this.cleanupCache(); console.log('Sending request to LLM proxy:', { promptLength: prompt.length, @@ -162,10 +165,35 @@ export class LLMProxyService { return response.content; } + // Clear expired entries from cache + private cleanupCache(): void { + const now = Date.now(); + const maxAge = this.cacheDuration; + + for (const [key, value] of this.cache.entries()) { + if (now - value.timestamp > maxAge) { + this.cache.delete(key); + } + } + + if (this.cache.size > this.maxCacheSize) { + const entries = Array.from(this.cache.entries()); + entries.sort((a, b) => a[1].timestamp - b[1].timestamp); + const toRemove = entries.slice(0, Math.floor(this.cache.size * 0.2)); + toRemove.forEach(([key]) => this.cache.delete(key)); + } + } + // Clear the cache public clearCache(): void { this.cache.clear(); } + + // Get cache stats + public getCacheStats(): { size: number; maxSize: number } { + this.cleanupCache(); + return { size: this.cache.size, maxSize: this.maxCacheSize }; + } // =============================== // LLM-ENHANCED METHODS diff --git a/lib/services/mlService.ts b/lib/services/mlService.ts index 16fe406..f0f315d 100644 --- a/lib/services/mlService.ts +++ b/lib/services/mlService.ts @@ -94,18 +94,27 @@ interface MLInitRequest extends Record { export class MLService { private static isInitialized = false; private static initializationPromise: Promise | null = null; + private static initPromise: Promise | null = null; /** * Initialize the ML Engine */ static async initialize(config?: MLConfig): Promise { - // Avoid multiple initialization attempts - if (this.initializationPromise) { - return this.initializationPromise; + if (this.initPromise) { + return this.initPromise; + } + + if (this.isInitialized) { + return true; } - this.initializationPromise = this._performInitialization(config); - return this.initializationPromise; + this.initPromise = this._performInitialization(config); + try { + const result = await this.initPromise; + return result; + } finally { + this.initPromise = null; + } } private static async _performInitialization(config?: MLConfig): Promise { diff --git a/lib/tauri-api.ts b/lib/tauri-api.ts index e7087ed..057d326 100644 --- a/lib/tauri-api.ts +++ b/lib/tauri-api.ts @@ -1,21 +1,6 @@ import { invoke } from '@tauri-apps/api/core'; import { listen } from '@tauri-apps/api/event'; - -export interface ApiKey { - id: string; - name: string; - service: string; - key: string; - description?: string; - environment: string; - rate_limit?: string; - expires_at?: string; - scopes: string[]; - created_at: string; - updated_at: string; - tags: string[]; - is_active: boolean; -} +import { ApiKey } from './types'; export interface VaultStatus { is_unlocked: boolean; @@ -71,14 +56,14 @@ export interface Documentation { id: string; title: string; content: string; - doc_type: string; // "api" | "guide" | "reference" | "notes" | "snippet" | "scraped" + doc_type: string; project_id?: string; provider_id?: string; url?: string; tags: string[]; - created_at: string; // ISO 8601 string from Rust DateTime - updated_at: string; // ISO 8601 string from Rust DateTime - language: string; // "en" | "it" | "es" etc. + created_at: string; + updated_at: string; + language: string; is_favorite: boolean; search_keywords: string[]; } @@ -87,7 +72,7 @@ export interface DocSection { id: string; title: string; content: string; - level: number; // 1-6 for heading levels (maps to Rust u8) + level: number; anchor?: string; parent_section_id?: string; } @@ -100,7 +85,7 @@ export interface ApiDocumentation { content: string; sections: DocSection[]; tags: string[]; - last_updated: string; // ISO 8601 string from Rust DateTime + last_updated: string; version?: string; language: string; } @@ -115,17 +100,6 @@ export interface DocSearchResult { provider_id: string; } -export interface MLEngineStatus { - initialized: boolean; - model_loaded: boolean; - context_enabled: boolean; - stats?: { - total_analyses: number; - cache_hits: number; - cache_size: number; - }; -} - export interface LLMRequest { prompt: string; context?: any; @@ -133,11 +107,11 @@ export interface LLMRequest { } export interface LLMConfig { - provider: string; // "openai" | "local" + provider: string; model: string; temperature: number; - max_tokens: number; // Backend expects snake_case: max_tokens - api_key?: string; // Backend expects snake_case: api_key + max_tokens: number; + api_key?: string; } export interface LLMResponse { @@ -151,18 +125,6 @@ export interface LLMResponse { error?: string; } -// ML Engine Types for Frontend -export interface MLEngineStatus { - initialized: boolean; - model_loaded: boolean; - context_enabled: boolean; - stats?: { - total_analyses: number; - cache_hits: number; - cache_size: number; - }; -} - export interface ContextInfo { active_app?: string; file_path?: string; diff --git a/lib/types.ts b/lib/types.ts index bebaa06..52fe178 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -361,17 +361,6 @@ export interface GenerationContext { tsconfig?: any } -export interface UserPreferences { - codeStyle: 'typescript' | 'javascript' - useSemicolons: boolean - indentation: 'spaces' | 'tabs' - indentSize: number - generateTypes: boolean - includeJsdoc: boolean - importStyle: 'default' | 'named' | 'namespace' - useAsyncAwait: boolean -} - export interface EnhancedGenerationResult { files: GeneratedTemplateFile[] dependencies: string[] @@ -579,6 +568,14 @@ export interface UserPreferences { language: string timezone: string notifications: NotificationConfig + codeStyle: 'typescript' | 'javascript' + useSemicolons: boolean + indentation: 'spaces' | 'tabs' + indentSize: number + generateTypes: boolean + includeJsdoc: boolean + importStyle: 'default' | 'named' | 'namespace' + useAsyncAwait: boolean } // =============================== diff --git a/next.config.js b/next.config.js index 7d91f99..aca8424 100644 --- a/next.config.js +++ b/next.config.js @@ -1,12 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - output: 'export', - trailingSlash: false, - distDir: 'out', + trailingSlash: true, images: { unoptimized: true, }, - } module.exports = nextConfig \ No newline at end of file diff --git a/package.json b/package.json index dc38a4b..cb892ae 100644 --- a/package.json +++ b/package.json @@ -23,20 +23,20 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@heroicons/react": "^2.0.18", - "@tauri-apps/api": "2.0.0-beta.13", - "@tauri-apps/plugin-autostart": "2.0.0-beta.3", - "@tauri-apps/plugin-dialog": "^2.3.0", - "@tauri-apps/plugin-fs": "2.0.0-beta.6", - "@tauri-apps/plugin-log": "^2.6.0", - "@tauri-apps/plugin-notification": "2.0.0-beta.4", - "@tauri-apps/plugin-shell": "2.0.0-beta.6", - "@tauri-apps/plugin-store": "2.0.0-beta.5", - "@tauri-apps/plugin-updater": "2.0.0-beta.4", - "@tauri-apps/plugin-window-state": "2.0.0-beta.4", + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-autostart": "^2.0.0", + "@tauri-apps/plugin-dialog": "^2.0.0", + "@tauri-apps/plugin-fs": "^2.0.0", + "@tauri-apps/plugin-log": "^2.0.0", + "@tauri-apps/plugin-notification": "^2.0.0", + "@tauri-apps/plugin-shell": "^2.0.0", + "@tauri-apps/plugin-store": "^2.0.0", + "@tauri-apps/plugin-updater": "^2.0.0", + "@tauri-apps/plugin-window-state": "^2.0.0", "@types/ws": "^8.18.1", "axios": "^1.10.0", "clsx": "^2.0.0", - "framer-motion": "^12.23.6", + "framer-motion": "^11.0.0", "lodash": "^4.17.21", "lucide-react": "^0.525.0", "next": "14.0.3", @@ -49,7 +49,7 @@ "zustand": "^4.4.6" }, "devDependencies": { - "@tauri-apps/cli": "2.0.0-beta.20", + "@tauri-apps/cli": "^2.0.0", "@types/lodash": "^4.17.20", "@types/node": "^20.9.0", "@types/react": "^18.2.37", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e779ebb..93a967b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,14 +15,14 @@ tauri = { version = "2.6.2", features = ["tray-icon"] } tauri-plugin-shell = "2" tauri-plugin-dialog = "2" tauri-plugin-fs = "2" -tauri-plugin-biometric = "2.0.0" -tauri-plugin-keychain = "2.0.0" -tauri-plugin-store = "2.0" -tauri-plugin-window-state = "2.0" -tauri-plugin-single-instance = "2.0" -tauri-plugin-updater = "2.0" -tauri-plugin-notification = "2.0" -tauri-plugin-autostart = "2.0" +tauri-plugin-biometric = "1" +tauri-plugin-keychain = "2" +tauri-plugin-store = "2" +tauri-plugin-window-state = "2" +tauri-plugin-single-instance = "2" +tauri-plugin-updater = "2" +tauri-plugin-notification = "2" +tauri-plugin-autostart = "2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0", features = ["full"] } @@ -51,8 +51,7 @@ urlencoding = "2.1" tauri-plugin-persisted-scope = "2.3.0" keyring = "2.0" whoami = "1.4" -tauri-plugin-deep-link = "2.4.0" -tauri-plugin-keyring = "0.1.0" +tauri-plugin-deep-link = "2" # ML Dependencies for local inference - using candle-transformers for GGUF support anyhow = "1.0" @@ -79,8 +78,4 @@ md5 = "0.7" url = "2.5" [features] -# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! custom-protocol = ["tauri/custom-protocol"] - -[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] -tauri-plugin-single-instance = "2.0" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 59f0b66..9f40376 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/schema.json", "productName": "KeyKeeper", "version": "0.1.0", - "identifier": "com.keykeeper.arm64os1", + "identifier": "com.keykeeper.desktop", "build": { "beforeBuildCommand": "yarn build", "beforeDevCommand": "yarn dev", @@ -26,7 +26,7 @@ } ], "security": { - "csp": "default-src 'self' localhost:* ; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws://localhost:* http://localhost:*; worker-src 'self';", + "csp": "default-src 'self' localhost:* ; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws://localhost:* http://localhost:*; worker-src 'self';", "capabilities": [ "keykeeper-capabilities" ], @@ -45,9 +45,6 @@ "icons/icon.icns", "icons/icon.ico" ], - "resources": [ - "models/*" - ], "shortDescription": "A secure API key manager", "longDescription": "KeyKeeper is a secure, local API key manager that keeps your keys safe and organized.", "linux": {}, @@ -95,7 +92,7 @@ "updater": { "active": true, "endpoints": [ - "https://api.github.com/repos/nikoemme/keykeeper/releases/latest" + "https://api.github.com/repos/nikomatt69/keykeeper/releases/latest" ], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDY2Q0M5MDk5OTQ4RUI4QgpSV1NMNjBpWkNjbHNCandqUldSOWs0cEtCUGhJZXFGWnRCejdacXRqdk1jZVN4QlYxV0l3Sk1pbwo=" diff --git a/tsconfig.json b/tsconfig.json index 0bb6b1a..4253f68 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "es6"], + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -14,6 +14,7 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, + "baseUrl": ".", "plugins": [ { "name": "next"