diff --git a/src/resources/extensions/sf/auto.js b/src/resources/extensions/sf/auto.js index a668deb6b..2cd3bccd3 100644 --- a/src/resources/extensions/sf/auto.js +++ b/src/resources/extensions/sf/auto.js @@ -26,9 +26,9 @@ import { unlinkSync, writeFileSync, } from "node:fs"; -import { homedir } from "node:os"; import { isAbsolute, join } from "node:path"; import { pathToFileURL } from "node:url"; +import { sfHome } from "./sf-home.js"; import { clearCmuxSidebar, logCmuxEvent, @@ -1782,7 +1782,7 @@ export async function startAuto(ctx, pi, base, verboseMode, options) { // Using SF_PKG_ROOT constructs a correct absolute path in both contexts (#3949). const agentDir = process.env.SF_CODING_AGENT_DIR || - join(process.env.SF_HOME || homedir(), ".sf", "agent"); + join(sfHome(), "agent"); const pkgRoot = process.env.SF_PKG_ROOT; const resourceLoaderPath = pkgRoot ? pathToFileURL(join(pkgRoot, "dist", "resource-loader.js")).href diff --git a/src/resources/extensions/sf/commands/handlers/core.js b/src/resources/extensions/sf/commands/handlers/core.js index 32fe8d773..3c07d61e0 100644 --- a/src/resources/extensions/sf/commands/handlers/core.js +++ b/src/resources/extensions/sf/commands/handlers/core.js @@ -29,6 +29,7 @@ import { formattedShortcutPair } from "../../shortcut-defs.js"; import { deriveState } from "../../state.js"; import { writeUokDiagnostics } from "../../uok/diagnostic-synthesis.js"; import { projectRoot } from "../context.js"; +import { sfHome } from "../../sf-home.js"; export function showHelp(ctx, args = "") { const summaryLines = [ "SF — Singularity Forge\n", @@ -1468,8 +1469,7 @@ async function handleResumeCommand(args, ctx) { // Fallback: show recent SF headless session files const { readdirSync, existsSync } = await import("node:fs"); const { join: pathJoin } = await import("node:path"); - const { homedir } = await import("node:os"); - const sfDir = pathJoin(homedir(), ".sf"); + const sfDir = sfHome(); const sessDir = pathJoin(sfDir, "sessions"); if (!existsSync(sessDir)) { ctx.ui.notify( diff --git a/src/resources/extensions/sf/key-manager.js b/src/resources/extensions/sf/key-manager.js index c5007fe8e..88570684f 100644 --- a/src/resources/extensions/sf/key-manager.js +++ b/src/resources/extensions/sf/key-manager.js @@ -9,6 +9,7 @@ import { dirname, join } from "node:path"; import { AuthStorage } from "@singularity-forge/coding-agent"; import { getErrorMessage } from "./error-utils.js"; import { isEnvAuthAllowed } from "./provider-env-auth.js"; +import { sfHome } from "./sf-home.js"; export const PROVIDER_REGISTRY = [ // LLM Providers { @@ -250,8 +251,7 @@ export function describeCredential(cred) { * Get the auth.json path. */ export function getAuthPath() { - const sfHome = process.env.SF_HOME || join(process.env.HOME ?? "~", ".sf"); - return join(sfHome, "agent", "auth.json"); + return join(sfHome(), "agent", "auth.json"); } /** * Create an AuthStorage instance for key management. diff --git a/src/resources/extensions/sf/memory-embeddings.js b/src/resources/extensions/sf/memory-embeddings.js index dcbb39342..413f4e564 100644 --- a/src/resources/extensions/sf/memory-embeddings.js +++ b/src/resources/extensions/sf/memory-embeddings.js @@ -13,7 +13,6 @@ // pipeline stages soft-degrade and `getRelevantMemoriesRanked` falls back // to static (confidence × hit_count) ranking. import { existsSync, readFileSync } from "node:fs"; -import { homedir } from "node:os"; import { join } from "node:path"; import { _getAdapter, @@ -22,11 +21,12 @@ import { upsertMemoryEmbedding, } from "./sf-db.js"; import { logWarning } from "./workflow-logger.js"; +import { sfHome } from "./sf-home.js"; /** Read the llm-gateway entry from ~/.sf/agent/auth.json if present. */ function readGatewayFromAuthJson() { try { - const authPath = join(homedir(), ".sf", "agent", "auth.json"); + const authPath = join(sfHome(), "agent", "auth.json"); if (!existsSync(authPath)) return null; const data = JSON.parse(readFileSync(authPath, "utf8")); const entry = data["llm-gateway"]; diff --git a/src/resources/extensions/sf/preferences-models.js b/src/resources/extensions/sf/preferences-models.js index d3b08e1db..eaff35219 100644 --- a/src/resources/extensions/sf/preferences-models.js +++ b/src/resources/extensions/sf/preferences-models.js @@ -10,6 +10,7 @@ import { homedir } from "node:os"; import { join } from "node:path"; import { getModels, getProviders } from "@singularity-forge/ai"; import { selectByBenchmarks } from "./benchmark-selector.js"; +import { sfHome } from "./sf-home.js"; import { defaultRoutingConfig, MODEL_CAPABILITY_TIER } from "./model-router.js"; import { @@ -535,7 +536,7 @@ export function resolveDefaultSessionModel(sessionProvider) { export function isCustomProvider(provider) { if (!provider) return false; const candidates = [ - join(homedir(), ".sf", "agent", "models.json"), + join(sfHome(), "agent", "models.json"), join(homedir(), ".pi", "agent", "models.json"), ]; for (const path of candidates) { diff --git a/src/resources/extensions/sf/preferences-skills.js b/src/resources/extensions/sf/preferences-skills.js index 5dcd67ae0..ae5c969ec 100644 --- a/src/resources/extensions/sf/preferences-skills.js +++ b/src/resources/extensions/sf/preferences-skills.js @@ -8,6 +8,7 @@ import { existsSync, readdirSync, statSync } from "node:fs"; import { homedir } from "node:os"; import { isAbsolute, join } from "node:path"; import { validatePreferences } from "./preferences-validation.js"; +import { sfHome } from "./sf-home.js"; /** * Get skill search directories in priority order for resolution. * @@ -25,7 +26,7 @@ export function getSkillSearchDirs(cwd) { { dir: join(cwd, ".claude", "skills"), method: "project-skill" }, ]; // Legacy fallback — read skills from old SF directory only if migration hasn't completed - const legacyDir = join(homedir(), ".sf", "agent", "skills"); + const legacyDir = join(sfHome(), "agent", "skills"); if ( existsSync(legacyDir) && !existsSync(join(legacyDir, ".migrated-to-agents")) diff --git a/src/resources/extensions/sf/skill-telemetry.js b/src/resources/extensions/sf/skill-telemetry.js index ce545fa72..8b077fcd4 100644 --- a/src/resources/extensions/sf/skill-telemetry.js +++ b/src/resources/extensions/sf/skill-telemetry.js @@ -13,6 +13,7 @@ import { existsSync, readdirSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; +import { sfHome } from "./sf-home.js"; // ─── In-memory state ────────────────────────────────────────────────────────── /** Skills available in the system prompt for the current unit */ @@ -28,7 +29,7 @@ const activelyLoadedSkills = new Set(); export function captureAvailableSkills() { const skillsDir = join(homedir(), ".agents", "skills"); const claudeSkillsDir = join(homedir(), ".claude", "skills"); - const legacyDir = join(homedir(), ".sf", "agent", "skills"); + const legacyDir = join(sfHome(), "agent", "skills"); const names = listSkillNames(skillsDir); const claudeNames = listSkillNames(claudeSkillsDir); // Include skills still in the legacy directory only if migration hasn't completed @@ -102,7 +103,7 @@ export function detectStaleSkills(units, thresholdDays) { // Check all installed skills, not just those with usage data const skillsDir = join(homedir(), ".agents", "skills"); const claudeSkillsDir = join(homedir(), ".claude", "skills"); - const legacyDir = join(homedir(), ".sf", "agent", "skills"); + const legacyDir = join(sfHome(), "agent", "skills"); const legacyMigrated = existsSync(join(legacyDir, ".migrated-to-agents")); const legacyNames = legacyMigrated ? [] : listSkillNames(legacyDir); const installedSet = new Set([ diff --git a/src/resources/extensions/sf/ui/color-band.js b/src/resources/extensions/sf/ui/color-band.js index ec23bd9e5..156b64a35 100644 --- a/src/resources/extensions/sf/ui/color-band.js +++ b/src/resources/extensions/sf/ui/color-band.js @@ -4,15 +4,15 @@ * Displays a colored band in the footer to visually distinguish sessions. */ import * as fs from "node:fs"; -import * as os from "node:os"; import * as path from "node:path"; +import { sfHome } from "../sf-home.js"; const DEFAULT_CONFIG = { enabledByDefault: true, blockChar: "▁", blockCount: "full", }; -const STATE_FILE = path.join(os.homedir(), ".sf", "session-color-state.json"); +const STATE_FILE = path.join(sfHome(), "session-color-state.json"); const COLOR_PALETTE = [ 196, 51, 226, 129, 46, 208, 27, 213, 118, 160, 87, 220, 93, 34, 202, 75, 199, 154, 124, 45, 214, 135, 40, 166, 69, 205, 190, 88, 80, 228, 97, 28, 172, 63, diff --git a/src/resources/extensions/sf/ui/marketplace.js b/src/resources/extensions/sf/ui/marketplace.js index 01a732b85..b0234ed5c 100644 --- a/src/resources/extensions/sf/ui/marketplace.js +++ b/src/resources/extensions/sf/ui/marketplace.js @@ -1,6 +1,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; +import { sfHome } from "../sf-home.js"; import { Key, matchesKey, @@ -92,7 +93,7 @@ function scanInstalledExtensions(dir, sourceLabel) { } function buildCatalog() { const installed = scanInstalledExtensions( - join(homedir(), ".sf", "agent", "extensions"), + join(sfHome(), "agent", "extensions"), "installed", ); const piCompat = scanInstalledExtensions( @@ -317,7 +318,7 @@ export function installExtensionNpm(packageId, displayName) { return; } import("node:child_process").then(({ spawn }) => { - const target = join(homedir(), ".sf", "agent", "extensions"); + const target = join(sfHome(), "agent", "extensions"); const proc = spawn("npm", ["install", "--prefix", target, packageId], { stdio: ["ignore", "pipe", "pipe"], detached: false, diff --git a/src/resources/extensions/sf/ui/prompt-history.js b/src/resources/extensions/sf/ui/prompt-history.js index 9050eaa04..505775ad3 100644 --- a/src/resources/extensions/sf/ui/prompt-history.js +++ b/src/resources/extensions/sf/ui/prompt-history.js @@ -1,5 +1,4 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs"; -import { homedir } from "node:os"; import { dirname, join } from "node:path"; import { Key, @@ -7,11 +6,12 @@ import { truncateToWidth, visibleWidth, } from "@singularity-forge/tui"; +import { sfHome } from "../sf-home.js"; const LIMIT = 20; const SCAN_LINE_LIMIT = 2000; function promptHistoryPath() { - return join(homedir(), ".sf", "agent", "prompt-history.jsonl"); + return join(sfHome(), "agent", "prompt-history.jsonl"); } function isEnvTruthy(value) { return ["1", "true", "TRUE", "yes", "YES"].includes(String(value ?? "")); diff --git a/src/resources/extensions/sf/ui/usage-bar.js b/src/resources/extensions/sf/ui/usage-bar.js index 002f5fd1b..7862c15b7 100644 --- a/src/resources/extensions/sf/ui/usage-bar.js +++ b/src/resources/extensions/sf/ui/usage-bar.js @@ -19,12 +19,13 @@ import { setupUser, } from "@google/gemini-cli-core"; import { visibleWidth } from "@singularity-forge/tui"; +import { sfHome } from "../sf-home.js"; // ============================================================================ // Auth helper // ============================================================================ function loadAuthJson() { - const sfAuthPath = path.join(os.homedir(), ".sf", "agent", "auth.json"); + const sfAuthPath = path.join(sfHome(), "agent", "auth.json"); try { if (fs.existsSync(sfAuthPath)) { return JSON.parse(fs.readFileSync(sfAuthPath, "utf-8"));