Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | /** * Identity Module for MCP Config * * §10.2: Manages persistent identifiers for managed MCP config entries: * - userId: Stable user identifier (persists across reinstalls on same machine) * - installId: Per-installation identifier (regenerated on reinstall) * * @packageDocumentation */ import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; import type { AIClientFormat, SnapbackManagedMetadata } from "./types.js"; // ============================================================================= // PATHS // ============================================================================= /** * Get SnapBack config directory path */ export function getSnapbackConfigDir(): string { const isWindows = process.platform === "win32"; if (isWindows) { return join(process.env.APPDATA || homedir(), "SnapBack"); } return join(homedir(), ".snapback"); } /** * Get identity file path */ function getIdentityFilePath(): string { return join(getSnapbackConfigDir(), "identity.json"); } // ============================================================================= // IDENTITY TYPES // ============================================================================= interface StoredIdentity { /** User ID - stable across sessions */ userId: string; /** Install ID - per machine install */ installId: string; /** Created timestamp */ createdAt: string; } // ============================================================================= // IDENTITY MANAGEMENT // ============================================================================= /** Cached identity to avoid repeated disk reads */ let cachedIdentity: StoredIdentity | null = null; /** * Get or create stored identity * * §5.2: Generates and persists userId and installId */ export function getOrCreateIdentity(): StoredIdentity { if (cachedIdentity) { return cachedIdentity; } const identityPath = getIdentityFilePath(); // Try to read existing identity if (existsSync(identityPath)) { try { const content = readFileSync(identityPath, "utf-8"); const identity = JSON.parse(content) as StoredIdentity; if (identity.userId && identity.installId) { cachedIdentity = identity; return identity; } } catch { // Invalid file, will regenerate } } // Create new identity const identity: StoredIdentity = { userId: randomUUID(), installId: randomUUID(), createdAt: new Date().toISOString(), }; // Ensure directory exists const configDir = getSnapbackConfigDir(); mkdirSync(configDir, { recursive: true }); // Write identity file writeFileSync(identityPath, JSON.stringify(identity, null, 2)); cachedIdentity = identity; return identity; } /** * Get current CLI version from package.json */ function getCLIVersion(): string { try { // Try to read from node_modules or nearby package.json const packagePaths = [ join(__dirname, "../package.json"), join(__dirname, "../../package.json"), join(process.cwd(), "package.json"), ]; for (const pkgPath of packagePaths) { if (existsSync(pkgPath)) { const content = readFileSync(pkgPath, "utf-8"); const pkg = JSON.parse(content); if (pkg.name?.includes("snapback")) { return pkg.version || "1.0.0"; } } } } catch { // Fallback } return "1.0.0"; } // ============================================================================= // MANAGED METADATA // ============================================================================= /** * Generate managed metadata for MCP config entries * * §10.2: Creates the snapbackManaged object for config entries * * @param client - Target client * @param options - Additional options * @returns Managed metadata object */ export function createManagedMetadata( client: AIClientFormat | "other", options: { workspaceId?: string; transport: "stdio" | "stdio-shim" | "http"; }, ): SnapbackManagedMetadata { const identity = getOrCreateIdentity(); return { userId: identity.userId, installId: identity.installId, client, workspaceId: options.workspaceId, cliVersion: getCLIVersion(), updatedAt: new Date().toISOString(), transport: options.transport, }; } /** * Check if a managed metadata object belongs to this installation * * §10.3: Used to determine if we can auto-modify an entry * * @param metadata - Metadata from existing config entry * @returns True if this install owns the entry */ export function isOwnedByThisInstall(metadata: SnapbackManagedMetadata | undefined): boolean { if (!metadata?.installId) { return false; } const identity = getOrCreateIdentity(); return metadata.installId === identity.installId; } /** * Reset cached identity (for testing) */ export function resetIdentityCache(): void { cachedIdentity = null; } |