chore: inject global claude directory by using GLOBAL_CLAUDE_DIR environment variables

This commit is contained in:
d-kimsuon
2025-09-18 21:36:43 +09:00
parent f3f3717e8a
commit 89e3034202
6 changed files with 48 additions and 15 deletions

View File

@@ -1,12 +1,15 @@
import type { SSEEventMap } from "../../types/sse";
export const callSSE = () => {
export const callSSE = (options?: { onOpen?: (event: Event) => void }) => {
const { onOpen } = options ?? {};
const eventSource = new EventSource(
new URL("/api/sse", window.location.origin).href,
);
const handleOnOpen = (event: Event) => {
console.log("SSE connection opened", event);
onOpen?.(event);
};
eventSource.onopen = handleOnOpen;

View File

@@ -1,3 +1,4 @@
import { useQueryClient } from "@tanstack/react-query";
import { useAtom } from "jotai";
import {
type FC,
@@ -7,6 +8,7 @@ import {
useRef,
} from "react";
import type { SSEEvent } from "../../../types/sse";
import { projectListQuery } from "../../api/queries";
import { callSSE } from "../callSSE";
import {
type EventListener,
@@ -21,9 +23,18 @@ export const ServerEventsProvider: FC<PropsWithChildren> = ({ children }) => {
Map<SSEEvent["kind"], Set<(event: SSEEvent) => void>>
>(new Map());
const [, setSSEState] = useAtom(sseAtom);
const queryClient = useQueryClient();
useEffect(() => {
const sse = callSSE();
const sse = callSSE({
onOpen: async () => {
// reconnect 中のイベントは購読できないので
// open 時にまとめて invalidate する
await queryClient.invalidateQueries({
queryKey: projectListQuery.queryKey,
});
},
});
sseRef.current = sse;
const { removeEventListener } = sse.addEventListener("connect", (event) => {
@@ -39,7 +50,7 @@ export const ServerEventsProvider: FC<PropsWithChildren> = ({ children }) => {
sse.cleanUp();
removeEventListener();
};
}, [setSSEState]);
}, [setSSEState, queryClient]);
const addEventListener = useCallback(
<T extends SSEEvent["kind"]>(eventType: T, listener: EventListener<T>) => {

View File

@@ -1,5 +1,4 @@
import { readdir } from "node:fs/promises";
import { homedir } from "node:os";
import { resolve } from "node:path";
import { zValidator } from "@hono/zod-validator";
import { setCookie } from "hono/cookie";
@@ -18,6 +17,7 @@ import { getBranches } from "../service/git/getBranches";
import { getCommits } from "../service/git/getCommits";
import { getDiff } from "../service/git/getDiff";
import { getMcpList } from "../service/mcp/getMcpList";
import { claudeCommandsDirPath } from "../service/paths";
import { getProject } from "../service/project/getProject";
import { getProjects } from "../service/project/getProjects";
import { getSession } from "../service/session/getSession";
@@ -194,7 +194,7 @@ export const routes = (app: HonoAppType) => {
const { project } = await getProject(projectId);
const [globalCommands, projectCommands] = await Promise.allSettled([
readdir(resolve(homedir(), ".claude", "commands"), {
readdir(claudeCommandsDirPath, {
withFileTypes: true,
}).then((dirents) =>
dirents

View File

@@ -1,6 +1,6 @@
import { type FSWatcher, watch } from "node:fs";
import z from "zod";
import { claudeProjectPath } from "../paths";
import { claudeProjectsDirPath } from "../paths";
import { getEventBus, type IEventBus } from "./EventBus";
const fileRegExp = /(?<projectId>.*?)\/(?<sessionId>.*?)\.jsonl/;
@@ -24,10 +24,10 @@ export class FileWatcherService {
this.isWatching = true;
try {
console.log("Starting file watcher on:", claudeProjectPath);
console.log("Starting file watcher on:", claudeProjectsDirPath);
// メインプロジェクトディレクトリを監視
this.watcher = watch(
claudeProjectPath,
claudeProjectsDirPath,
{ persistent: false, recursive: true },
(eventType, filename) => {
if (!filename) return;

View File

@@ -1,4 +1,20 @@
import { homedir } from "node:os";
import { resolve } from "node:path";
export const claudeProjectPath = resolve(homedir(), ".claude", "projects");
// biome-ignore lint/complexity/useLiteralKeys: typescript restriction
const GLOBAL_CLAUDE_DIR = process.env["GLOBAL_CLAUDE_DIR"];
export const globalClaudeDirectoryPath =
GLOBAL_CLAUDE_DIR === undefined
? resolve(homedir(), ".claude")
: resolve(GLOBAL_CLAUDE_DIR);
export const claudeProjectsDirPath = resolve(
globalClaudeDirectoryPath,
"projects",
);
export const claudeCommandsDirPath = resolve(
globalClaudeDirectoryPath,
"commands",
);

View File

@@ -1,8 +1,7 @@
import { constants } from "node:fs";
import { access, readdir } from "node:fs/promises";
import { resolve } from "node:path";
import { claudeProjectPath } from "../paths";
import { claudeProjectsDirPath } from "../paths";
import type { Project } from "../types";
import { getProjectMeta } from "./getProjectMeta";
import { encodeProjectId } from "./id";
@@ -10,20 +9,24 @@ import { encodeProjectId } from "./id";
export const getProjects = async (): Promise<{ projects: Project[] }> => {
try {
// Check if the claude projects directory exists
await access(claudeProjectPath, constants.F_OK);
await access(claudeProjectsDirPath, constants.F_OK);
} catch (_error) {
// Directory doesn't exist, return empty array
console.warn(`Claude projects directory not found at ${claudeProjectPath}`);
console.warn(
`Claude projects directory not found at ${claudeProjectsDirPath}`,
);
return { projects: [] };
}
try {
const dirents = await readdir(claudeProjectPath, { withFileTypes: true });
const dirents = await readdir(claudeProjectsDirPath, {
withFileTypes: true,
});
const projects = await Promise.all(
dirents
.filter((d) => d.isDirectory())
.map(async (d) => {
const fullPath = resolve(claudeProjectPath, d.name);
const fullPath = resolve(claudeProjectsDirPath, d.name);
const id = encodeProjectId(fullPath);
return {