Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions packages/cli/src/cli.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { render } from "ink";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { setShellIfWindows, getProjectCode } from "@vegamo/deepcode-core";
import { getProjectCode, getUserDataDir, setShellIfWindows } from "@vegamo/deepcode-core";
import { checkForNpmUpdate, promptForPendingUpdate } from "./common/update-check";
import { AppContainer } from "./ui";
import { parseArguments } from "./cli-args";
Expand Down Expand Up @@ -36,7 +35,7 @@ async function main(): Promise<void> {
// Resolve --last to the most recent session ID for the current project
if (parsed.last) {
const projectCode = getProjectCode(projectRoot);
const indexPath = join(homedir(), ".deepcode", "projects", projectCode, "sessions-index.json");
const indexPath = join(getUserDataDir(), "projects", projectCode, "sessions-index.json");
try {
const index = JSON.parse(readFileSync(indexPath, "utf-8"));
const entries: { id: string; updateTime: string }[] = Array.isArray(index?.entries) ? index.entries : [];
Expand All @@ -54,7 +53,7 @@ async function main(): Promise<void> {

if (forkSessionId === true) {
const projectCode = getProjectCode(projectRoot);
const indexPath = join(homedir(), ".deepcode", "projects", projectCode, "sessions-index.json");
const indexPath = join(getUserDataDir(), "projects", projectCode, "sessions-index.json");
try {
const index = JSON.parse(readFileSync(indexPath, "utf-8"));
const entries: { id: string; updateTime: string }[] = Array.isArray(index?.entries) ? index.entries : [];
Expand Down Expand Up @@ -88,7 +87,7 @@ async function main(): Promise<void> {
// Validate --resume <sessionId> before entering TUI
if (typeof resumeSessionId === "string") {
const projectCode = getProjectCode(projectRoot);
const indexPath = join(homedir(), ".deepcode", "projects", projectCode, "sessions-index.json");
const indexPath = join(getUserDataDir(), "projects", projectCode, "sessions-index.json");
try {
const index = JSON.parse(readFileSync(indexPath, "utf-8"));
const found =
Expand All @@ -105,7 +104,7 @@ async function main(): Promise<void> {

if (typeof forkSessionId === "string") {
const projectCode = getProjectCode(projectRoot);
const indexPath = join(homedir(), ".deepcode", "projects", projectCode, "sessions-index.json");
const indexPath = join(getUserDataDir(), "projects", projectCode, "sessions-index.json");
try {
const index = JSON.parse(readFileSync(indexPath, "utf-8"));
const found = Array.isArray(index?.entries) && index.entries.some((e: { id: string }) => e.id === forkSessionId);
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/common/update-check.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { spawn, type ChildProcess, type SpawnOptions } from "child_process";
import React from "react";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { render, type Instance } from "ink";
import { UpdatePrompt, type UpdatePromptChoice } from "../ui";
import { killProcessTree } from "@vegamo/deepcode-core";
import { getUserDataDir, killProcessTree } from "@vegamo/deepcode-core";
import type { PackageJson } from "../utils/package";

type UpdateState = {
Expand Down Expand Up @@ -118,7 +117,7 @@ export function compareVersions(a: string, b: string): number {
}

export function getUpdateStatePath(): string {
return path.join(os.homedir(), ".deepcode", UPDATE_STATE_FILE);
return path.join(getUserDataDir(), UPDATE_STATE_FILE);
}

async function promptUpdateChoice({
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/common/debug-logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { getUserDataDir } from "../settings";

const DEBUG_LOG_FILE = "debug.log";

Expand Down Expand Up @@ -34,7 +34,7 @@ export function logOpenAIChatCompletionDebug(entry: OpenAIChatCompletionDebugEnt
}

export function getDebugLogPath(): string {
return path.join(os.homedir(), ".deepcode", "logs", DEBUG_LOG_FILE);
return path.join(getUserDataDir(), "logs", DEBUG_LOG_FILE);
}

export function normalizeDebugError(error: unknown): { name: string; message: string; stack?: string } {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/common/error-logger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import type { LlmErrorDetails } from "./llm-error";
import { getUserDataDir } from "../settings";

const LOG_DIR = path.join(os.homedir(), ".deepcode", "logs");
const LOG_DIR = path.join(getUserDataDir(), "logs");
const ERROR_LOG_PATH = path.join(LOG_DIR, "error.log");

function ensureLogDir(): void {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/common/openai-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as os from "os";
import * as path from "path";
import OpenAI from "openai";
import { Agent, fetch as undiciFetch } from "undici";
import { resolveCurrentSettings } from "../settings";
import { getUserDataDir, resolveCurrentSettings } from "../settings";

// Custom undici Agent with a 180-second keepAlive timeout. The default
// global fetch (undici) only keeps connections alive for 4 seconds, which
Expand Down Expand Up @@ -108,7 +108,7 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {

function getMachineId(): string | undefined {
try {
const idPath = path.join(os.homedir(), ".deepcode", "machine-id");
const idPath = path.join(getUserDataDir(), "machine-id");
if (fs.existsSync(idPath)) {
const raw = fs.readFileSync(idPath, "utf8").trim();
if (raw) {
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export {
applyModelConfigSelection,
modelConfigKey,
getUserSettingsPath,
getUserConfigDir,
getUserDataDir,
getProjectSettingsPath,
getDefaultContextWindow,
getDefaultAutoCompactWindow,
Expand Down
7 changes: 4 additions & 3 deletions packages/core/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import { McpManager } from "./mcp/mcp-manager";
import {
getDefaultAutoCompactWindow,
getUserDataDir,
type McpServerConfig,
type PermissionScope,
type PermissionSettings,
Expand Down Expand Up @@ -845,7 +846,7 @@ ${agentInstructions}
return [
{ root: path.join(this.projectRoot, ".deepcode", "skills"), displayRoot: "./.deepcode/skills" },
{ root: path.join(this.projectRoot, ".agents", "skills"), displayRoot: "./.agents/skills" },
{ root: path.join(homeDir, ".deepcode", "skills"), displayRoot: "~/.deepcode/skills" },
{ root: path.join(getUserDataDir(), "skills"), displayRoot: "~/.deepcode/skills" },
{ root: path.join(homeDir, ".agents", "skills"), displayRoot: "~/.agents/skills" },
{ root: this.getBundledSkillsRoot(), displayRoot: "bundled:" },
];
Expand Down Expand Up @@ -2008,7 +2009,7 @@ ${agentInstructions}
sessionsIndexPath: string;
} {
const projectCode = getProjectCode(this.projectRoot);
const projectDir = path.join(os.homedir(), ".deepcode", "projects", projectCode);
const projectDir = path.join(getUserDataDir(), "projects", projectCode);
const sessionsIndexPath = path.join(projectDir, "sessions-index.json");
return { projectCode, projectDir, sessionsIndexPath };
}
Expand Down Expand Up @@ -2400,7 +2401,7 @@ ${agentInstructions}
return projectInstructions.content;
}

return this.readNonEmptyFile(path.join(os.homedir(), ".deepcode", "AGENTS.md"));
return this.readNonEmptyFile(path.join(getUserDataDir(), "AGENTS.md"));
}

private buildSystemMessage(
Expand Down
18 changes: 17 additions & 1 deletion packages/core/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,8 +655,24 @@ export const DEFAULT_BASE_URL = "https://api.deepseek.com";
// Settings file I/O
// ---------------------------------------------------------------------------

/** User-level config directory. Honors $XDG_CONFIG_HOME/deepcode on Linux; defaults to ~/.deepcode. (#226) */
export function getUserConfigDir(): string {
if (process.platform === "linux" && process.env.XDG_CONFIG_HOME) {
return path.join(process.env.XDG_CONFIG_HOME, "deepcode");
}
return path.join(os.homedir(), ".deepcode");
}

/** User-level data directory (projects, logs, machine-id). Honors $XDG_DATA_HOME/deepcode on Linux; defaults to ~/.deepcode. (#226) */
export function getUserDataDir(): string {
if (process.platform === "linux" && process.env.XDG_DATA_HOME) {
return path.join(process.env.XDG_DATA_HOME, "deepcode");
}
return path.join(os.homedir(), ".deepcode");
}

export function getUserSettingsPath(): string {
return path.join(os.homedir(), ".deepcode", "settings.json");
return path.join(getUserConfigDir(), "settings.json");
}

export function getProjectSettingsPath(projectRoot: string): string {
Expand Down
37 changes: 37 additions & 0 deletions packages/core/src/tests/settings-and-notify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,46 @@ import {
type NotifySpawn,
} from "../common/notify";
import { applyModelConfigSelection, resolveSettings, resolveSettingsSources } from "../settings";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { getUserConfigDir, getUserDataDir, getUserSettingsPath } from "../settings";

const TEST_PROCESS_ENV = {};

test("getUserConfigDir and getUserDataDir honor XDG env vars on Linux (#226)", () => {
const originalPlatform = Object.getOwnPropertyDescriptor(process, "platform");
const originalConfig = process.env.XDG_CONFIG_HOME;
const originalData = process.env.XDG_DATA_HOME;
try {
Object.defineProperty(process, "platform", { value: "linux" });
process.env.XDG_CONFIG_HOME = "/tmp/xdg-config";
process.env.XDG_DATA_HOME = "/tmp/xdg-data";
assert.equal(getUserConfigDir(), path.join("/tmp/xdg-config", "deepcode"));
assert.equal(getUserDataDir(), path.join("/tmp/xdg-data", "deepcode"));
assert.equal(getUserSettingsPath(), path.join("/tmp/xdg-config", "deepcode", "settings.json"));

delete process.env.XDG_CONFIG_HOME;
delete process.env.XDG_DATA_HOME;
assert.equal(getUserConfigDir(), path.join(os.homedir(), ".deepcode"));
assert.equal(getUserDataDir(), path.join(os.homedir(), ".deepcode"));
} finally {
if (originalConfig === undefined) {
delete process.env.XDG_CONFIG_HOME;
} else {
process.env.XDG_CONFIG_HOME = originalConfig;
}
if (originalData === undefined) {
delete process.env.XDG_DATA_HOME;
} else {
process.env.XDG_DATA_HOME = originalData;
}
if (originalPlatform) {
Object.defineProperty(process, "platform", originalPlatform);
}
}
});

test("resolveSettings reads top-level thinkingEnabled, notify, and webSearchTool", () => {
const resolved = resolveSettings(
{
Expand Down
4 changes: 2 additions & 2 deletions packages/vscode-ide-companion/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import OpenAI from "openai";
import MarkdownIt from "markdown-it";
import type { SessionMessage } from "@vegamo/deepcode-core";
Expand All @@ -17,6 +16,7 @@ import {
type DeepcodingSettings,
type ReasoningEffort,
type ResolvedDeepcodingSettings,
getUserSettingsPath,
setShellIfWindows,
} from "@vegamo/deepcode-core";
import { getNonce } from "./utils.js";
Expand Down Expand Up @@ -438,7 +438,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider {

private readUserSettings(): DeepcodingSettings | null {
try {
const settingsPath = path.join(os.homedir(), ".deepcode", "settings.json");
const settingsPath = getUserSettingsPath();
if (!fs.existsSync(settingsPath)) {
return null;
}
Expand Down