UNPKG

codestate-core

Version:

Core domain models, use cases, and services for CodeState

1,500 lines (1,482 loc) 190 kB
// packages/core/domain/models/Result.ts function isSuccess(result) { return result.ok === true; } function isFailure(result) { return result.ok === false; } // packages/core/services/config/ConfigService.ts var ConfigService = class { constructor(repository, logger) { this.repository = repository; this.logger = logger; } async getConfig() { this.logger.debug("ConfigService.getConfig called"); const result = await this.repository.load(); if (isFailure(result)) { this.logger.error("Failed to get config", { error: result.error }); } else { this.logger.log("Config loaded", {}); } return result; } async setConfig(config) { this.logger.debug("ConfigService.setConfig called"); const result = await this.repository.save(config); if (isFailure(result)) { this.logger.error("Failed to save config", { error: result.error }); } else { this.logger.log("Config saved", {}); } return result; } async updateConfig(partial) { this.logger.debug("ConfigService.updateConfig called", { partial }); const current = await this.repository.load(); if (isFailure(current)) { this.logger.error("Failed to load config for update", { error: current.error }); return current; } const merged = { ...current.value, ...partial }; const saveResult = await this.repository.save(merged); if (isFailure(saveResult)) { this.logger.error("Failed to save updated config", { error: saveResult.error }); return { ok: false, error: saveResult.error }; } this.logger.log("Config updated", {}); return { ok: true, value: merged }; } }; // packages/core/domain/schemas/SchemaRegistry.ts import { z } from "zod"; import { randomUUID } from "crypto"; var LogLevelSchema = z.enum(["ERROR", "WARN", "LOG", "DEBUG"]); var LoggerConfigSchema = z.object({ level: LogLevelSchema, sinks: z.array(z.enum(["console", "file"])), filePath: z.string().optional() }); var FileStorageConfigSchema = z.object({ encryptionEnabled: z.boolean(), encryptionKey: z.string().optional(), dataDir: z.string() }); var FeatureFlagsSchema = z.object({ experimentalTui: z.boolean(), experimentalIde: z.boolean(), advancedSearch: z.boolean(), cloudSync: z.boolean() }); var PluginEnvironmentSchema = z.enum(["cli", "tui", "ide"]); var ErrorCodeSchema = z.enum([ "UNKNOWN", "CONFIG_INVALID", "STORAGE_INVALID_PATH", "STORAGE_DECRYPTION_FAILED", "STORAGE_READ_FAILED", "STORAGE_WRITE_FAILED", "STORAGE_DELETE_FAILED", "ENCRYPTION_FAILED", "ENCRYPTION_INVALID_FORMAT", "SCRIPT_INVALID", "SCRIPT_DUPLICATE", "SCRIPT_NOT_FOUND", "SCRIPT_PATH_INVALID", "SCRIPT_MALICIOUS", "GIT_NOT_REPOSITORY", "GIT_COMMAND_FAILED", "GIT_STASH_NOT_FOUND", "GIT_STASH_CONFLICT", "TERMINAL_COMMAND_FAILED", "TERMINAL_TIMEOUT", "TERMINAL_COMMAND_NOT_FOUND" ]); var EncryptionConfigSchema = z.object({ enabled: z.boolean(), encryptionKey: z.string().optional() }); var ConfigSchema = z.object({ version: z.string(), ide: z.string(), encryption: EncryptionConfigSchema, storagePath: z.string(), logger: LoggerConfigSchema, experimental: z.record(z.string(), z.boolean()).optional(), extensions: z.record(z.string(), z.unknown()).optional() }); var LifecycleEventSchema = z.enum(["open", "resume", "none"]); var ScriptSchema = z.object({ id: z.string().uuid("Invalid UUID format").default(() => randomUUID()), name: z.string().min(1, "Script name is required"), rootPath: z.string().min(1, "Root path is required"), script: z.string().min(1, "Script command is required").optional(), // DEPRECATED: Backward compatible commands: z.array(z.object({ command: z.string().min(1, "Command is required"), name: z.string().min(1, "Command name is required"), priority: z.number().int().min(0, "Priority must be non-negative integer") })).optional(), // NEW: Backward compatible lifecycle: z.array(LifecycleEventSchema).optional(), // NEW: Optional lifecycle for individual scripts executionMode: z.enum(["same-terminal", "new-terminals"]).default("same-terminal"), // NEW: Control how commands are executed closeTerminalAfterExecution: z.boolean().default(false) // NEW: Control whether to close terminal after execution }).refine((data) => { return data.script !== void 0 || data.commands !== void 0 && data.commands.length > 0; }, { message: "Either script or commands array must be provided", path: ["script", "commands"] }); var ScriptCommandSchema = z.object({ command: z.string().min(1, "Command is required"), name: z.string().min(1, "Command name is required"), priority: z.number().int().min(0, "Priority must be non-negative integer") }); var ScriptIndexEntrySchema = z.object({ id: z.string().uuid("Invalid UUID format"), rootPath: z.string().min(1, "Root path is required"), referenceFile: z.string().min(1, "Reference file path is required") }); var ScriptIndexSchema = z.object({ entries: z.array(ScriptIndexEntrySchema) }); var ScriptCollectionSchema = z.object({ scripts: z.array(ScriptSchema) }); var ScriptReferenceSchema = z.object({ id: z.string().min(1, "Script ID is required"), rootPath: z.string().min(1, "Root path is required") }); var TerminalCollectionSchema = z.object({ id: z.string().uuid("Invalid UUID format").default(() => randomUUID()), name: z.string().min(1, "Terminal collection name is required"), rootPath: z.string().min(1, "Root path is required"), lifecycle: z.array(LifecycleEventSchema).min(1, "At least one lifecycle event is required"), scriptReferences: z.array(ScriptReferenceSchema).min(1, "At least one script reference is required"), closeTerminalAfterExecution: z.boolean().default(false) // NEW: Control whether to close terminal after execution }); var TerminalCollectionIndexEntrySchema = z.object({ id: z.string().uuid("Invalid UUID format"), name: z.string().min(1, "Terminal collection name is required"), rootPath: z.string().min(1, "Root path is required"), referenceFile: z.string().min(1, "Reference file path is required") }); var TerminalCollectionIndexSchema = z.object({ entries: z.array(TerminalCollectionIndexEntrySchema) }); var GitFileStatusSchema = z.enum(["modified", "added", "deleted", "untracked", "renamed", "copied", "updated"]); var GitFileSchema = z.object({ path: z.string(), status: GitFileStatusSchema, staged: z.boolean() }); var GitStatusSchema = z.object({ isDirty: z.boolean(), dirtyFiles: z.array(GitFileSchema), newFiles: z.array(GitFileSchema), modifiedFiles: z.array(GitFileSchema), deletedFiles: z.array(GitFileSchema), untrackedFiles: z.array(GitFileSchema) }); var GitStashSchema = z.object({ id: z.string(), name: z.string(), message: z.string(), timestamp: z.number(), branch: z.string() }); var GitStashResultSchema = z.object({ success: z.boolean(), stashId: z.string().optional(), error: z.string().optional() }); var GitStashApplyResultSchema = z.object({ success: z.boolean(), conflicts: z.array(z.string()).optional(), error: z.string().optional() }); var TerminalCommandSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), cwd: z.string().optional(), env: z.record(z.string(), z.string()).optional(), timeout: z.number().optional() }); var TerminalResultSchema = z.object({ success: z.boolean(), exitCode: z.number(), stdout: z.string(), stderr: z.string(), duration: z.number(), error: z.string().optional() }); var TerminalOptionsSchema = z.object({ cwd: z.string().optional(), env: z.record(z.string(), z.string()).optional(), timeout: z.number().optional(), shell: z.string().optional() }); var FileStateSchema = z.object({ path: z.string(), cursor: z.object({ line: z.number(), column: z.number() }).optional(), scroll: z.object({ top: z.number(), left: z.number() }).optional(), isActive: z.boolean(), position: z.number().optional() // NEW: Backward compatible }); var GitStateSchema = z.object({ branch: z.string(), commit: z.string(), isDirty: z.boolean(), stashId: z.string().nullable().optional() }); var SessionTerminalCommandSchema = z.object({ command: z.string(), name: z.string(), priority: z.number().int().min(0, "Priority must be non-negative integer") }); var TerminalCommandStateSchema = z.object({ terminalId: z.number(), terminalName: z.string().optional(), commands: z.array(SessionTerminalCommandSchema) }); var SessionSchema = z.object({ id: z.string(), name: z.string(), projectRoot: z.string(), createdAt: z.union([z.string(), z.date()]).transform((val) => typeof val === "string" ? new Date(val) : val), updatedAt: z.union([z.string(), z.date()]).transform((val) => typeof val === "string" ? new Date(val) : val), tags: z.array(z.string()), notes: z.string().optional(), files: z.array(FileStateSchema), git: GitStateSchema, extensions: z.record(z.string(), z.unknown()).optional(), terminalCommands: z.array(TerminalCommandStateSchema).optional(), // NEW: Backward compatible terminalCollections: z.array(z.string()).optional(), // NEW: Terminal collection IDs scripts: z.array(z.string()).optional() // NEW: Individual script IDs }); var SessionIndexEntrySchema = z.object({ id: z.string(), name: z.string(), projectRoot: z.string(), createdAt: z.union([z.string(), z.date()]), updatedAt: z.union([z.string(), z.date()]), tags: z.array(z.string()), notes: z.string().optional(), referenceFile: z.string() }); var SessionIndexSchema = z.object({ version: z.string(), sessions: z.array(SessionIndexEntrySchema) }); function validateFileStorageConfig(data) { return FileStorageConfigSchema.parse(data); } function validateConfig(data) { return ConfigSchema.parse(data); } function validateScript(data) { return ScriptSchema.parse(data); } function validateScriptIndex(data) { return ScriptIndexSchema.parse(data); } function validateScriptCollection(data) { return ScriptCollectionSchema.parse(data); } function validateSession(data) { return SessionSchema.parse(data); } function validateSessionIndex(data) { return SessionIndexSchema.parse(data); } function validateTerminalCollection(data) { return TerminalCollectionSchema.parse(data); } function validateTerminalCollectionIndex(data) { return TerminalCollectionIndexSchema.parse(data); } // packages/core/domain/types/ErrorTypes.ts var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => { ErrorCode2["UNKNOWN"] = "UNKNOWN"; ErrorCode2["CONFIG_INVALID"] = "CONFIG_INVALID"; ErrorCode2["STORAGE_INVALID_PATH"] = "STORAGE_INVALID_PATH"; ErrorCode2["STORAGE_DECRYPTION_FAILED"] = "STORAGE_DECRYPTION_FAILED"; ErrorCode2["STORAGE_READ_FAILED"] = "STORAGE_READ_FAILED"; ErrorCode2["STORAGE_WRITE_FAILED"] = "STORAGE_WRITE_FAILED"; ErrorCode2["STORAGE_DELETE_FAILED"] = "STORAGE_DELETE_FAILED"; ErrorCode2["ENCRYPTION_FAILED"] = "ENCRYPTION_FAILED"; ErrorCode2["ENCRYPTION_INVALID_FORMAT"] = "ENCRYPTION_INVALID_FORMAT"; ErrorCode2["SCRIPT_INVALID"] = "SCRIPT_INVALID"; ErrorCode2["SCRIPT_DUPLICATE"] = "SCRIPT_DUPLICATE"; ErrorCode2["SCRIPT_NOT_FOUND"] = "SCRIPT_NOT_FOUND"; ErrorCode2["SCRIPT_PATH_INVALID"] = "SCRIPT_PATH_INVALID"; ErrorCode2["SCRIPT_MALICIOUS"] = "SCRIPT_MALICIOUS"; ErrorCode2["GIT_NOT_REPOSITORY"] = "GIT_NOT_REPOSITORY"; ErrorCode2["GIT_COMMAND_FAILED"] = "GIT_COMMAND_FAILED"; ErrorCode2["GIT_STASH_NOT_FOUND"] = "GIT_STASH_NOT_FOUND"; ErrorCode2["GIT_STASH_CONFLICT"] = "GIT_STASH_CONFLICT"; ErrorCode2["TERMINAL_COMMAND_FAILED"] = "TERMINAL_COMMAND_FAILED"; ErrorCode2["TERMINAL_TIMEOUT"] = "TERMINAL_TIMEOUT"; ErrorCode2["TERMINAL_COMMAND_NOT_FOUND"] = "TERMINAL_COMMAND_NOT_FOUND"; return ErrorCode2; })(ErrorCode || {}); var AppError = class extends Error { constructor(message, code = "UNKNOWN" /* UNKNOWN */, meta) { super(message); this.name = "AppError"; this.code = code; this.meta = meta; } }; var ConfigError = class extends AppError { constructor(message, meta) { super(message, "CONFIG_INVALID" /* CONFIG_INVALID */, meta); this.name = "ConfigError"; } }; var StorageError = class extends AppError { constructor(message, code = "STORAGE_READ_FAILED" /* STORAGE_READ_FAILED */, meta) { super(message, code, meta); this.name = "StorageError"; } }; var EncryptionError = class extends AppError { constructor(message, code = "ENCRYPTION_FAILED" /* ENCRYPTION_FAILED */, meta) { super(message, code, meta); this.name = "EncryptionError"; } }; var ScriptError = class extends AppError { constructor(message, code = "SCRIPT_INVALID" /* SCRIPT_INVALID */, meta) { super(message, code, meta); this.name = "ScriptError"; } }; var GitError = class extends AppError { constructor(message, code = "GIT_COMMAND_FAILED" /* GIT_COMMAND_FAILED */, meta) { super(message, code, meta); this.name = "GitError"; } }; var TerminalError = class extends AppError { constructor(message, code = "TERMINAL_COMMAND_FAILED" /* TERMINAL_COMMAND_FAILED */, meta) { super(message, code, meta); this.name = "TerminalError"; } }; // packages/core/infrastructure/repositories/ConfigRepository.ts import * as fs from "fs/promises"; import * as path from "path"; var DEFAULT_CONFIG_PATH = path.join( process.env.HOME || process.env.USERPROFILE || ".", ".codestate", "config.json" ); var TEMP_SUFFIX = ".tmp"; var BACKUP_SUFFIX = ".bak"; function getDefaultConfig() { return { version: "1.0.0", ide: "vscode", encryption: { enabled: false }, storagePath: path.join( process.env.HOME || process.env.USERPROFILE || ".", ".codestate" ), logger: { level: "LOG", sinks: ["file"], filePath: path.join( process.env.HOME || process.env.USERPROFILE || ".", ".codestate", "logs", "codestate.log" ) }, experimental: {}, extensions: {} }; } var ConfigRepository = class { constructor(logger, encryption, configPath = DEFAULT_CONFIG_PATH) { this.logger = logger; this.encryption = encryption; this.configPath = configPath; } async load() { try { await this.ensureDir(); this.logger.debug("Attempting to load config", { path: this.configPath }); const raw = await fs.readFile(this.configPath, { encoding: "utf8" }); let data = raw; if (raw.startsWith("ENCRYPTED_v1")) { this.logger.log("Config file is encrypted. Attempting decryption.", { path: this.configPath }); const key = ""; const decrypted = await this.encryption.decrypt(raw, key); if (isFailure(decrypted)) { this.logger.error("Decryption failed", { error: decrypted.error }); return { ok: false, error: decrypted.error }; } data = decrypted.value; } let parsed; try { parsed = JSON.parse(data); } catch (parseErr) { this.logger.error( "Config file is corrupt (invalid JSON). Backing up and creating default.", { path: this.configPath } ); await this.backupCorruptConfig(); const defaults = getDefaultConfig(); await this.save(defaults); return { ok: true, value: defaults }; } let config; try { config = validateConfig(parsed); } catch (validationErr) { this.logger.error( "Config file is corrupt (schema validation failed). Backing up and creating default.", { path: this.configPath } ); await this.backupCorruptConfig(); const defaults = getDefaultConfig(); await this.save(defaults); return { ok: true, value: defaults }; } this.logger.log("Config loaded successfully", { path: this.configPath, encrypted: raw.startsWith("ENCRYPTED_v1") }); return { ok: true, value: config }; } catch (err) { if (err.code === "ENOENT") { this.logger.warn("Config file not found. Creating default config.", { path: this.configPath }); const defaults = getDefaultConfig(); await this.save(defaults); return { ok: true, value: defaults }; } this.logger.error("Failed to load config", { error: err.message, path: this.configPath }); return { ok: false, error: new ConfigError(err.message) }; } } async save(config) { try { await this.ensureDir(); this.logger.debug("Attempting to save config", { path: this.configPath }); const validated = validateConfig(config); let data = JSON.stringify(validated, null, 2); let encrypted = false; if (config.encryption?.enabled && config.encryption.encryptionKey) { this.logger.log("Encrypting config before save", { path: this.configPath }); const encResult = await this.encryption.encrypt( data, config.encryption.encryptionKey ); if (isFailure(encResult)) { this.logger.error("Encryption failed", { error: encResult.error }); return { ok: false, error: encResult.error }; } data = encResult.value; encrypted = true; } const tempPath = this.configPath + TEMP_SUFFIX; await fs.writeFile(tempPath, data, { encoding: "utf8", mode: 384 }); this.logger.debug("Temp config file written", { tempPath }); await fs.rename(this.configPath, this.configPath + BACKUP_SUFFIX).then(() => { this.logger.log("Config backup created", { backupPath: this.configPath + BACKUP_SUFFIX }); }).catch(() => { }); await fs.rename(tempPath, this.configPath); this.logger.log("Config saved successfully", { path: this.configPath, encrypted }); return { ok: true, value: void 0 }; } catch (err) { this.logger.error("Failed to save config", { error: err.message, path: this.configPath }); return { ok: false, error: new ConfigError(err.message) }; } } async ensureDir() { const dir = path.dirname(this.configPath); await fs.mkdir(dir, { recursive: true, mode: 448 }).then(() => { this.logger.debug("Ensured config directory exists", { dir }); }).catch(() => { }); } async backupCorruptConfig() { try { const backupPath = this.configPath + ".bak." + Date.now(); await fs.rename(this.configPath, backupPath); this.logger.warn("Backed up corrupt config file", { backupPath }); } catch (err) { this.logger.error("Failed to backup corrupt config file", { error: err.message }); } } }; // packages/core/infrastructure/services/FileLogger.ts import { appendFileSync, mkdirSync } from "fs"; import * as path2 from "path"; var LOG_LEVEL_PRIORITY = { "ERROR": 0, "WARN": 1, "LOG": 2, "DEBUG": 3 }; var FileLogger = class { constructor(config) { if (!config.filePath) throw new Error("FileLogger requires filePath in LoggerConfig"); this.level = config.level; this.filePath = config.filePath; this.ensureLogDirectory(); } plainLog(message, meta) { const entry = { level: "plain", timestamp: (/* @__PURE__ */ new Date()).toISOString(), message, ...meta ? { meta } : {} }; appendFileSync(this.filePath, JSON.stringify(entry) + "\n", { encoding: "utf8" }); } ensureLogDirectory() { const logDir = path2.dirname(this.filePath); try { mkdirSync(logDir, { recursive: true }); } catch (error) { } } shouldLog(messageLevel) { return LOG_LEVEL_PRIORITY[this.level] >= LOG_LEVEL_PRIORITY[messageLevel]; } write(level, message, meta) { const entry = { level, timestamp: (/* @__PURE__ */ new Date()).toISOString(), message, ...meta ? { meta } : {} }; appendFileSync(this.filePath, JSON.stringify(entry) + "\n", { encoding: "utf8" }); } log(message, meta) { if (!this.shouldLog("LOG")) return; this.write("log", message, meta); } error(message, meta) { if (!this.shouldLog("ERROR")) return; this.write("error", message, meta); } warn(message, meta) { if (!this.shouldLog("WARN")) return; this.write("warn", message, meta); } debug(message, meta) { if (!this.shouldLog("DEBUG")) return; this.write("debug", message, meta); } }; // packages/core/infrastructure/services/BasicEncryption.ts import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto"; var HEADER = "ENCRYPTED_v1"; var SALT_LENGTH = 16; var IV_LENGTH = 12; var KEY_LENGTH = 32; var PBKDF2_ITER = 1e5; var BasicEncryption = class { constructor(logger) { this.logger = logger; } async encrypt(data, key) { try { const salt = randomBytes(SALT_LENGTH); const iv = randomBytes(IV_LENGTH); const derivedKey = pbkdf2Sync( key, salt, PBKDF2_ITER, KEY_LENGTH, "sha512" ); const cipher = createCipheriv("aes-256-gcm", derivedKey, iv); const ciphertext = Buffer.concat([ cipher.update(data, "utf8"), cipher.final() ]); const authTag = cipher.getAuthTag(); this.logger.debug("Data encrypted", { algorithm: "AES-256-GCM", operation: "encrypt" }); return { ok: true, value: [ HEADER, iv.toString("base64"), salt.toString("base64"), ciphertext.toString("base64"), authTag.toString("base64") ].join(":") }; } catch (err) { this.logger.error("Encryption failed", { error: err instanceof Error ? err.message : err, operation: "encrypt" }); return { ok: false, error: new EncryptionError( "Encryption failed", "ENCRYPTION_FAILED" /* ENCRYPTION_FAILED */, { originalError: err instanceof Error ? err.message : err, operation: "encrypt" } ) }; } } async decrypt(data, key) { try { const parts = data.split(":"); if (parts[0] !== HEADER || parts.length !== 5) { this.logger.error("Invalid encrypted data format", { operation: "decrypt" }); return { ok: false, error: new EncryptionError( "Invalid encrypted data format", "ENCRYPTION_INVALID_FORMAT" /* ENCRYPTION_INVALID_FORMAT */, { operation: "decrypt" } ) }; } const [, ivB64, saltB64, ciphertextB64, authTagB64] = parts; const iv = Buffer.from(ivB64, "base64"); const salt = Buffer.from(saltB64, "base64"); const ciphertext = Buffer.from(ciphertextB64, "base64"); const authTag = Buffer.from(authTagB64, "base64"); const derivedKey = pbkdf2Sync( key, salt, PBKDF2_ITER, KEY_LENGTH, "sha512" ); const decipher = createDecipheriv("aes-256-gcm", derivedKey, iv); decipher.setAuthTag(authTag); const plaintext = Buffer.concat([ decipher.update(ciphertext), decipher.final() ]); this.logger.debug("Data decrypted", { algorithm: "AES-256-GCM", operation: "decrypt" }); return { ok: true, value: plaintext.toString("utf8") }; } catch (err) { this.logger.error("Decryption failed", { error: err instanceof Error ? err.message : err, operation: "decrypt" }); return { ok: false, error: new EncryptionError( "Decryption failed", "ENCRYPTION_FAILED" /* ENCRYPTION_FAILED */, { originalError: err instanceof Error ? err.message : err, operation: "decrypt" } ) }; } } }; // packages/core/services/config/ConfigFacade.ts import * as path3 from "path"; var ConfigFacade = class { constructor(configPath, logger, encryption) { const _logger = logger || new FileLogger({ level: "LOG", sinks: ["file"], filePath: path3.join(process.env.HOME || process.env.USERPROFILE || ".", ".codestate", "logs", "codestate.log") }); const _encryption = encryption || new BasicEncryption(_logger); const repository = new ConfigRepository(_logger, _encryption, configPath); this.service = new ConfigService(repository, _logger); } async getConfig(...args) { return this.service.getConfig(...args); } async setConfig(...args) { return this.service.setConfig(...args); } async updateConfig(...args) { return this.service.updateConfig(...args); } }; // packages/core/use-cases/config/GetConfig.ts var GetConfig = class { constructor(configService) { this.configService = configService || new ConfigFacade(); } async execute() { return this.configService.getConfig(); } }; // packages/core/use-cases/config/UpdateConfig.ts var UpdateConfig = class { constructor(configService) { this.configService = configService || new ConfigFacade(); } async execute(partial) { return this.configService.updateConfig(partial); } }; // packages/core/use-cases/config/utils.ts import * as path4 from "path"; function getPackageVersion() { return process.env.PACKAGE_VERSION || process.env.npm_package_version || "1.5.1"; } function getDefaultConfig2(dataDir) { const storagePath = dataDir || path4.join(process.env.HOME || process.env.USERPROFILE || ".", ".codestate"); return { version: getPackageVersion(), ide: "vscode", encryption: { enabled: false }, storagePath, logger: { level: "LOG", sinks: ["file"], filePath: path4.join(storagePath, "logs", "codestate.log") }, experimental: {}, extensions: {} }; } // packages/core/use-cases/config/ResetConfig.ts var ResetConfig = class { constructor(configService) { this.configService = configService || new ConfigFacade(); } async execute() { const result = await this.configService.setConfig(getDefaultConfig2()); if (isFailure(result)) return { ok: false, error: result.error }; return { ok: true, value: getDefaultConfig2() }; } }; // packages/core/infrastructure/services/FileStorage.ts import { promises as fs2, constants as fsConstants } from "fs"; import * as path5 from "path"; var FileStorage = class { constructor(logger, encryption, config) { this.logger = logger; this.encryption = encryption; this.config = validateFileStorageConfig(config); } resolvePath(relPath) { const fullPath = path5.resolve(this.config.dataDir, relPath); if (!fullPath.startsWith(path5.resolve(this.config.dataDir))) { throw new StorageError( "Invalid file path", "STORAGE_INVALID_PATH" /* STORAGE_INVALID_PATH */, { relPath } ); } return fullPath; } async read(relPath) { const filePath = this.resolvePath(relPath); try { const data = await fs2.readFile(filePath, "utf8"); this.logger.debug("File read", { filePath }); if (this.config.encryptionEnabled && this.config.encryptionKey) { const decrypted = await this.encryption.decrypt( data, this.config.encryptionKey ); if (isFailure(decrypted)) { this.logger.error("Decryption failed during read", { filePath }); return { ok: false, error: new StorageError( "Decryption failed", "STORAGE_DECRYPTION_FAILED" /* STORAGE_DECRYPTION_FAILED */, { filePath } ) }; } return { ok: true, value: decrypted.value }; } return { ok: true, value: data }; } catch (err) { this.logger.error("File read failed", { filePath, error: err instanceof Error ? err.message : err }); return { ok: false, error: new StorageError( "File read failed", "STORAGE_READ_FAILED" /* STORAGE_READ_FAILED */, { filePath, originalError: err instanceof Error ? err.message : err } ) }; } } async write(relPath, data) { const filePath = this.resolvePath(relPath); const dir = path5.dirname(filePath); try { await fs2.mkdir(dir, { recursive: true, mode: 448 }); let toWrite = data; if (this.config.encryptionEnabled && this.config.encryptionKey) { const encrypted = await this.encryption.encrypt( data, this.config.encryptionKey ); if (isFailure(encrypted)) { this.logger.error("Encryption failed during write", { filePath }); return { ok: false, error: new StorageError( "Encryption failed", "STORAGE_WRITE_FAILED" /* STORAGE_WRITE_FAILED */, { filePath } ) }; } toWrite = encrypted.value; } const tmpPath = filePath + ".tmp"; await fs2.writeFile(tmpPath, toWrite, { mode: 384 }); const handle = await fs2.open(tmpPath, "r+"); await handle.sync(); await handle.close(); try { await fs2.access(filePath, fsConstants.F_OK); await fs2.copyFile(filePath, filePath + ".bak"); } catch { } await fs2.rename(tmpPath, filePath); this.logger.debug("File written atomically", { filePath }); return { ok: true, value: void 0 }; } catch (err) { this.logger.error("File write failed", { filePath, error: err instanceof Error ? err.message : err }); return { ok: false, error: new StorageError( "File write failed", "STORAGE_WRITE_FAILED" /* STORAGE_WRITE_FAILED */, { filePath, originalError: err instanceof Error ? err.message : err } ) }; } } async exists(relPath) { const filePath = this.resolvePath(relPath); try { await fs2.access(filePath, fsConstants.F_OK); this.logger.debug("File exists", { filePath }); return { ok: true, value: true }; } catch { return { ok: true, value: false }; } } async delete(relPath) { const filePath = this.resolvePath(relPath); try { try { await fs2.copyFile(filePath, filePath + ".bak"); } catch { } await fs2.unlink(filePath); this.logger.debug("File deleted", { filePath }); return { ok: true, value: void 0 }; } catch (err) { this.logger.error("File delete failed", { filePath, error: err instanceof Error ? err.message : err }); return { ok: false, error: new StorageError( "File delete failed", "STORAGE_DELETE_FAILED" /* STORAGE_DELETE_FAILED */, { filePath, originalError: err instanceof Error ? err.message : err } ) }; } } }; // packages/core/use-cases/config/ResetAll.ts import * as path6 from "path"; import * as fs3 from "fs/promises"; var ResetAll = class { constructor(dataDir) { this.fileStorage = null; this.dataDir = null; this.configService = new ConfigFacade(); if (dataDir) { this.dataDir = dataDir; this.initializeServices(); } } async initializeServices() { if (!this.dataDir) { const configResult = await this.configService.getConfig(); if (configResult.ok && configResult.value.storagePath) { this.dataDir = configResult.value.storagePath; } else { this.dataDir = path6.join(process.env.HOME || process.env.USERPROFILE || ".", ".codestate"); } } const logger = new FileLogger({ level: "LOG", sinks: ["file"], filePath: path6.join(this.dataDir, "logs", "reset.log") }); this.fileStorage = new FileStorage(logger, new BasicEncryption(logger), { encryptionEnabled: false, dataDir: this.dataDir }); } ensureDataDir() { if (!this.dataDir) { throw new Error("Data directory not initialized. Call execute() first."); } return this.dataDir; } async getDefaultDataDir() { try { const configResult = await this.configService.getConfig(); if (configResult.ok && configResult.value.storagePath) { return configResult.value.storagePath; } } catch (error) { } return path6.join(process.env.HOME || process.env.USERPROFILE || ".", ".codestate"); } async execute(options) { try { if (!this.fileStorage || !this.dataDir) { await this.initializeServices(); } if (!this.dataDir) { return { ok: false, error: new Error("Failed to initialize data directory") }; } const resetItems = []; const shouldResetAll = options.all || options.sessions && options.scripts && options.terminals && options.config; const shouldResetSessions = shouldResetAll || options.sessions; const shouldResetScripts = shouldResetAll || options.scripts; const shouldResetTerminals = shouldResetAll || options.terminals; const shouldResetConfig = shouldResetAll || options.config; if (shouldResetSessions) { await this.resetSessions(); resetItems.push("sessions"); } if (shouldResetScripts) { await this.resetScripts(); resetItems.push("scripts"); } if (shouldResetTerminals) { await this.resetTerminals(); resetItems.push("terminals"); } if (shouldResetConfig) { await this.resetConfig(); resetItems.push("config"); } if (shouldResetAll) { await this.cleanupDataDirectory(); } return { ok: true, value: { resetItems } }; } catch (error) { return { ok: false, error: error instanceof Error ? error : new Error("Unknown error during reset") }; } } async resetSessions() { try { const dataDir = this.ensureDataDir(); const sessionsDir = path6.join(dataDir, "sessions"); await fs3.rm(sessionsDir, { recursive: true, force: true }); await fs3.mkdir(sessionsDir, { recursive: true }); } catch (error) { } } async resetScripts() { try { const dataDir = this.ensureDataDir(); const scriptsDir = path6.join(dataDir, "scripts"); await fs3.rm(scriptsDir, { recursive: true, force: true }); await fs3.mkdir(scriptsDir, { recursive: true }); } catch (error) { } } async resetTerminals() { try { const dataDir = this.ensureDataDir(); const terminalsDir = path6.join(dataDir, "terminals"); await fs3.rm(terminalsDir, { recursive: true, force: true }); await fs3.mkdir(terminalsDir, { recursive: true }); } catch (error) { } } async resetConfig() { try { const dataDir = this.ensureDataDir(); const configPath = path6.join(dataDir, "config.json"); const defaultConfig = getDefaultConfig2(dataDir); await fs3.writeFile(configPath, JSON.stringify(defaultConfig, null, 2)); } catch (error) { } } async cleanupDataDirectory() { try { const dataDir = this.ensureDataDir(); const subdirs = ["sessions", "scripts", "terminals", "logs"]; for (const subdir of subdirs) { const subdirPath = path6.join(dataDir, subdir); try { await fs3.rm(subdirPath, { recursive: true, force: true }); } catch (error) { } } const filesToRemove = [ "index.json", "script-index.json", "terminal-collection-index.json", "config.json" ]; for (const file of filesToRemove) { const filePath = path6.join(dataDir, file); try { await fs3.unlink(filePath); } catch (error) { } } } catch (error) { } } }; // packages/core/use-cases/config/CheckVersionUpgrade.ts var CheckVersionUpgrade = class { constructor(configService) { this.MINIMUM_RESET_VERSION = "1.4.5"; this.CURRENT_VERSION = process.env.PACKAGE_VERSION || process.env.npm_package_version || "1.5.1"; this.configService = configService || new ConfigFacade(); this.logger = new CLILoggerFacade(); } async execute() { try { const configResult = await this.configService.getConfig(); if (!configResult.ok) { return { ok: true, value: { currentVersion: this.CURRENT_VERSION, storedVersion: "0.0.0", requiresReset: false, isUpgrade: false } }; } const config = configResult.value; const storedVersion = config.version || "0.0.0"; const requiresReset = this.shouldRequireReset(storedVersion); const isUpgrade = this.isVersionUpgrade(storedVersion); const versionInfo = { currentVersion: this.CURRENT_VERSION, storedVersion, requiresReset, isUpgrade }; if (requiresReset) { this.logger.plainLog(`\u{1F504} Version upgrade detected: ${storedVersion} \u2192 ${this.CURRENT_VERSION}`); this.logger.plainLog("\u26A0\uFE0F This version requires a complete reset due to breaking changes."); this.logger.plainLog("\u{1F504} Performing automatic reset..."); const resetAll = new ResetAll(); const resetResult = await resetAll.execute({ all: true }); if (resetResult.ok) { this.logger.plainLog("\u2705 Automatic reset completed successfully!"); await this.configService.updateConfig({ version: this.CURRENT_VERSION }); this.logger.plainLog("\u2705 Configuration updated to new version!"); } else { this.logger.error("\u274C Automatic reset failed", { error: resetResult.error }); return { ok: false, error: new Error(`Failed to perform automatic reset: ${resetResult.error}`) }; } } else if (isUpgrade) { this.logger.plainLog(`\u{1F504} Version upgrade detected: ${storedVersion} \u2192 ${this.CURRENT_VERSION}`); await this.configService.updateConfig({ version: this.CURRENT_VERSION }); this.logger.plainLog("\u2705 Configuration updated to new version!"); } return { ok: true, value: versionInfo }; } catch (error) { return { ok: false, error: error instanceof Error ? error : new Error("Unknown error during version check") }; } } shouldRequireReset(storedVersion) { const stored = this.parseVersion(storedVersion); const minimum = this.parseVersion(this.MINIMUM_RESET_VERSION); return this.compareVersions(stored, minimum) <= 0; } isVersionUpgrade(storedVersion) { const stored = this.parseVersion(storedVersion); const current = this.parseVersion(this.CURRENT_VERSION); return this.compareVersions(stored, current) < 0; } parseVersion(version) { return version.split(".").map((part) => parseInt(part, 10) || 0); } compareVersions(version1, version2) { const maxLength = Math.max(version1.length, version2.length); for (let i = 0; i < maxLength; i++) { const v1 = version1[i] || 0; const v2 = version2[i] || 0; if (v1 < v2) return -1; if (v1 > v2) return 1; } return 0; } }; // packages/core/use-cases/config/ExportConfig.ts var ExportConfig = class { constructor(configService) { this.configService = configService || new ConfigFacade(); } async execute() { const result = await this.configService.getConfig(); if (isFailure(result)) return { ok: false, error: result.error }; return { ok: true, value: JSON.stringify(result.value, null, 2) }; } }; // packages/core/use-cases/config/ImportConfig.ts var ImportConfig = class { constructor(configService) { this.configService = configService || new ConfigFacade(); } async execute(json) { let parsed; try { parsed = validateConfig(JSON.parse(json)); } catch (err) { return { ok: false, error: err }; } const result = await this.configService.setConfig(parsed); if (isFailure(result)) return { ok: false, error: result.error }; return { ok: true, value: parsed }; } }; // packages/core/services/scripts/ScriptService.ts var ScriptService = class { constructor(repository, logger) { this.repository = repository; this.logger = logger; } async createScript(script) { this.logger.debug("ScriptService.createScript called", { script }); const result = await this.repository.createScript(script); if (isFailure(result)) { this.logger.error("Failed to create script", { error: result.error, script }); } else { this.logger.log("Script created successfully", { script }); } return result; } async createScripts(scripts) { this.logger.debug("ScriptService.createScripts called", { count: scripts.length }); const result = await this.repository.createScripts(scripts); if (isFailure(result)) { this.logger.error("Failed to create scripts", { error: result.error, count: scripts.length }); } else { this.logger.log("Scripts created successfully", { count: scripts.length }); } return result; } async getScriptsByRootPath(rootPath) { this.logger.debug("ScriptService.getScriptsByRootPath called", { rootPath }); const result = await this.repository.getScriptsByRootPath(rootPath); if (isFailure(result)) { this.logger.error("Failed to get scripts by root path", { error: result.error, rootPath }); } else { this.logger.log("Scripts retrieved by root path", { rootPath, count: result.value.length }); } return result; } async getAllScripts() { this.logger.debug("ScriptService.getAllScripts called"); const result = await this.repository.getAllScripts(); if (isFailure(result)) { this.logger.error("Failed to get all scripts", { error: result.error }); } else { this.logger.log("All scripts retrieved", { count: result.value.length }); } return result; } async getScriptById(id) { this.logger.debug("ScriptService.getScriptById called", { id }); const result = await this.repository.getScriptById(id); if (isFailure(result)) { this.logger.error("Failed to get script by ID", { error: result.error, id }); } else { this.logger.log("Script retrieved by ID", { id, name: result.value.name }); } return result; } async updateScript(name, rootPath, scriptUpdate) { this.logger.debug("ScriptService.updateScript called", { name, rootPath, scriptUpdate }); const result = await this.repository.updateScript(name, rootPath, scriptUpdate); if (isFailure(result)) { this.logger.error("Failed to update script", { error: result.error, name, rootPath }); } else { this.logger.log("Script updated successfully", { name, rootPath }); } return result; } async updateScripts(updates) { this.logger.debug("ScriptService.updateScripts called", { count: updates.length }); const result = await this.repository.updateScripts(updates); if (isFailure(result)) { this.logger.error("Failed to update scripts", { error: result.error, count: updates.length }); } else { this.logger.log("Scripts updated successfully", { count: updates.length }); } return result; } async deleteScript(name, rootPath) { this.logger.debug("ScriptService.deleteScript called", { name, rootPath }); const result = await this.repository.deleteScript(name, rootPath); if (isFailure(result)) { this.logger.error("Failed to delete script", { error: result.error, name, rootPath }); } else { this.logger.log("Script deleted successfully", { name, rootPath }); } return result; } async deleteScripts(scripts) { this.logger.debug("ScriptService.deleteScripts called", { count: scripts.length }); const result = await this.repository.deleteScripts(scripts); if (isFailure(result)) { this.logger.error("Failed to delete scripts", { error: result.error, count: scripts.length }); } else { this.logger.log("Scripts deleted successfully", { count: scripts.length }); } return result; } async deleteScriptsByRootPath(rootPath) { this.logger.debug("ScriptService.deleteScriptsByRootPath called", { rootPath }); const result = await this.repository.deleteScriptsByRootPath(rootPath); if (isFailure(result)) { this.logger.error("Failed to delete scripts by root path", { error: result.error, rootPath }); } else { this.logger.log("Scripts deleted by root path successfully", { rootPath }); } return result; } async getScriptIndex() { this.logger.debug("ScriptService.getScriptIndex called"); const result = await this.repository.loadScriptIndex(); if (isFailure(result)) { this.logger.error("Failed to get script index", { error: result.error }); } else { this.logger.log("Script index retrieved", { entryCount: result.value.entries.length }); } return result; } async updateScriptIndex(index) { this.logger.debug("ScriptService.updateScriptIndex called"); const result = await this.repository.saveScriptIndex(index); if (isFailure(result)) { this.logger.error("Failed to update script index", { error: result.error }); } else { this.logger.log("Script index updated successfully"); } return result; } }; // packages/core/infrastructure/repositories/ScriptRepository.ts import * as fs4 from "fs/promises"; import * as path7 from "path"; import { randomUUID as randomUUID2 } from "crypto"; var DEFAULT_SCRIPTS_DIR = path7.join( process.env.HOME || process.env.USERPROFILE || ".", ".codestate", "scripts" ); var INDEX_FILE = "index.json"; var TEMP_SUFFIX2 = ".tmp"; var BACKUP_SUFFIX2 = ".bak"; var ScriptRepository = class { constructor(logger, encryption, configService, scriptsDir = DEFAULT_SCRIPTS_DIR) { this.logger = logger; this.encryption = encryption; this.configService = configService; this.scriptsDir = scriptsDir; } async createScript(script) { try { await this.ensureScriptsDir(); const validatedScript = validateScript(script); if (!await this.pathExists(validatedScript.rootPath)) { this.logger.error("Root path does not exist", { rootPath: validatedScript.rootPath }); return { ok: false, error: new ScriptError( "Root path does not exist", "SCRIPT_PATH_INVALID" /* SCRIPT_PATH_INVALID */, { rootPath: validatedScript.rootPath } ) }; } const existingScripts = await this.getScriptsByRootPath( validatedScript.rootPath ); if (existingScripts.ok && validatedScript.script) { const duplicate = existingScripts.value.find( (s) => s.script === validatedScript.script ); if (duplicate) { this.logger.error("Duplicate script command found", { script: validatedScript.script, rootPath: validatedScript.rootPath }); return { ok: false, error: new ScriptError( "Script command already exists", "SCRIPT_DUPLICATE" /* SCRIPT_DUPLICATE */, { script: validatedScript.script, rootPath: validatedScript.rootPath } ) }; } } const collection = await this.getOrCreateScriptCollection( validatedScript.rootPath ); if (isFailure(collection)) { return { ok: false, error: collection.error }; } collection.value.scripts.push(validatedScript); const saveResult = await this.saveScriptCollection( validatedScript.rootPath, collection.value ); if (isFailure(saveResult)) { return { ok: false, error: saveResult.error }; } await this.updateIndexForRootPath(validatedScript.rootPath); this.logger.log("Script created successfully", { name: validatedScript.name, rootPath: validatedScript.rootPath }); return { ok: true, value: void 0 }; } catch (err) { this.logger.error("Failed to create script", { error: err.message, script }); return { ok: false, error: new ScriptError(err.message, "SCRIPT_INVALID" /* SCRIPT_INVALID */, { originalError: err.message }) }; } } async createScripts(scripts) { try { if (scripts.length === 0) { return { ok: true, value: void 0 }; } this.logger.debug("Creating multiple scripts", { count: scripts.length }); const scriptsByRootPath = /* @__PURE__ */ new Map(); for (const script of scripts) { const validatedScript = validateScript(script); if (!await this.pathExists(validatedScript.rootPath)) { this.logger.error("Root path does not exist", { rootPath: validatedScript.rootPath }); return { ok: false, error: new ScriptError( "Root path does not exist", "SCRIPT_PATH_INVALID" /* SCRIPT_PATH_INVALID */, { rootPath: validatedScript.rootPath } ) }; } if (!scriptsByRootPath.has(validatedScript.rootPath)) { scriptsByRootPath.set(validatedScript.rootPath, []); } scriptsByRootPath.get(validatedScript.rootPath).push(validatedScript); } for (const [rootPath, rootScripts] of scriptsByRootPath) { const existingScripts = await this.getScriptsByRootPath(rootPath); const existingCollection = existingScripts.ok ? existingScripts.value : []; const allSc