diff --git a/src/services/client.ts b/src/services/client.ts index 36aaa71..09f0f96 100644 --- a/src/services/client.ts +++ b/src/services/client.ts @@ -4,6 +4,12 @@ import { log } from "./logger.js"; import type { MemoryType } from "../types/index.js"; const TIMEOUT_MS = 30000; +const SPACE_NAME_TIMEOUT_MS = 5000; +const API_URL = + process.env.SUPERMEMORY_API_URL || + process.env.SUPERMEMORY_BASE_URL || + "https://api.supermemory.ai"; +const CODEX_SOURCE = "codex"; function withTimeout(promise: Promise, ms: number): Promise { let id: ReturnType; @@ -191,7 +197,11 @@ export class SupermemoryClient { } = { content, containerTag, - metadata: metadata as Record, + metadata: { + sm_source: CODEX_SOURCE, + sm_client: CODEX_SOURCE, + ...metadata, + } as Record, }; if (options?.customId) { payload.customId = options.customId; @@ -209,6 +219,70 @@ export class SupermemoryClient { } } + async updateContainerTagName(containerTag: string, name: string) { + log("updateContainerTagName: start", { containerTag, name }); + try { + const currentResponse = await withTimeout( + fetch(`${API_URL}/v3/container-tags/${encodeURIComponent(containerTag)}`, { + headers: { + Authorization: `Bearer ${getApiKeyValue()}`, + }, + }), + SPACE_NAME_TIMEOUT_MS + ); + + if (!currentResponse.ok) { + log("updateContainerTagName: skipped", { + containerTag, + status: currentResponse.status, + }); + return { success: false as const, error: `HTTP ${currentResponse.status}` }; + } + + const current = (await currentResponse.json()) as { name?: string | null }; + const currentName = current.name?.trim(); + if ( + currentName && + currentName !== `Space ${containerTag}` && + !currentName.startsWith("Codex · ") + ) { + log("updateContainerTagName: kept custom name", { containerTag, currentName }); + return { success: true as const }; + } + + if (currentName === name) { + return { success: true as const }; + } + + const response = await withTimeout( + fetch(`${API_URL}/v3/container-tags/${encodeURIComponent(containerTag)}`, { + method: "PATCH", + headers: { + Authorization: `Bearer ${getApiKeyValue()}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ name }), + }), + SPACE_NAME_TIMEOUT_MS + ); + + if (!response.ok) { + log("updateContainerTagName: skipped", { + containerTag, + status: response.status, + }); + return { success: false as const, error: `HTTP ${response.status}` }; + } + + log("updateContainerTagName: success", { containerTag, name }); + return { success: true as const }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + log("updateContainerTagName: error", { containerTag, error: errorMessage }); + return { success: false as const, error: errorMessage }; + } + } + async forgetMemory(content: string, containerTag: string): Promise<{ success: true; message: string; id?: string } | { success: false; error: string }> { log("forgetMemory: start", { containerTag, contentLength: content.length }); try { diff --git a/src/services/tags.ts b/src/services/tags.ts index 8bf8d0a..2d3acdb 100644 --- a/src/services/tags.ts +++ b/src/services/tags.ts @@ -68,6 +68,20 @@ function getGitRoot(directory: string): string | null { } } +function getGitRepoName(directory: string): string | null { + try { + const remoteUrl = execSync("git remote get-url origin", { + cwd: directory, + encoding: "utf-8", + stdio: ["pipe", "pipe", "pipe"], + }).trim(); + const match = remoteUrl.match(/[/:]([^/]+?)(?:\.git)?$/); + return match ? match[1] : null; + } catch { + return null; + } +} + export function getUserTag(): string { if (CONFIG.userContainerTag) return CONFIG.userContainerTag; const email = getGitEmail(); @@ -82,6 +96,12 @@ export function getProjectTag(directory: string): string { return `${CONFIG.containerTagPrefix}_project_${sha256(basePath)}`; } +export function getProjectName(directory: string): string { + const gitRoot = getGitRoot(directory); + const basePath = gitRoot || directory; + return getGitRepoName(basePath) || basename(basePath) || "unknown"; +} + export function getTags(directory: string): { user: string; project: string } { return { user: getUserTag(), diff --git a/src/skills/save-memory.ts b/src/skills/save-memory.ts index 37bd2f1..62278c7 100644 --- a/src/skills/save-memory.ts +++ b/src/skills/save-memory.ts @@ -1,6 +1,6 @@ import { isConfigured } from "../config.js"; import { SupermemoryClient } from "../services/client.js"; -import { getProjectTag } from "../services/tags.js"; +import { getProjectName, getProjectTag } from "../services/tags.js"; async function main(): Promise { if (!isConfigured()) { @@ -20,17 +20,20 @@ async function main(): Promise { const client = new SupermemoryClient(); const projectTag = getProjectTag(process.cwd()); + const projectName = getProjectName(process.cwd()); try { const metadata = { type: "project-knowledge" as const, source: "skill", + project: projectName, timestamp: new Date().toISOString(), }; const result = await client.addMemory(content, projectTag, metadata); if (result.success) { + await client.updateContainerTagName(projectTag, `Codex · ${projectName}`); console.log(`Memory saved (id: ${result.id}) to project '${projectTag}'`); } else { console.log(`Failed to save memory: ${result.error}`);