@mastra/code-sdk 0.1.0-alpha.0 → 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/dist/agents/mastracode-gateway.d.ts +4 -2
  3. package/dist/agents/mastracode-gateway.d.ts.map +1 -1
  4. package/dist/agents/mastracode-gateway.js +11 -7
  5. package/dist/agents/mastracode-gateway.js.map +1 -1
  6. package/dist/agents/model.js +1 -1
  7. package/dist/agents/model.js.map +1 -1
  8. package/dist/agents/tools.d.ts +3 -3
  9. package/dist/agents/tools.d.ts.map +1 -1
  10. package/dist/agents/tools.js +33 -22
  11. package/dist/agents/tools.js.map +1 -1
  12. package/dist/agents/workspace.d.ts.map +1 -1
  13. package/dist/agents/workspace.js +51 -19
  14. package/dist/agents/workspace.js.map +1 -1
  15. package/dist/index.d.ts +7 -9
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +17 -5
  18. package/dist/index.js.map +1 -1
  19. package/dist/onboarding/settings.d.ts +6 -2
  20. package/dist/onboarding/settings.d.ts.map +1 -1
  21. package/dist/onboarding/settings.js +6 -2
  22. package/dist/onboarding/settings.js.map +1 -1
  23. package/dist/plugins/dependencies.d.ts.map +1 -1
  24. package/dist/plugins/dependencies.js +28 -58
  25. package/dist/plugins/dependencies.js.map +1 -1
  26. package/dist/utils/path-security.d.ts +2 -0
  27. package/dist/utils/path-security.d.ts.map +1 -0
  28. package/dist/utils/path-security.js +9 -0
  29. package/dist/utils/path-security.js.map +1 -0
  30. package/dist/utils/project.d.ts +1 -2
  31. package/dist/utils/project.d.ts.map +1 -1
  32. package/dist/utils/project.js.map +1 -1
  33. package/dist/utils/slash-command-loader.d.ts +5 -1
  34. package/dist/utils/slash-command-loader.d.ts.map +1 -1
  35. package/dist/utils/slash-command-loader.js +30 -8
  36. package/dist/utils/slash-command-loader.js.map +1 -1
  37. package/dist/utils/storage-factory.d.ts +3 -3
  38. package/dist/utils/storage-factory.d.ts.map +1 -1
  39. package/dist/utils/storage-factory.js +5 -2
  40. package/dist/utils/storage-factory.js.map +1 -1
  41. package/dist/utils/storage-maintenance.d.ts +103 -0
  42. package/dist/utils/storage-maintenance.d.ts.map +1 -0
  43. package/dist/utils/storage-maintenance.js +236 -0
  44. package/dist/utils/storage-maintenance.js.map +1 -0
  45. package/package.json +17 -15
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/project.ts"],"sourcesContent":["/**\n * Project detection utilities\n *\n * Detects project identity from git repo or filesystem path.\n * Handles git worktrees by finding the main repository.\n */\n\nimport { execFile, execSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nexport interface ProjectInfo {\n /** Unique resource ID for this project (used for thread grouping) */\n resourceId: string;\n /** Human-readable project name */\n name: string;\n /** Absolute path to the project root */\n rootPath: string;\n /** Git remote URL if available */\n gitUrl?: string;\n /** Current git branch */\n gitBranch?: string;\n /** Whether this is a git worktree */\n isWorktree: boolean;\n /** Path to main git repo (different from rootPath if worktree) */\n mainRepoPath?: string;\n /** Whether the resourceId was explicitly overridden (env var or config) */\n resourceIdOverride?: boolean;\n}\n\n/**\n * Run a git command and return stdout, or undefined if it fails\n */\nfunction git(args: string, cwd: string): string | undefined {\n try {\n return execSync(`git ${args}`, {\n cwd,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Slugify a string for use in IDs\n */\nfunction slugify(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\n/**\n * Create a short hash of a string\n */\nfunction shortHash(str: string): string {\n return createHash('sha256').update(str).digest('hex').slice(0, 12);\n}\n\n/**\n * Normalize a git URL to a canonical form for comparison\n * - Removes .git suffix\n * - Converts SSH to HTTPS format for consistency\n * - Lowercases\n */\nfunction normalizeGitUrl(url: string): string {\n return url\n .replace(/\\.git$/, '')\n .replace(/^git@([^:]+):/, 'https://$1/')\n .replace(/^ssh:\\/\\/git@/, 'https://')\n .toLowerCase();\n}\n\n/**\n * Detect project info from a directory path\n */\nexport function detectProject(projectPath: string): ProjectInfo {\n const absolutePath = path.resolve(projectPath);\n\n // Check if this is a git repo\n const gitDir = git('rev-parse --git-dir', absolutePath);\n const isGitRepo = gitDir !== undefined;\n\n let rootPath = absolutePath;\n let gitUrl: string | undefined;\n let gitBranch: string | undefined;\n let isWorktree = false;\n let mainRepoPath: string | undefined;\n\n if (isGitRepo) {\n // Get the repo root (handles being in a subdirectory)\n rootPath = git('rev-parse --show-toplevel', absolutePath) || absolutePath;\n\n // Check for worktree - git-common-dir differs from git-dir in worktrees\n const commonDir = git('rev-parse --git-common-dir', absolutePath);\n if (commonDir && commonDir !== '.git' && commonDir !== gitDir) {\n isWorktree = true;\n // The common dir is inside the main repo's .git folder\n mainRepoPath = path.dirname(path.resolve(rootPath, commonDir));\n }\n\n // Get remote URL (prefer origin, fall back to first remote)\n gitUrl = git('remote get-url origin', absolutePath);\n if (!gitUrl) {\n const remotes = git('remote', absolutePath);\n if (remotes) {\n const firstRemote = remotes.split('\\n')[0];\n if (firstRemote) {\n gitUrl = git(`remote get-url ${firstRemote}`, absolutePath);\n }\n }\n }\n\n // Get current branch\n gitBranch = git('rev-parse --abbrev-ref HEAD', absolutePath);\n }\n\n // Generate resource ID\n // Priority: normalized git URL > main repo path (for worktrees) > absolute path\n let resourceIdSource: string;\n if (gitUrl) {\n resourceIdSource = normalizeGitUrl(gitUrl);\n } else if (mainRepoPath) {\n resourceIdSource = mainRepoPath;\n } else {\n resourceIdSource = rootPath;\n }\n\n // Create a readable but unique resource ID\n // Format: slugified-name-shorthash\n const baseName = gitUrl\n ? gitUrl\n .split('/')\n .pop()\n ?.replace(/\\.git$/, '') || 'project'\n : path.basename(rootPath);\n\n const resourceId = `${slugify(baseName)}-${shortHash(resourceIdSource)}`;\n\n return {\n resourceId,\n name: baseName,\n rootPath,\n gitUrl,\n gitBranch,\n isWorktree,\n mainRepoPath,\n };\n}\n\n/**\n * Get the current git branch for a given directory.\n * Lightweight alternative to detectProject() for refreshing just the branch.\n */\nexport function getCurrentGitBranch(cwd: string): string | undefined {\n return git('rev-parse --abbrev-ref HEAD', cwd);\n}\n\n/**\n * Async version of getCurrentGitBranch — avoids blocking the event loop\n * with execSync. Falls back to undefined on any failure.\n */\nexport function getCurrentGitBranchAsync(cwd: string): Promise<string | undefined> {\n return new Promise(resolve => {\n execFile('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf-8' }, (err, stdout) => {\n if (err) {\n resolve(undefined);\n return;\n }\n const branch = stdout.trim();\n resolve(branch || undefined);\n });\n });\n}\n\n/**\n * Get the application data directory for mastracode\n * - macOS: ~/Library/Application Support/mastracode\n * - Linux: ~/.local/share/mastracode\n * - Windows: %APPDATA%/mastracode\n */\nexport function getAppDataDir(): string {\n if (process.env.MASTRA_APP_DATA_DIR) {\n fs.mkdirSync(process.env.MASTRA_APP_DATA_DIR, { recursive: true });\n return process.env.MASTRA_APP_DATA_DIR;\n }\n\n const platform = os.platform();\n let baseDir: string;\n\n if (platform === 'darwin') {\n baseDir = path.join(os.homedir(), 'Library', 'Application Support');\n } else if (platform === 'win32') {\n baseDir = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');\n } else {\n // Linux and others - follow XDG spec\n baseDir = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');\n }\n\n const appDir = path.join(baseDir, 'mastracode');\n\n // Ensure directory exists\n if (!fs.existsSync(appDir)) {\n fs.mkdirSync(appDir, { recursive: true });\n }\n\n return appDir;\n}\n/**\n * Get the database path for mastracode\n * Can be overridden with the MASTRA_DB_PATH environment variable for debugging.\n */\nexport function getDatabasePath(): string {\n if (process.env.MASTRA_DB_PATH) {\n return process.env.MASTRA_DB_PATH;\n }\n return path.join(getAppDataDir(), 'mastra.db');\n}\n\n/**\n * Get the vector database path for mastracode.\n * Separate from the main DB to avoid bloating it with embedding data.\n */\nexport function getVectorDatabasePath(): string {\n return path.join(getAppDataDir(), 'mastra-vectors.db');\n}\n\n/**\n * Get the observability DuckDB database path for mastracode.\n * Separate from the main DB — DuckDB is used for OLAP-style trace/score/feedback queries.\n * Can be overridden with the MASTRA_OBSERVABILITY_DB_PATH environment variable.\n */\nexport function getObservabilityDatabasePath(): string {\n if (process.env.MASTRA_OBSERVABILITY_DB_PATH) {\n return process.env.MASTRA_OBSERVABILITY_DB_PATH;\n }\n return path.join(getAppDataDir(), 'observability.duckdb');\n}\n\nimport type { StorageBackend, StorageSettings } from '../onboarding/settings.js';\n\n/**\n * LibSQL storage configuration.\n */\nexport interface LibSQLStorageConfig {\n backend: 'libsql';\n url: string;\n authToken?: string;\n isRemote: boolean;\n /**\n * Optional explicit url for the recall vector DB. When omitted, the factory\n * uses the shared default vector file. Per-tenant storage sets this so each\n * tenant's recall vectors live in their own isolated DB, not a shared file.\n */\n vectorUrl?: string;\n /** Auth token for the vector DB when `vectorUrl` points at a remote libSQL. */\n vectorAuthToken?: string;\n}\n\n/**\n * PostgreSQL storage configuration.\n */\nexport interface PgStorageConfig {\n backend: 'pg';\n connectionString?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n schemaName?: string;\n disableInit?: boolean;\n skipDefaultIndexes?: boolean;\n}\n\n/**\n * Resolved storage configuration for either backend.\n */\nexport type StorageConfig = LibSQLStorageConfig | PgStorageConfig;\n\n/**\n * Get the resolved storage configuration.\n *\n * Priority (highest to lowest):\n * 1. Environment variables: MASTRA_STORAGE_BACKEND + backend-specific vars\n * 2. Global settings (from /settings): settings.storage\n * 3. Legacy config files: .mastracode/database.json (LibSQL only)\n * 4. Local file database (LibSQL default)\n *\n * For PG, the env vars are:\n * MASTRA_STORAGE_BACKEND=pg\n * MASTRA_PG_CONNECTION_STRING or MASTRA_PG_HOST/PORT/DATABASE/USER/PASSWORD\n * MASTRA_PG_SCHEMA_NAME (optional)\n *\n * For LibSQL, the legacy env vars still work:\n * MASTRA_DB_URL + MASTRA_DB_AUTH_TOKEN\n */\nexport function getStorageConfig(\n projectDir?: string,\n storageSettings?: StorageSettings,\n configDirName = DEFAULT_CONFIG_DIR,\n): StorageConfig {\n // 1. Environment variable — explicit backend selection\n const envBackend = process.env.MASTRA_STORAGE_BACKEND as StorageBackend | undefined;\n\n if (envBackend === 'pg') {\n return resolvePgFromEnv();\n }\n\n // Legacy LibSQL env vars (MASTRA_DB_URL) — treat as explicit libsql\n if (envBackend === 'libsql' || process.env.MASTRA_DB_URL) {\n return resolveLibSQLFromEnv();\n }\n\n // 2. Global settings (from /settings)\n if (storageSettings && storageSettings.backend === 'pg') {\n return resolvePgFromSettings(storageSettings);\n }\n\n if (storageSettings && storageSettings.backend === 'libsql' && storageSettings.libsql.url) {\n return {\n backend: 'libsql',\n url: storageSettings.libsql.url,\n authToken: storageSettings.libsql.authToken,\n isRemote: !storageSettings.libsql.url.startsWith('file:'),\n };\n }\n\n // 3. Legacy project/global config files (.mastracode/database.json)\n if (projectDir) {\n const projectConfig = loadDatabaseConfig(path.join(projectDir, configDirName, 'database.json'));\n if (projectConfig) return projectConfig;\n }\n const globalConfig = loadDatabaseConfig(path.join(os.homedir(), configDirName, 'database.json'));\n if (globalConfig) return globalConfig;\n\n // 4. Default: local LibSQL file database\n return {\n backend: 'libsql',\n url: `file:${getDatabasePath()}`,\n isRemote: false,\n };\n}\n\nfunction resolveLibSQLFromEnv(): LibSQLStorageConfig {\n const url = process.env.MASTRA_DB_URL!;\n return {\n backend: 'libsql',\n url,\n authToken: process.env.MASTRA_DB_AUTH_TOKEN,\n isRemote: !url.startsWith('file:'),\n };\n}\n\nfunction resolvePgFromEnv(): PgStorageConfig {\n const connectionString = process.env.MASTRA_PG_CONNECTION_STRING;\n if (connectionString) {\n return {\n backend: 'pg',\n connectionString,\n schemaName: process.env.MASTRA_PG_SCHEMA_NAME,\n };\n }\n\n // Host/port style\n return {\n backend: 'pg',\n host: process.env.MASTRA_PG_HOST,\n port: process.env.MASTRA_PG_PORT ? parseInt(process.env.MASTRA_PG_PORT, 10) : undefined,\n database: process.env.MASTRA_PG_DATABASE,\n user: process.env.MASTRA_PG_USER,\n password: process.env.MASTRA_PG_PASSWORD,\n schemaName: process.env.MASTRA_PG_SCHEMA_NAME,\n };\n}\n\nfunction resolvePgFromSettings(settings: StorageSettings): PgStorageConfig {\n const pg = settings.pg;\n return {\n backend: 'pg',\n connectionString: pg.connectionString,\n host: pg.host,\n port: pg.port,\n database: pg.database,\n user: pg.user,\n password: pg.password,\n schemaName: pg.schemaName,\n disableInit: pg.disableInit,\n skipDefaultIndexes: pg.skipDefaultIndexes,\n };\n}\n\n/**\n * Load database config from a legacy JSON file.\n * Expected format: { \"url\": \"libsql://...\", \"authToken\": \"...\" }\n */\nfunction loadDatabaseConfig(filePath: string): LibSQLStorageConfig | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (typeof parsed?.url === 'string' && parsed.url) {\n return {\n backend: 'libsql',\n url: parsed.url,\n authToken: typeof parsed.authToken === 'string' ? parsed.authToken : undefined,\n isRemote: !parsed.url.startsWith('file:'),\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the current user identity.\n *\n * Priority:\n * 1. MASTRA_USER_ID environment variable\n * 2. git config user.email (from project dir or global)\n * 3. OS username as fallback\n */\nexport function getUserId(projectDir?: string): string {\n // 1. Environment variable override\n if (process.env.MASTRA_USER_ID) {\n return process.env.MASTRA_USER_ID;\n }\n\n // 2. git user.email\n const cwd = projectDir || process.cwd();\n const email = git('config user.email', cwd);\n if (email) {\n return email;\n }\n\n // 3. OS username fallback\n return os.userInfo().username || 'unknown';\n}\n\n/**\n * Get the current user's display name.\n *\n * Priority:\n * 1. git config user.name\n * 2. OS username as fallback\n */\nexport function getUserName(projectDir?: string): string {\n const cwd = projectDir || process.cwd();\n const name = git('config user.name', cwd);\n if (name) {\n return name;\n }\n return os.userInfo().username || 'unknown';\n}\n\n/**\n * Observational memory scope: \"thread\" (per-conversation) or \"resource\" (shared across threads).\n */\nexport type OmScope = 'thread' | 'resource';\n\n/**\n * Get the configured observational memory scope.\n *\n * Priority:\n * 1. MASTRA_OM_SCOPE environment variable (\"thread\" or \"resource\")\n * 2. Project config: .mastracode/database.json → omScope\n * 3. Global config: ~/.mastracode/database.json → omScope\n * 4. Default: \"thread\"\n */\nexport function getOmScope(projectDir?: string, configDirName = DEFAULT_CONFIG_DIR): OmScope {\n // 1. Environment variable\n const envScope = process.env.MASTRA_OM_SCOPE;\n if (envScope === 'thread' || envScope === 'resource') {\n return envScope;\n }\n\n // 2. Project-level config\n if (projectDir) {\n const scope = loadOmScopeFromConfig(path.join(projectDir, configDirName, 'database.json'));\n if (scope) return scope;\n }\n\n // 3. Global config\n const scope = loadOmScopeFromConfig(path.join(os.homedir(), configDirName, 'database.json'));\n if (scope) return scope;\n\n // 4. Default\n return 'thread';\n}\n\nfunction loadOmScopeFromConfig(filePath: string): OmScope | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (parsed?.omScope === 'thread' || parsed?.omScope === 'resource') {\n return parsed.omScope;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get an explicit resource ID override, if configured.\n *\n * Resource IDs act as shared tags — two users who set the same resourceId\n * will share threads and observations for that resource.\n *\n * Priority:\n * 1. MASTRA_RESOURCE_ID environment variable\n * 2. Project config: .mastracode/database.json → resourceId\n * 3. Global config: ~/.mastracode/database.json → resourceId\n * 4. null (use auto-detected value)\n */\nexport function getResourceIdOverride(projectDir?: string, configDirName = DEFAULT_CONFIG_DIR): string | null {\n // 1. Environment variable\n if (process.env.MASTRA_RESOURCE_ID) {\n return process.env.MASTRA_RESOURCE_ID;\n }\n\n // 2. Project-level config\n if (projectDir) {\n const rid = loadStringField(path.join(projectDir, configDirName, 'database.json'), 'resourceId');\n if (rid) return rid;\n }\n\n // 3. Global config\n const rid = loadStringField(path.join(os.homedir(), configDirName, 'database.json'), 'resourceId');\n if (rid) return rid;\n\n return null;\n}\n\nfunction loadStringField(filePath: string, field: string): string | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n const value = parsed?.[field];\n if (typeof value === 'string' && value) {\n return value;\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["scope","rid"]}
1
+ {"version":3,"sources":["../../src/utils/project.ts"],"sourcesContent":["/**\n * Project detection utilities\n *\n * Detects project identity from git repo or filesystem path.\n * Handles git worktrees by finding the main repository.\n */\n\nimport { execFile, execSync } from 'node:child_process';\nimport { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nexport interface ProjectInfo {\n /** Unique resource ID for this project (used for thread grouping) */\n resourceId: string;\n /** Human-readable project name */\n name: string;\n /** Absolute path to the project root */\n rootPath: string;\n /** Git remote URL if available */\n gitUrl?: string;\n /** Current git branch */\n gitBranch?: string;\n /** Whether this is a git worktree */\n isWorktree: boolean;\n /** Path to main git repo (different from rootPath if worktree) */\n mainRepoPath?: string;\n /** Whether the resourceId was explicitly overridden (env var or config) */\n resourceIdOverride?: boolean;\n}\n\n/**\n * Run a git command and return stdout, or undefined if it fails\n */\nfunction git(args: string, cwd: string): string | undefined {\n try {\n return execSync(`git ${args}`, {\n cwd,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }).trim();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Slugify a string for use in IDs\n */\nfunction slugify(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\n/**\n * Create a short hash of a string\n */\nfunction shortHash(str: string): string {\n return createHash('sha256').update(str).digest('hex').slice(0, 12);\n}\n\n/**\n * Normalize a git URL to a canonical form for comparison\n * - Removes .git suffix\n * - Converts SSH to HTTPS format for consistency\n * - Lowercases\n */\nfunction normalizeGitUrl(url: string): string {\n return url\n .replace(/\\.git$/, '')\n .replace(/^git@([^:]+):/, 'https://$1/')\n .replace(/^ssh:\\/\\/git@/, 'https://')\n .toLowerCase();\n}\n\n/**\n * Detect project info from a directory path\n */\nexport function detectProject(projectPath: string): ProjectInfo {\n const absolutePath = path.resolve(projectPath);\n\n // Check if this is a git repo\n const gitDir = git('rev-parse --git-dir', absolutePath);\n const isGitRepo = gitDir !== undefined;\n\n let rootPath = absolutePath;\n let gitUrl: string | undefined;\n let gitBranch: string | undefined;\n let isWorktree = false;\n let mainRepoPath: string | undefined;\n\n if (isGitRepo) {\n // Get the repo root (handles being in a subdirectory)\n rootPath = git('rev-parse --show-toplevel', absolutePath) || absolutePath;\n\n // Check for worktree - git-common-dir differs from git-dir in worktrees\n const commonDir = git('rev-parse --git-common-dir', absolutePath);\n if (commonDir && commonDir !== '.git' && commonDir !== gitDir) {\n isWorktree = true;\n // The common dir is inside the main repo's .git folder\n mainRepoPath = path.dirname(path.resolve(rootPath, commonDir));\n }\n\n // Get remote URL (prefer origin, fall back to first remote)\n gitUrl = git('remote get-url origin', absolutePath);\n if (!gitUrl) {\n const remotes = git('remote', absolutePath);\n if (remotes) {\n const firstRemote = remotes.split('\\n')[0];\n if (firstRemote) {\n gitUrl = git(`remote get-url ${firstRemote}`, absolutePath);\n }\n }\n }\n\n // Get current branch\n gitBranch = git('rev-parse --abbrev-ref HEAD', absolutePath);\n }\n\n // Generate resource ID\n // Priority: normalized git URL > main repo path (for worktrees) > absolute path\n let resourceIdSource: string;\n if (gitUrl) {\n resourceIdSource = normalizeGitUrl(gitUrl);\n } else if (mainRepoPath) {\n resourceIdSource = mainRepoPath;\n } else {\n resourceIdSource = rootPath;\n }\n\n // Create a readable but unique resource ID\n // Format: slugified-name-shorthash\n const baseName = gitUrl\n ? gitUrl\n .split('/')\n .pop()\n ?.replace(/\\.git$/, '') || 'project'\n : path.basename(rootPath);\n\n const resourceId = `${slugify(baseName)}-${shortHash(resourceIdSource)}`;\n\n return {\n resourceId,\n name: baseName,\n rootPath,\n gitUrl,\n gitBranch,\n isWorktree,\n mainRepoPath,\n };\n}\n\n/**\n * Get the current git branch for a given directory.\n * Lightweight alternative to detectProject() for refreshing just the branch.\n */\nexport function getCurrentGitBranch(cwd: string): string | undefined {\n return git('rev-parse --abbrev-ref HEAD', cwd);\n}\n\n/**\n * Async version of getCurrentGitBranch — avoids blocking the event loop\n * with execSync. Falls back to undefined on any failure.\n */\nexport function getCurrentGitBranchAsync(cwd: string): Promise<string | undefined> {\n return new Promise(resolve => {\n execFile('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf-8' }, (err, stdout) => {\n if (err) {\n resolve(undefined);\n return;\n }\n const branch = stdout.trim();\n resolve(branch || undefined);\n });\n });\n}\n\n/**\n * Get the application data directory for mastracode\n * - macOS: ~/Library/Application Support/mastracode\n * - Linux: ~/.local/share/mastracode\n * - Windows: %APPDATA%/mastracode\n */\nexport function getAppDataDir(): string {\n if (process.env.MASTRA_APP_DATA_DIR) {\n fs.mkdirSync(process.env.MASTRA_APP_DATA_DIR, { recursive: true });\n return process.env.MASTRA_APP_DATA_DIR;\n }\n\n const platform = os.platform();\n let baseDir: string;\n\n if (platform === 'darwin') {\n baseDir = path.join(os.homedir(), 'Library', 'Application Support');\n } else if (platform === 'win32') {\n baseDir = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');\n } else {\n // Linux and others - follow XDG spec\n baseDir = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');\n }\n\n const appDir = path.join(baseDir, 'mastracode');\n\n // Ensure directory exists\n if (!fs.existsSync(appDir)) {\n fs.mkdirSync(appDir, { recursive: true });\n }\n\n return appDir;\n}\n/**\n * Get the database path for mastracode\n * Can be overridden with the MASTRA_DB_PATH environment variable for debugging.\n */\nexport function getDatabasePath(): string {\n if (process.env.MASTRA_DB_PATH) {\n return process.env.MASTRA_DB_PATH;\n }\n return path.join(getAppDataDir(), 'mastra.db');\n}\n\n/**\n * Get the vector database path for mastracode.\n * Separate from the main DB to avoid bloating it with embedding data.\n */\nexport function getVectorDatabasePath(): string {\n return path.join(getAppDataDir(), 'mastra-vectors.db');\n}\n\n/**\n * Get the observability DuckDB database path for mastracode.\n * Separate from the main DB — DuckDB is used for OLAP-style trace/score/feedback queries.\n * Can be overridden with the MASTRA_OBSERVABILITY_DB_PATH environment variable.\n */\nexport function getObservabilityDatabasePath(): string {\n if (process.env.MASTRA_OBSERVABILITY_DB_PATH) {\n return process.env.MASTRA_OBSERVABILITY_DB_PATH;\n }\n return path.join(getAppDataDir(), 'observability.duckdb');\n}\n\nimport type { StorageBackend, StorageSettings } from '../onboarding/settings.js';\n\n/**\n * LibSQL storage configuration.\n */\nexport interface LibSQLStorageConfig {\n backend: 'libsql';\n url: string;\n authToken?: string;\n isRemote: boolean;\n /**\n * Optional explicit url for the recall vector DB. When omitted, the factory\n * uses the shared default vector file.\n */\n vectorUrl?: string;\n /** Auth token for the vector DB when `vectorUrl` points at a remote libSQL. */\n vectorAuthToken?: string;\n}\n\n/**\n * PostgreSQL storage configuration.\n */\nexport interface PgStorageConfig {\n backend: 'pg';\n connectionString?: string;\n host?: string;\n port?: number;\n database?: string;\n user?: string;\n password?: string;\n schemaName?: string;\n disableInit?: boolean;\n skipDefaultIndexes?: boolean;\n}\n\n/**\n * Resolved storage configuration for either backend.\n */\nexport type StorageConfig = LibSQLStorageConfig | PgStorageConfig;\n\n/**\n * Get the resolved storage configuration.\n *\n * Priority (highest to lowest):\n * 1. Environment variables: MASTRA_STORAGE_BACKEND + backend-specific vars\n * 2. Global settings (from /settings): settings.storage\n * 3. Legacy config files: .mastracode/database.json (LibSQL only)\n * 4. Local file database (LibSQL default)\n *\n * For PG, the env vars are:\n * MASTRA_STORAGE_BACKEND=pg\n * MASTRA_PG_CONNECTION_STRING or MASTRA_PG_HOST/PORT/DATABASE/USER/PASSWORD\n * MASTRA_PG_SCHEMA_NAME (optional)\n *\n * For LibSQL, the legacy env vars still work:\n * MASTRA_DB_URL + MASTRA_DB_AUTH_TOKEN\n */\nexport function getStorageConfig(\n projectDir?: string,\n storageSettings?: StorageSettings,\n configDirName = DEFAULT_CONFIG_DIR,\n): StorageConfig {\n // 1. Environment variable — explicit backend selection\n const envBackend = process.env.MASTRA_STORAGE_BACKEND as StorageBackend | undefined;\n\n if (envBackend === 'pg') {\n return resolvePgFromEnv();\n }\n\n // Legacy LibSQL env vars (MASTRA_DB_URL) — treat as explicit libsql\n if (envBackend === 'libsql' || process.env.MASTRA_DB_URL) {\n return resolveLibSQLFromEnv();\n }\n\n // 2. Global settings (from /settings)\n if (storageSettings && storageSettings.backend === 'pg') {\n return resolvePgFromSettings(storageSettings);\n }\n\n if (storageSettings && storageSettings.backend === 'libsql' && storageSettings.libsql.url) {\n return {\n backend: 'libsql',\n url: storageSettings.libsql.url,\n authToken: storageSettings.libsql.authToken,\n isRemote: !storageSettings.libsql.url.startsWith('file:'),\n };\n }\n\n // 3. Legacy project/global config files (.mastracode/database.json)\n if (projectDir) {\n const projectConfig = loadDatabaseConfig(path.join(projectDir, configDirName, 'database.json'));\n if (projectConfig) return projectConfig;\n }\n const globalConfig = loadDatabaseConfig(path.join(os.homedir(), configDirName, 'database.json'));\n if (globalConfig) return globalConfig;\n\n // 4. Default: local LibSQL file database\n return {\n backend: 'libsql',\n url: `file:${getDatabasePath()}`,\n isRemote: false,\n };\n}\n\nfunction resolveLibSQLFromEnv(): LibSQLStorageConfig {\n const url = process.env.MASTRA_DB_URL!;\n return {\n backend: 'libsql',\n url,\n authToken: process.env.MASTRA_DB_AUTH_TOKEN,\n isRemote: !url.startsWith('file:'),\n };\n}\n\nfunction resolvePgFromEnv(): PgStorageConfig {\n const connectionString = process.env.MASTRA_PG_CONNECTION_STRING;\n if (connectionString) {\n return {\n backend: 'pg',\n connectionString,\n schemaName: process.env.MASTRA_PG_SCHEMA_NAME,\n };\n }\n\n // Host/port style\n return {\n backend: 'pg',\n host: process.env.MASTRA_PG_HOST,\n port: process.env.MASTRA_PG_PORT ? parseInt(process.env.MASTRA_PG_PORT, 10) : undefined,\n database: process.env.MASTRA_PG_DATABASE,\n user: process.env.MASTRA_PG_USER,\n password: process.env.MASTRA_PG_PASSWORD,\n schemaName: process.env.MASTRA_PG_SCHEMA_NAME,\n };\n}\n\nfunction resolvePgFromSettings(settings: StorageSettings): PgStorageConfig {\n const pg = settings.pg;\n return {\n backend: 'pg',\n connectionString: pg.connectionString,\n host: pg.host,\n port: pg.port,\n database: pg.database,\n user: pg.user,\n password: pg.password,\n schemaName: pg.schemaName,\n disableInit: pg.disableInit,\n skipDefaultIndexes: pg.skipDefaultIndexes,\n };\n}\n\n/**\n * Load database config from a legacy JSON file.\n * Expected format: { \"url\": \"libsql://...\", \"authToken\": \"...\" }\n */\nfunction loadDatabaseConfig(filePath: string): LibSQLStorageConfig | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (typeof parsed?.url === 'string' && parsed.url) {\n return {\n backend: 'libsql',\n url: parsed.url,\n authToken: typeof parsed.authToken === 'string' ? parsed.authToken : undefined,\n isRemote: !parsed.url.startsWith('file:'),\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get the current user identity.\n *\n * Priority:\n * 1. MASTRA_USER_ID environment variable\n * 2. git config user.email (from project dir or global)\n * 3. OS username as fallback\n */\nexport function getUserId(projectDir?: string): string {\n // 1. Environment variable override\n if (process.env.MASTRA_USER_ID) {\n return process.env.MASTRA_USER_ID;\n }\n\n // 2. git user.email\n const cwd = projectDir || process.cwd();\n const email = git('config user.email', cwd);\n if (email) {\n return email;\n }\n\n // 3. OS username fallback\n return os.userInfo().username || 'unknown';\n}\n\n/**\n * Get the current user's display name.\n *\n * Priority:\n * 1. git config user.name\n * 2. OS username as fallback\n */\nexport function getUserName(projectDir?: string): string {\n const cwd = projectDir || process.cwd();\n const name = git('config user.name', cwd);\n if (name) {\n return name;\n }\n return os.userInfo().username || 'unknown';\n}\n\n/**\n * Observational memory scope: \"thread\" (per-conversation) or \"resource\" (shared across threads).\n */\nexport type OmScope = 'thread' | 'resource';\n\n/**\n * Get the configured observational memory scope.\n *\n * Priority:\n * 1. MASTRA_OM_SCOPE environment variable (\"thread\" or \"resource\")\n * 2. Project config: .mastracode/database.json → omScope\n * 3. Global config: ~/.mastracode/database.json → omScope\n * 4. Default: \"thread\"\n */\nexport function getOmScope(projectDir?: string, configDirName = DEFAULT_CONFIG_DIR): OmScope {\n // 1. Environment variable\n const envScope = process.env.MASTRA_OM_SCOPE;\n if (envScope === 'thread' || envScope === 'resource') {\n return envScope;\n }\n\n // 2. Project-level config\n if (projectDir) {\n const scope = loadOmScopeFromConfig(path.join(projectDir, configDirName, 'database.json'));\n if (scope) return scope;\n }\n\n // 3. Global config\n const scope = loadOmScopeFromConfig(path.join(os.homedir(), configDirName, 'database.json'));\n if (scope) return scope;\n\n // 4. Default\n return 'thread';\n}\n\nfunction loadOmScopeFromConfig(filePath: string): OmScope | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (parsed?.omScope === 'thread' || parsed?.omScope === 'resource') {\n return parsed.omScope;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Get an explicit resource ID override, if configured.\n *\n * Resource IDs act as shared tags — two users who set the same resourceId\n * will share threads and observations for that resource.\n *\n * Priority:\n * 1. MASTRA_RESOURCE_ID environment variable\n * 2. Project config: .mastracode/database.json → resourceId\n * 3. Global config: ~/.mastracode/database.json → resourceId\n * 4. null (use auto-detected value)\n */\nexport function getResourceIdOverride(projectDir?: string, configDirName = DEFAULT_CONFIG_DIR): string | null {\n // 1. Environment variable\n if (process.env.MASTRA_RESOURCE_ID) {\n return process.env.MASTRA_RESOURCE_ID;\n }\n\n // 2. Project-level config\n if (projectDir) {\n const rid = loadStringField(path.join(projectDir, configDirName, 'database.json'), 'resourceId');\n if (rid) return rid;\n }\n\n // 3. Global config\n const rid = loadStringField(path.join(os.homedir(), configDirName, 'database.json'), 'resourceId');\n if (rid) return rid;\n\n return null;\n}\n\nfunction loadStringField(filePath: string, field: string): string | null {\n try {\n if (!fs.existsSync(filePath)) return null;\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n const value = parsed?.[field];\n if (typeof value === 'string' && value) {\n return value;\n }\n return null;\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["scope","rid"]}
@@ -31,7 +31,11 @@ export declare function extractCommandName(filePath: string, baseDir: string): s
31
31
  * @param rootDir - Original root commands directory (used for namespace derivation).
32
32
  * When omitted the first call sets it to dirPath.
33
33
  */
34
- export declare function scanCommandDirectory(dirPath: string, rootDir?: string): Promise<SlashCommandMetadata[]>;
34
+ export interface ScanCommandDirectoryOptions {
35
+ allowedRoot?: string;
36
+ visitedDirectories?: Set<string>;
37
+ }
38
+ export declare function scanCommandDirectory(dirPath: string, rootDir?: string, options?: ScanCommandDirectoryOptions): Promise<SlashCommandMetadata[]>;
35
39
  /**
36
40
  * Load custom slash commands from all configured directories
37
41
  * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user
@@ -1 +1 @@
1
- {"version":3,"file":"slash-command-loader.d.ts","sourceRoot":"","sources":["../../src/utils/slash-command-loader.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAqD/G;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAY5E;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA2B7G;AACD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,SAAqB,EAClC,gBAAgB,GAAE,MAAM,EAAO,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA4DjC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,SAAqB,GAAG,MAAM,CAEpG;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,SAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjH"}
1
+ {"version":3,"file":"slash-command-loader.d.ts","sourceRoot":"","sources":["../../src/utils/slash-command-loader.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAqD/G;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAY5E;AAED;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CA4CjC;AACD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,SAAqB,EAClC,gBAAgB,GAAE,MAAM,EAAO,GAC9B,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAkEjC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,SAAqB,GAAG,MAAM,CAEpG;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,SAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjH"}
@@ -2,6 +2,7 @@ import { promises as fs } from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { DEFAULT_CONFIG_DIR } from "../constants.js";
5
+ import { isPathWithinRoot } from "./path-security.js";
5
6
  async function parseCommandFile(filePath, baseDir) {
6
7
  try {
7
8
  const content = await fs.readFile(filePath, "utf-8");
@@ -53,17 +54,32 @@ function extractCommandName(filePath, baseDir) {
53
54
  const namespace = dirName.replace(/[\\/]/g, ":");
54
55
  return `${namespace}:${baseName}`;
55
56
  }
56
- async function scanCommandDirectory(dirPath, rootDir) {
57
+ async function scanCommandDirectory(dirPath, rootDir, options = {}) {
57
58
  const baseDir = rootDir ?? dirPath;
58
59
  const commands = [];
60
+ const visitedDirectories = options.visitedDirectories ?? /* @__PURE__ */ new Set();
59
61
  try {
62
+ const realDirectory = await fs.realpath(dirPath);
63
+ const realAllowedRoot = options.allowedRoot ? await fs.realpath(options.allowedRoot) : void 0;
64
+ if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;
65
+ if (visitedDirectories.has(realDirectory)) return commands;
66
+ visitedDirectories.add(realDirectory);
60
67
  const entries = await fs.readdir(dirPath, { withFileTypes: true });
61
68
  for (const entry of entries) {
62
69
  const fullPath = path.join(dirPath, entry.name);
63
- if (entry.isDirectory()) {
64
- const subCommands = await scanCommandDirectory(fullPath, baseDir);
70
+ const stats = entry.isSymbolicLink() ? await fs.stat(fullPath).catch(() => null) : entry;
71
+ if (!stats) continue;
72
+ if (stats.isDirectory()) {
73
+ const subCommands = await scanCommandDirectory(fullPath, baseDir, {
74
+ ...options,
75
+ visitedDirectories
76
+ });
65
77
  commands.push(...subCommands);
66
- } else if (entry.isFile() && entry.name.endsWith(".md")) {
78
+ } else if (entry.name.endsWith(".md") && stats.isFile()) {
79
+ if (realAllowedRoot) {
80
+ const realFile = await fs.realpath(fullPath).catch(() => null);
81
+ if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;
82
+ }
67
83
  const command = await parseCommandFile(fullPath, baseDir);
68
84
  if (command) {
69
85
  commands.push(command);
@@ -99,21 +115,27 @@ async function loadCustomCommands(projectDir, configDirName = DEFAULT_CONFIG_DIR
99
115
  }
100
116
  if (projectDir) {
101
117
  const opencodeProjectDir = path.join(projectDir, ".opencode", "command");
102
- const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir);
118
+ const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir, void 0, {
119
+ allowedRoot: projectDir
120
+ });
103
121
  addCommands(opencodeProjectCommands);
104
122
  }
105
123
  if (projectDir) {
106
124
  const claudeProjectDir = path.join(projectDir, ".claude", "commands");
107
- const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir);
125
+ const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir, void 0, {
126
+ allowedRoot: projectDir
127
+ });
108
128
  addCommands(claudeProjectCommands);
109
129
  }
110
130
  if (projectDir) {
111
131
  const mastraProjectDir = path.join(projectDir, configDirName, "commands");
112
- const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir);
132
+ const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir, void 0, {
133
+ allowedRoot: projectDir
134
+ });
113
135
  addCommands(mastraProjectCommands);
114
136
  }
115
137
  for (const commandsDir of extraCommandDirs) {
116
- addCommands(await scanCommandDirectory(commandsDir));
138
+ addCommands(await scanCommandDirectory(commandsDir, void 0, { allowedRoot: commandsDir }));
117
139
  }
118
140
  return Array.from(commandMap.values());
119
141
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/slash-command-loader.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\n\n/**\n * Metadata for a slash command\n */\nexport interface SlashCommandMetadata {\n /** Command name (e.g., \"git:commit\") */\n name: string;\n /** Human-readable description */\n description: string;\n /** The command template with variables */\n template: string;\n /** Source file path */\n sourcePath: string;\n /** Namespace derived from directory structure */\n namespace?: string;\n /** Whether this command should also be exposed as /goal/<name> */\n goal?: boolean;\n}\n\n/**\n * Parse a command file and extract metadata and template\n * Supports both frontmatter-based and plain markdown files\n */\nexport async function parseCommandFile(filePath: string, baseDir?: string): Promise<SlashCommandMetadata | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const trimmedContent = content.trim();\n\n // Check if file has frontmatter (starts with ---)\n if (!trimmedContent.startsWith('---')) {\n // No frontmatter - treat entire file as template\n // Derive name from file path\n const name = baseDir ? extractCommandName(filePath, baseDir) : path.basename(filePath, '.md');\n\n return {\n name,\n description: '',\n template: content,\n sourcePath: filePath,\n };\n }\n\n // Split frontmatter and template\n const parts = content.split('---');\n if (parts.length < 3) {\n return null;\n }\n\n const frontmatter = parts[1]!.trim();\n const template = parts.slice(2).join('---').trim();\n\n // Parse YAML frontmatter\n const metadata = parseYaml(frontmatter) as Record<string, unknown>;\n\n // Derive name from file path if not specified in frontmatter\n let name: string;\n if (typeof metadata?.name === 'string' && metadata.name) {\n name = metadata.name;\n } else if (baseDir) {\n name = extractCommandName(filePath, baseDir);\n } else {\n name = path.basename(filePath, '.md');\n }\n\n return {\n name,\n description: typeof metadata?.description === 'string' ? metadata.description : '',\n template,\n sourcePath: filePath,\n namespace: typeof metadata?.namespace === 'string' ? metadata.namespace : undefined,\n goal: metadata?.goal === true,\n };\n } catch (error) {\n console.error(`Error parsing command file ${filePath}:`, error);\n return null;\n }\n}\n\n/**\n * Extract command name from file path\n * Converts path like \"git/commit.md\" to \"git:commit\"\n */\nexport function extractCommandName(filePath: string, baseDir: string): string {\n const relativePath = path.relative(baseDir, filePath);\n const dirName = path.dirname(relativePath);\n const baseName = path.basename(relativePath, '.md');\n\n if (dirName === '.' || dirName === '') {\n return baseName;\n }\n\n // Replace path separators with colons for namespacing\n const namespace = dirName.replace(/[\\\\/]/g, ':');\n return `${namespace}:${baseName}`;\n}\n\n/**\n * Recursively scan a directory for command files.\n * @param dirPath - Current directory to scan\n * @param rootDir - Original root commands directory (used for namespace derivation).\n * When omitted the first call sets it to dirPath.\n */\nexport async function scanCommandDirectory(dirPath: string, rootDir?: string): Promise<SlashCommandMetadata[]> {\n const baseDir = rootDir ?? dirPath;\n const commands: SlashCommandMetadata[] = [];\n\n try {\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n\n if (entry.isDirectory()) {\n // Recursively scan subdirectories, preserving the root directory for namespace derivation\n const subCommands = await scanCommandDirectory(fullPath, baseDir);\n commands.push(...subCommands);\n } else if (entry.isFile() && entry.name.endsWith('.md')) {\n // Parse markdown command files, passing the root commands dir as baseDir for name derivation\n const command = await parseCommandFile(fullPath, baseDir);\n if (command) {\n commands.push(command);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read - silently skip\n }\n\n return commands;\n}\n/**\n * Load custom slash commands from all configured directories\n * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user\n */\nexport async function loadCustomCommands(\n projectDir?: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraCommandDirs: string[] = [],\n): Promise<SlashCommandMetadata[]> {\n // Use a Map so later (higher priority) sources override earlier ones with the same name\n const commandMap = new Map<string, SlashCommandMetadata>();\n\n const addCommands = (newCommands: SlashCommandMetadata[]) => {\n for (const cmd of newCommands) {\n commandMap.set(cmd.name, cmd);\n }\n };\n\n const homeDir = process.env.HOME || process.env.USERPROFILE;\n\n // 1. Load from opencode user directory ~/.opencode/command (lowest priority)\n if (homeDir) {\n const opencodeUserDir = path.join(homeDir, '.opencode', 'command');\n const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);\n addCommands(opencodeUserCommands);\n }\n\n // 2. Load from claude user directory ~/.claude/commands (Claude Code compat)\n if (homeDir) {\n const claudeUserDir = path.join(homeDir, '.claude', 'commands');\n const claudeUserCommands = await scanCommandDirectory(claudeUserDir);\n addCommands(claudeUserCommands);\n }\n\n // 3. Load from mastra user directory ~/<configDirName>/commands\n if (homeDir) {\n const mastraUserDir = path.join(homeDir, configDirName, 'commands');\n const mastraUserCommands = await scanCommandDirectory(mastraUserDir);\n addCommands(mastraUserCommands);\n }\n\n // 4. Load from opencode project directory .opencode/command\n if (projectDir) {\n const opencodeProjectDir = path.join(projectDir, '.opencode', 'command');\n const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir);\n addCommands(opencodeProjectCommands);\n }\n\n // 5. Load from claude project directory .claude/commands (Claude Code compat)\n if (projectDir) {\n const claudeProjectDir = path.join(projectDir, '.claude', 'commands');\n const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir);\n addCommands(claudeProjectCommands);\n }\n\n // 6. Load from mastra project directory <configDirName>/commands\n if (projectDir) {\n const mastraProjectDir = path.join(projectDir, configDirName, 'commands');\n const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir);\n addCommands(mastraProjectCommands);\n }\n\n // 7. Load from active plugin command directories (highest priority)\n for (const commandsDir of extraCommandDirs) {\n addCommands(await scanCommandDirectory(commandsDir));\n }\n\n return Array.from(commandMap.values());\n}\n\n/**\n * Get the commands directory path for a project\n */\nexport function getProjectCommandsDir(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'commands');\n}\n\n/**\n * Initialize a commands directory with an example command\n */\nexport async function initCommandsDirectory(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): Promise<void> {\n const commandsDir = getProjectCommandsDir(projectDir, configDirName);\n\n try {\n await fs.mkdir(commandsDir, { recursive: true });\n\n // Create an example command\n const examplePath = path.join(commandsDir, 'example.md');\n const exampleContent = `---\nname: example\ndescription: An example slash command\n---\n\nThis is an example slash command template.\nYou can use variables like \\$ARGUMENTS or \\$1, \\$2 for positional args.\nYou can also include file content with @filename.\nShell commands with !command will be executed and output included.\n`;\n\n try {\n await fs.access(examplePath);\n // File already exists, don't overwrite\n } catch {\n await fs.writeFile(examplePath, exampleContent, 'utf-8');\n }\n } catch (error) {\n console.error('Error initializing commands directory:', error);\n }\n}\n"],"mappings":"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","names":["name"]}
1
+ {"version":3,"sources":["../../src/utils/slash-command-loader.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport * as path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { DEFAULT_CONFIG_DIR } from '../constants.js';\nimport { isPathWithinRoot } from './path-security.js';\n\n/**\n * Metadata for a slash command\n */\nexport interface SlashCommandMetadata {\n /** Command name (e.g., \"git:commit\") */\n name: string;\n /** Human-readable description */\n description: string;\n /** The command template with variables */\n template: string;\n /** Source file path */\n sourcePath: string;\n /** Namespace derived from directory structure */\n namespace?: string;\n /** Whether this command should also be exposed as /goal/<name> */\n goal?: boolean;\n}\n\n/**\n * Parse a command file and extract metadata and template\n * Supports both frontmatter-based and plain markdown files\n */\nexport async function parseCommandFile(filePath: string, baseDir?: string): Promise<SlashCommandMetadata | null> {\n try {\n const content = await fs.readFile(filePath, 'utf-8');\n const trimmedContent = content.trim();\n\n // Check if file has frontmatter (starts with ---)\n if (!trimmedContent.startsWith('---')) {\n // No frontmatter - treat entire file as template\n // Derive name from file path\n const name = baseDir ? extractCommandName(filePath, baseDir) : path.basename(filePath, '.md');\n\n return {\n name,\n description: '',\n template: content,\n sourcePath: filePath,\n };\n }\n\n // Split frontmatter and template\n const parts = content.split('---');\n if (parts.length < 3) {\n return null;\n }\n\n const frontmatter = parts[1]!.trim();\n const template = parts.slice(2).join('---').trim();\n\n // Parse YAML frontmatter\n const metadata = parseYaml(frontmatter) as Record<string, unknown>;\n\n // Derive name from file path if not specified in frontmatter\n let name: string;\n if (typeof metadata?.name === 'string' && metadata.name) {\n name = metadata.name;\n } else if (baseDir) {\n name = extractCommandName(filePath, baseDir);\n } else {\n name = path.basename(filePath, '.md');\n }\n\n return {\n name,\n description: typeof metadata?.description === 'string' ? metadata.description : '',\n template,\n sourcePath: filePath,\n namespace: typeof metadata?.namespace === 'string' ? metadata.namespace : undefined,\n goal: metadata?.goal === true,\n };\n } catch (error) {\n console.error(`Error parsing command file ${filePath}:`, error);\n return null;\n }\n}\n\n/**\n * Extract command name from file path\n * Converts path like \"git/commit.md\" to \"git:commit\"\n */\nexport function extractCommandName(filePath: string, baseDir: string): string {\n const relativePath = path.relative(baseDir, filePath);\n const dirName = path.dirname(relativePath);\n const baseName = path.basename(relativePath, '.md');\n\n if (dirName === '.' || dirName === '') {\n return baseName;\n }\n\n // Replace path separators with colons for namespacing\n const namespace = dirName.replace(/[\\\\/]/g, ':');\n return `${namespace}:${baseName}`;\n}\n\n/**\n * Recursively scan a directory for command files.\n * @param dirPath - Current directory to scan\n * @param rootDir - Original root commands directory (used for namespace derivation).\n * When omitted the first call sets it to dirPath.\n */\nexport interface ScanCommandDirectoryOptions {\n allowedRoot?: string;\n visitedDirectories?: Set<string>;\n}\n\nexport async function scanCommandDirectory(\n dirPath: string,\n rootDir?: string,\n options: ScanCommandDirectoryOptions = {},\n): Promise<SlashCommandMetadata[]> {\n const baseDir = rootDir ?? dirPath;\n const commands: SlashCommandMetadata[] = [];\n const visitedDirectories = options.visitedDirectories ?? new Set<string>();\n\n try {\n const realDirectory = await fs.realpath(dirPath);\n const realAllowedRoot = options.allowedRoot ? await fs.realpath(options.allowedRoot) : undefined;\n if (realAllowedRoot && !isPathWithinRoot(realDirectory, realAllowedRoot)) return commands;\n if (visitedDirectories.has(realDirectory)) return commands;\n visitedDirectories.add(realDirectory);\n\n const entries = await fs.readdir(dirPath, { withFileTypes: true });\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry.name);\n const stats = entry.isSymbolicLink() ? await fs.stat(fullPath).catch(() => null) : entry;\n if (!stats) continue;\n\n if (stats.isDirectory()) {\n // Recursively scan subdirectories, preserving the root directory for namespace derivation\n const subCommands = await scanCommandDirectory(fullPath, baseDir, {\n ...options,\n visitedDirectories,\n });\n commands.push(...subCommands);\n } else if (entry.name.endsWith('.md') && stats.isFile()) {\n if (realAllowedRoot) {\n const realFile = await fs.realpath(fullPath).catch(() => null);\n if (!realFile || !isPathWithinRoot(realFile, realAllowedRoot)) continue;\n }\n\n // Parse markdown command files, passing the root commands dir as baseDir for name derivation\n const command = await parseCommandFile(fullPath, baseDir);\n if (command) {\n commands.push(command);\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read - silently skip\n }\n\n return commands;\n}\n/**\n * Load custom slash commands from all configured directories\n * Priority: mastra project > claude project > opencode project > mastra user > claude user > opencode user\n */\nexport async function loadCustomCommands(\n projectDir?: string,\n configDirName = DEFAULT_CONFIG_DIR,\n extraCommandDirs: string[] = [],\n): Promise<SlashCommandMetadata[]> {\n // Use a Map so later (higher priority) sources override earlier ones with the same name\n const commandMap = new Map<string, SlashCommandMetadata>();\n\n const addCommands = (newCommands: SlashCommandMetadata[]) => {\n for (const cmd of newCommands) {\n commandMap.set(cmd.name, cmd);\n }\n };\n\n const homeDir = process.env.HOME || process.env.USERPROFILE;\n\n // 1. Load from opencode user directory ~/.opencode/command (lowest priority)\n if (homeDir) {\n const opencodeUserDir = path.join(homeDir, '.opencode', 'command');\n const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);\n addCommands(opencodeUserCommands);\n }\n\n // 2. Load from claude user directory ~/.claude/commands (Claude Code compat)\n if (homeDir) {\n const claudeUserDir = path.join(homeDir, '.claude', 'commands');\n const claudeUserCommands = await scanCommandDirectory(claudeUserDir);\n addCommands(claudeUserCommands);\n }\n\n // 3. Load from mastra user directory ~/<configDirName>/commands\n if (homeDir) {\n const mastraUserDir = path.join(homeDir, configDirName, 'commands');\n const mastraUserCommands = await scanCommandDirectory(mastraUserDir);\n addCommands(mastraUserCommands);\n }\n\n // 4. Load from opencode project directory .opencode/command\n if (projectDir) {\n const opencodeProjectDir = path.join(projectDir, '.opencode', 'command');\n const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(opencodeProjectCommands);\n }\n\n // 5. Load from claude project directory .claude/commands (Claude Code compat)\n if (projectDir) {\n const claudeProjectDir = path.join(projectDir, '.claude', 'commands');\n const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(claudeProjectCommands);\n }\n\n // 6. Load from mastra project directory <configDirName>/commands\n if (projectDir) {\n const mastraProjectDir = path.join(projectDir, configDirName, 'commands');\n const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir, undefined, {\n allowedRoot: projectDir,\n });\n addCommands(mastraProjectCommands);\n }\n\n // 7. Load from active plugin command directories (highest priority)\n for (const commandsDir of extraCommandDirs) {\n addCommands(await scanCommandDirectory(commandsDir, undefined, { allowedRoot: commandsDir }));\n }\n\n return Array.from(commandMap.values());\n}\n\n/**\n * Get the commands directory path for a project\n */\nexport function getProjectCommandsDir(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): string {\n return path.join(projectDir, configDirName, 'commands');\n}\n\n/**\n * Initialize a commands directory with an example command\n */\nexport async function initCommandsDirectory(projectDir: string, configDirName = DEFAULT_CONFIG_DIR): Promise<void> {\n const commandsDir = getProjectCommandsDir(projectDir, configDirName);\n\n try {\n await fs.mkdir(commandsDir, { recursive: true });\n\n // Create an example command\n const examplePath = path.join(commandsDir, 'example.md');\n const exampleContent = `---\nname: example\ndescription: An example slash command\n---\n\nThis is an example slash command template.\nYou can use variables like \\$ARGUMENTS or \\$1, \\$2 for positional args.\nYou can also include file content with @filename.\nShell commands with !command will be executed and output included.\n`;\n\n try {\n await fs.access(examplePath);\n // File already exists, don't overwrite\n } catch {\n await fs.writeFile(examplePath, exampleContent, 'utf-8');\n }\n } catch (error) {\n console.error('Error initializing commands directory:', error);\n }\n}\n"],"mappings":"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","names":["name"]}
@@ -4,7 +4,7 @@
4
4
  * If PG is selected but fails to connect, falls back to LibSQL so the TUI
5
5
  * can start and the user can fix the connection via /settings.
6
6
  */
7
- import type { MastraCompositeStore } from '@mastra/core/storage';
7
+ import type { MastraCompositeStore, RetentionConfig } from '@mastra/core/storage';
8
8
  import type { MastraVector } from '@mastra/core/vector';
9
9
  import type { StorageConfig } from './project.js';
10
10
  export declare const MASTRA_CODE_LOCAL_PRAGMAS: {
@@ -13,13 +13,13 @@ export declare const MASTRA_CODE_LOCAL_PRAGMAS: {
13
13
  };
14
14
  /**
15
15
  * Construct a LibSQL store for an arbitrary url/authToken, applying the same
16
- * local pragmas the default factory uses. Shared so per-tenant storage
17
- * (see `web/tenant-storage.ts`) doesn't duplicate the construction.
16
+ * local pragmas the default factory uses.
18
17
  */
19
18
  export declare function buildLibSQLStore(opts: {
20
19
  id?: string;
21
20
  url: string;
22
21
  authToken?: string;
22
+ retention?: RetentionConfig;
23
23
  }): MastraCompositeStore;
24
24
  /** Construct a LibSQL vector store for an arbitrary url/authToken. */
25
25
  export declare function buildLibSQLVector(opts: {
@@ -1 +1 @@
1
- {"version":3,"file":"storage-factory.d.ts","sourceRoot":"","sources":["../../src/utils/storage-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAGnE,eAAO,MAAM,yBAAyB;;;CAGrC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,oBAAoB,CAO7G;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAMtG;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAUjF;AAwDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,aAAa,EACrB,gBAAgB,GAAE,QAAQ,GAAG,IAAqB,GACjD,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC"}
1
+ {"version":3,"file":"storage-factory.d.ts","sourceRoot":"","sources":["../../src/utils/storage-factory.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAInE,eAAO,MAAM,yBAAyB;;;CAGrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B,GAAG,oBAAoB,CAQvB;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,YAAY,CAMtG;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,oBAAoB,CAAC;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAUjF;AAyDD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,aAAa,EACrB,gBAAgB,GAAE,QAAQ,GAAG,IAAqB,GACjD,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAuBnC"}
@@ -1,6 +1,7 @@
1
1
  import { LibSQLStore, LibSQLVector } from "@mastra/libsql";
2
2
  import { PostgresStore } from "@mastra/pg";
3
3
  import { getDatabasePath, getVectorDatabasePath } from "./project.js";
4
+ import { DEFAULT_RETENTION } from "./storage-maintenance.js";
4
5
  const MASTRA_CODE_LOCAL_PRAGMAS = {
5
6
  cacheSize: -128e3,
6
7
  mmapSize: 536870912
@@ -10,6 +11,7 @@ function buildLibSQLStore(opts) {
10
11
  id: opts.id ?? "mastra-code-storage",
11
12
  url: opts.url,
12
13
  ...opts.authToken ? { authToken: opts.authToken } : {},
14
+ ...opts.retention ? { retention: opts.retention } : {},
13
15
  localPragmas: MASTRA_CODE_LOCAL_PRAGMAS
14
16
  });
15
17
  }
@@ -21,14 +23,14 @@ function buildLibSQLVector(opts) {
21
23
  });
22
24
  }
23
25
  function createFallbackLibSQL() {
24
- return buildLibSQLStore({ url: `file:${getDatabasePath()}` });
26
+ return buildLibSQLStore({ url: `file:${getDatabasePath()}`, retention: DEFAULT_RETENTION });
25
27
  }
26
28
  async function createStorage(config) {
27
29
  if (config.backend === "pg") {
28
30
  return createPgStorage(config);
29
31
  }
30
32
  return {
31
- storage: buildLibSQLStore({ url: config.url, authToken: config.authToken }),
33
+ storage: buildLibSQLStore({ url: config.url, authToken: config.authToken, retention: DEFAULT_RETENTION }),
32
34
  backend: "libsql"
33
35
  };
34
36
  }
@@ -42,6 +44,7 @@ async function createPgStorage(config) {
42
44
  }
43
45
  const base = {
44
46
  id: "mastra-code-storage",
47
+ retention: DEFAULT_RETENTION,
45
48
  ...config.schemaName ? { schemaName: config.schemaName } : {},
46
49
  ...config.disableInit ? { disableInit: config.disableInit } : {},
47
50
  ...config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/storage-factory.ts"],"sourcesContent":["/**\n * Storage factory — creates the appropriate storage backend based on resolved config.\n *\n * If PG is selected but fails to connect, falls back to LibSQL so the TUI\n * can start and the user can fix the connection via /settings.\n */\n\nimport type { MastraCompositeStore } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLStore, LibSQLVector } from '@mastra/libsql';\nimport { PostgresStore } from '@mastra/pg';\n\nimport type { StorageConfig, PgStorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\n\nexport const MASTRA_CODE_LOCAL_PRAGMAS = {\n cacheSize: -128000,\n mmapSize: 536870912,\n};\n\n/**\n * Construct a LibSQL store for an arbitrary url/authToken, applying the same\n * local pragmas the default factory uses. Shared so per-tenant storage\n * (see `web/tenant-storage.ts`) doesn't duplicate the construction.\n */\nexport function buildLibSQLStore(opts: { id?: string; url: string; authToken?: string }): MastraCompositeStore {\n return new LibSQLStore({\n id: opts.id ?? 'mastra-code-storage',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n localPragmas: MASTRA_CODE_LOCAL_PRAGMAS,\n });\n}\n\n/** Construct a LibSQL vector store for an arbitrary url/authToken. */\nexport function buildLibSQLVector(opts: { id?: string; url: string; authToken?: string }): MastraVector {\n return new LibSQLVector({\n id: opts.id ?? 'mastra-code-vectors',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n });\n}\n\nexport interface StorageResult {\n storage: MastraCompositeStore;\n /** The effective backend after any fallback logic has run. */\n backend: 'libsql' | 'pg';\n /** Non-null when PG was requested but failed — contains a user-facing warning. */\n warning?: string;\n}\n\nfunction createFallbackLibSQL(): MastraCompositeStore {\n return buildLibSQLStore({ url: `file:${getDatabasePath()}` });\n}\n\n/**\n * Create a storage instance from the resolved config.\n *\n * - `libsql` backend → LibSQLStore (always available)\n * - `pg` backend → PostgresStore, falls back to LibSQL on connection failure\n */\nexport async function createStorage(config: StorageConfig): Promise<StorageResult> {\n if (config.backend === 'pg') {\n return createPgStorage(config);\n }\n\n // Default: LibSQL\n return {\n storage: buildLibSQLStore({ url: config.url, authToken: config.authToken }),\n backend: 'libsql',\n };\n}\n\nasync function createPgStorage(config: PgStorageConfig): Promise<StorageResult> {\n // No connection info → fall back with guidance\n if (!config.connectionString && !config.host) {\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n 'PostgreSQL backend selected but no connection info configured. ' +\n 'Using LibSQL fallback. Set a connection string via /settings.',\n };\n }\n\n const base = {\n id: 'mastra-code-storage' as const,\n ...(config.schemaName ? { schemaName: config.schemaName } : {}),\n ...(config.disableInit ? { disableInit: config.disableInit } : {}),\n ...(config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}),\n };\n\n const store = config.connectionString\n ? new PostgresStore({ ...base, connectionString: config.connectionString })\n : new PostgresStore({\n ...base,\n host: config.host!,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n });\n\n // Test the connection before committing — if it fails, fall back to LibSQL\n // so the user can fix the config via /settings.\n try {\n await store.init();\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const target = config.connectionString ?? `${config.host}:${config.port ?? 5432}`;\n try {\n await store.close();\n } catch {\n // ignore cleanup errors\n }\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n `Failed to connect to PostgreSQL at ${target}: ${msg}\\n` +\n 'Using LibSQL fallback. Fix the connection via /settings.',\n };\n }\n\n return { storage: store, backend: 'pg' };\n}\n\n/**\n * Create a vector store for recall search.\n * Uses a separate LibSQL file to avoid bloating the main storage DB with embedding data.\n * For PG backends, reuses the same connection (PG handles the extra tables fine).\n */\nexport async function createVectorStore(\n config: StorageConfig,\n effectiveBackend: 'libsql' | 'pg' = config.backend,\n): Promise<MastraVector | undefined> {\n if (effectiveBackend === 'pg') {\n // PG can handle vector tables in the same database\n const pgConfig = config as PgStorageConfig;\n if (!pgConfig.connectionString && !pgConfig.host) return undefined;\n\n const { PgVector } = await import('@mastra/pg');\n return new PgVector({\n id: 'mastra-code-vectors',\n connectionString:\n pgConfig.connectionString ??\n `postgresql://${pgConfig.user}:${pgConfig.password}@${pgConfig.host}:${pgConfig.port ?? 5432}/${pgConfig.database}`,\n });\n }\n\n // LibSQL: separate file for vectors. Per-tenant configs supply an explicit\n // vectorUrl so each tenant's recall vectors are isolated; otherwise use the\n // shared default file.\n const libsqlConfig = config as { vectorUrl?: string; vectorAuthToken?: string };\n return buildLibSQLVector({\n url: libsqlConfig.vectorUrl ?? `file:${getVectorDatabasePath()}`,\n authToken: libsqlConfig.vectorAuthToken,\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/utils/storage-factory.ts"],"sourcesContent":["/**\n * Storage factory — creates the appropriate storage backend based on resolved config.\n *\n * If PG is selected but fails to connect, falls back to LibSQL so the TUI\n * can start and the user can fix the connection via /settings.\n */\n\nimport type { MastraCompositeStore, RetentionConfig } from '@mastra/core/storage';\nimport type { MastraVector } from '@mastra/core/vector';\nimport { LibSQLStore, LibSQLVector } from '@mastra/libsql';\nimport { PostgresStore } from '@mastra/pg';\n\nimport type { StorageConfig, PgStorageConfig } from './project.js';\nimport { getDatabasePath, getVectorDatabasePath } from './project.js';\nimport { DEFAULT_RETENTION } from './storage-maintenance.js';\n\nexport const MASTRA_CODE_LOCAL_PRAGMAS = {\n cacheSize: -128000,\n mmapSize: 536870912,\n};\n\n/**\n * Construct a LibSQL store for an arbitrary url/authToken, applying the same\n * local pragmas the default factory uses.\n */\nexport function buildLibSQLStore(opts: {\n id?: string;\n url: string;\n authToken?: string;\n retention?: RetentionConfig;\n}): MastraCompositeStore {\n return new LibSQLStore({\n id: opts.id ?? 'mastra-code-storage',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n ...(opts.retention ? { retention: opts.retention } : {}),\n localPragmas: MASTRA_CODE_LOCAL_PRAGMAS,\n });\n}\n\n/** Construct a LibSQL vector store for an arbitrary url/authToken. */\nexport function buildLibSQLVector(opts: { id?: string; url: string; authToken?: string }): MastraVector {\n return new LibSQLVector({\n id: opts.id ?? 'mastra-code-vectors',\n url: opts.url,\n ...(opts.authToken ? { authToken: opts.authToken } : {}),\n });\n}\n\nexport interface StorageResult {\n storage: MastraCompositeStore;\n /** The effective backend after any fallback logic has run. */\n backend: 'libsql' | 'pg';\n /** Non-null when PG was requested but failed — contains a user-facing warning. */\n warning?: string;\n}\n\nfunction createFallbackLibSQL(): MastraCompositeStore {\n return buildLibSQLStore({ url: `file:${getDatabasePath()}`, retention: DEFAULT_RETENTION });\n}\n\n/**\n * Create a storage instance from the resolved config.\n *\n * - `libsql` backend → LibSQLStore (always available)\n * - `pg` backend → PostgresStore, falls back to LibSQL on connection failure\n */\nexport async function createStorage(config: StorageConfig): Promise<StorageResult> {\n if (config.backend === 'pg') {\n return createPgStorage(config);\n }\n\n // Default: LibSQL\n return {\n storage: buildLibSQLStore({ url: config.url, authToken: config.authToken, retention: DEFAULT_RETENTION }),\n backend: 'libsql',\n };\n}\n\nasync function createPgStorage(config: PgStorageConfig): Promise<StorageResult> {\n // No connection info → fall back with guidance\n if (!config.connectionString && !config.host) {\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n 'PostgreSQL backend selected but no connection info configured. ' +\n 'Using LibSQL fallback. Set a connection string via /settings.',\n };\n }\n\n const base = {\n id: 'mastra-code-storage' as const,\n retention: DEFAULT_RETENTION,\n ...(config.schemaName ? { schemaName: config.schemaName } : {}),\n ...(config.disableInit ? { disableInit: config.disableInit } : {}),\n ...(config.skipDefaultIndexes ? { skipDefaultIndexes: config.skipDefaultIndexes } : {}),\n };\n\n const store = config.connectionString\n ? new PostgresStore({ ...base, connectionString: config.connectionString })\n : new PostgresStore({\n ...base,\n host: config.host!,\n port: config.port,\n database: config.database,\n user: config.user,\n password: config.password,\n });\n\n // Test the connection before committing — if it fails, fall back to LibSQL\n // so the user can fix the config via /settings.\n try {\n await store.init();\n } catch (err: any) {\n const msg = err?.message ?? String(err);\n const target = config.connectionString ?? `${config.host}:${config.port ?? 5432}`;\n try {\n await store.close();\n } catch {\n // ignore cleanup errors\n }\n return {\n storage: createFallbackLibSQL(),\n backend: 'libsql',\n warning:\n `Failed to connect to PostgreSQL at ${target}: ${msg}\\n` +\n 'Using LibSQL fallback. Fix the connection via /settings.',\n };\n }\n\n return { storage: store, backend: 'pg' };\n}\n\n/**\n * Create a vector store for recall search.\n * Uses a separate LibSQL file to avoid bloating the main storage DB with embedding data.\n * For PG backends, reuses the same connection (PG handles the extra tables fine).\n */\nexport async function createVectorStore(\n config: StorageConfig,\n effectiveBackend: 'libsql' | 'pg' = config.backend,\n): Promise<MastraVector | undefined> {\n if (effectiveBackend === 'pg') {\n // PG can handle vector tables in the same database\n const pgConfig = config as PgStorageConfig;\n if (!pgConfig.connectionString && !pgConfig.host) return undefined;\n\n const { PgVector } = await import('@mastra/pg');\n return new PgVector({\n id: 'mastra-code-vectors',\n connectionString:\n pgConfig.connectionString ??\n `postgresql://${pgConfig.user}:${pgConfig.password}@${pgConfig.host}:${pgConfig.port ?? 5432}/${pgConfig.database}`,\n });\n }\n\n // LibSQL: separate file for vectors. Per-tenant configs supply an explicit\n // vectorUrl so each tenant's recall vectors are isolated; otherwise use the\n // shared default file.\n const libsqlConfig = config as { vectorUrl?: string; vectorAuthToken?: string };\n return buildLibSQLVector({\n url: libsqlConfig.vectorUrl ?? `file:${getVectorDatabasePath()}`,\n authToken: libsqlConfig.vectorAuthToken,\n });\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Storage maintenance — retention defaults and disk reclamation for mastracode.
3
+ *
4
+ * Retention (`DEFAULT_RETENTION`) is wired into the inner store by the storage
5
+ * factory so `storage.prune()` can delete aged rows across every domain,
6
+ * including the legacy libsql observability spans table.
7
+ *
8
+ * `prune()` only deletes rows — SQLite keeps the freed pages inside the file.
9
+ * `reclaimLibSQLDisk()` returns that space to the OS by streaming a
10
+ * `VACUUM INTO` copy of each file and swapping it into place. It requires all
11
+ * connections to be closed first, so it only runs from the post-TUI
12
+ * maintenance flow. We deliberately do NOT expose VACUUM as a public API on
13
+ * LibSQLStore.
14
+ */
15
+ import type { MastraCompositeStore, PruneOptions, PruneResult, RetentionConfig } from '@mastra/core/storage';
16
+ import type { StorageConfig } from './project.js';
17
+ /**
18
+ * Default retention for mastracode's own storage. Tables not listed are kept
19
+ * forever. Ages chosen for a local dev tool where observability data dwarfs
20
+ * everything else (spans have been observed at 17+ GB):
21
+ * - spans/logs: short-lived debugging telemetry → 14d
22
+ * - scorer results & workflow snapshots: medium-lived run records → 30d
23
+ * - chat history (messages/threads): long-lived, user-visible → 90d
24
+ */
25
+ export declare const DEFAULT_RETENTION: RetentionConfig;
26
+ /** Result of reclaiming disk for one database file (sizes include the WAL). */
27
+ export interface ReclaimResult {
28
+ file: string;
29
+ bytesBefore: number;
30
+ bytesAfter: number;
31
+ }
32
+ /** Handle the TUI uses to run storage maintenance without reaching into store internals. */
33
+ export interface StorageMaintenance {
34
+ backend: 'libsql' | 'pg';
35
+ /** Retention policies in effect (for display in /prune output). */
36
+ retention: RetentionConfig;
37
+ /** Delete rows older than the retention policies. Safe to re-run; batched and cancellable. `options.retention` replaces the standing policies for this call only. */
38
+ prune: (options?: PruneOptions) => Promise<PruneResult[]>;
39
+ /** Close the long-lived storage connection (checkpoints WAL for local libsql). Nothing can use the store afterwards. */
40
+ closeStorage?: () => Promise<void>;
41
+ /** Compact local libsql files (VACUUM INTO + swap) to return freed pages to the OS. Only set for local libsql backends. */
42
+ reclaimDisk?: (onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void) => Promise<ReclaimResult[]>;
43
+ }
44
+ /** Free bytes required before compacting: the compacted copy plus 20% + 256 MB headroom. */
45
+ export declare function requiredFreeBytes(liveBytes: number): number;
46
+ /**
47
+ * Compact each local libsql db file by streaming a `VACUUM INTO` copy next to
48
+ * it, then swapping the copy into place. Reports before/after sizes.
49
+ *
50
+ * Deliberately NOT an in-place `VACUUM`: that copies all live content into a
51
+ * temp store (held in memory by libsql builds with `temp_store=MEMORY`) and
52
+ * then rewrites the entire database back through the WAL — on multi-GB
53
+ * databases that has frozen whole machines via memory/IO exhaustion.
54
+ * `VACUUM INTO` reads the logical database once and writes the compacted copy
55
+ * sequentially with bounded memory, and the WAL never grows.
56
+ *
57
+ * Each file is preflighted: live bytes are estimated from
58
+ * `page_count - freelist_count` and the copy is refused unless the volume has
59
+ * `requiredFreeBytes()` available.
60
+ *
61
+ * MUST run with every connection to these files closed (the swap replaces the
62
+ * inode — a surviving connection would keep writing to the unlinked old file).
63
+ * `runStorageMaintenance()` closes storage before calling this.
64
+ */
65
+ export declare function reclaimLibSQLDisk(dbFiles: string[], onFileStart?: (file: string, bytesBefore: number, liveBytes: number) => void): Promise<ReclaimResult[]>;
66
+ /**
67
+ * Resolve the local libsql db files eligible for checkpoint + VACUUM.
68
+ * Returns [] for remote (turso) urls and PG backends. When PG was requested
69
+ * but the factory fell back to libsql, the fallback uses the default files.
70
+ */
71
+ export declare function resolveLocalDbFiles(config: StorageConfig, effectiveBackend: 'libsql' | 'pg'): string[];
72
+ /**
73
+ * Build the maintenance handle for the effective storage backend.
74
+ * `localDbFiles` are the local libsql file paths eligible for VACUUM;
75
+ * pass an empty array for remote (turso) or PG backends.
76
+ */
77
+ export declare function createStorageMaintenance(opts: {
78
+ storage: MastraCompositeStore;
79
+ backend: 'libsql' | 'pg';
80
+ retention: RetentionConfig;
81
+ localDbFiles: string[];
82
+ /** Closes the vector store's connection; required for local libsql so the compaction swap isn't blocked by our own handle. */
83
+ closeVector?: () => Promise<void>;
84
+ }): StorageMaintenance;
85
+ export declare function formatBytes(bytes: number): string;
86
+ /**
87
+ * Run storage maintenance with exclusive access to the database, reporting
88
+ * progress through `log` (one line per pass so large deletes are visible).
89
+ *
90
+ * Must run AFTER the TUI is stopped and background writers are quiesced —
91
+ * batched deletes and VACUUM contend with live writes (SQLITE_BUSY) and can
92
+ * starve the event loop that renders the TUI. The storage connection is
93
+ * closed at the end (checkpointing the WAL for local libsql), so the process
94
+ * must exit afterwards.
95
+ */
96
+ export declare function runStorageMaintenance(opts: {
97
+ maintenance: StorageMaintenance;
98
+ vacuum: boolean;
99
+ /** Skip the memory domain (messages/threads) so chat history is preserved. */
100
+ keepMemory?: boolean;
101
+ log: (line: string) => void;
102
+ }): Promise<void>;
103
+ //# sourceMappingURL=storage-maintenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-maintenance.d.ts","sourceRoot":"","sources":["../../src/utils/storage-maintenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQ7G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAGlD;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,eAe/B,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,4FAA4F;AAC5F,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,mEAAmE;IACnE,SAAS,EAAE,eAAe,CAAC;IAC3B,qKAAqK;IACrK,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,wHAAwH;IACxH,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,2HAA2H;IAC3H,WAAW,CAAC,EAAE,CACZ,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,KACzE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CAC/B;AAeD,4FAA4F;AAC5F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3D;AA0BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EAAE,EACjB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,GAC3E,OAAO,CAAC,aAAa,EAAE,CAAC,CA0G1B;AAQD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,EAAE,CAatG;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8HAA8H;IAC9H,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC,GAAG,kBAAkB,CAiBrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWjD;AAKD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,WAAW,EAAE,kBAAkB,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;IAChB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EhB"}