codestate-core
Version:
Core domain models, use cases, and services for CodeState
1,500 lines (1,482 loc) • 190 kB
JavaScript
// 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