import { join, resolve } from 'node:path'; import { mkdirSync } from 'node:fs'; import { homedir } from 'node:os'; export const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data'); // Unified, file-based store for all agent items, living outside the repo. Every skill/tool/task/ // process/extension is a directory under one of these type subfolders — no scope tiers, no DB. export const OFFICER_ITEMS_DIR = process.env.OFFICER_ITEMS_DIR ?? join(process.cwd(), 'officer-items'); export type ItemType = 'skills' | 'tools' | 'tasks' | 'processes' | 'extensions'; export const ITEM_TYPES: ItemType[] = ['skills', 'tools', 'tasks', 'processes', 'extensions']; export const itemsDir = (type: ItemType) => join(OFFICER_ITEMS_DIR, type); export const ensureItemDirs = () => { for (const type of ITEM_TYPES) mkdirSync(itemsDir(type), { recursive: true }); }; export const SERVER_CONFIG_DIR = join(DATA_PATH, 'server-settings'); // The `pi`/opencode agent's own config dir (auth.json + models.json). External-tool path. export const AGENT_CONFIG_DIR = join(homedir(), '.pi', 'agent'); export const SEED_PATH = resolve(import.meta.dir, '../../seed'); // The managed home under DATA_PATH — what provisioning seeds and what the generated Claude config // points at. Distinct from the owner's real login home below. export const getHomeDir = (email: string) => join(DATA_PATH, email, 'home'); // Where the owner's sessions actually run: their real login home when HOME_DIR is set, so platform // terminals/chats/tasks share config and credentials with the shell they use outside Officer. export const getOwnerHomeDir = (email: string): string => process.env.HOME_DIR ?? getHomeDir(email); export const getUserPiConfigDir = (email: string) => join(DATA_PATH, email, 'home', '.pi', 'agent'); export const getUserProjectsDir = (email: string) => join(DATA_PATH, email, 'home', 'Projects'); export const getTmpAttachmentsDir = (email: string) => join(DATA_PATH, email, 'attachments', 'tmp'); export const getAttachmentsDir = (email: string, sessionId: string) => join(DATA_PATH, email, 'attachments', sessionId); // Per-account email storage: DATA_PATH//email_accounts//emails.db, with a // single attachment_cache/ shared across the owner's accounts. export const getEmailAccountsDir = (ownerEmail: string) => join(DATA_PATH, ownerEmail, 'email_accounts'); export const getEmailDbPath = (ownerEmail: string, accountEmail: string) => join(getEmailAccountsDir(ownerEmail), accountEmail, 'emails.db'); export const getEmailAttachmentCacheDir = (ownerEmail: string) => join(getEmailAccountsDir(ownerEmail), 'attachment_cache'); /** Derive a valid Linux username from a display username or email. */ export const toShellUsername = (username: string, email: string): string => { const raw = username || email.split('@')[0]!; // Replace invalid chars, lowercase, truncate to 32 chars return ( raw .replace(/@.*$/, '') .replace(/[^a-zA-Z0-9._-]/g, '_') .toLowerCase() .slice(0, 32) || 'officer' ); }; export const getUserAppsDir = (email: string) => join(DATA_PATH, email, 'apps');