contextwise 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cloud/sync_manager.ts","../src/config/loader.ts","../src/config/schema.ts","../src/vault/file_driver.ts","../src/vault/crypto.ts","../src/cloud/crypto.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { ConfigLoader } from '../config/loader.js';\nimport {\n ContextWiseConfig,\n ContextWiseConfigSchema,\n isStdioUpstream,\n} from '../config/schema.js';\nimport { logger } from '../utils/logger.js';\nimport { getDefaultVaultFilePath } from '../vault/file_driver.js';\nimport type { EncryptedVaultPayload } from '../vault/types.js';\nimport { cloudClient } from './client.js';\nimport { getOrCreateDeviceIdentity } from './crypto.js';\nimport type { SyncPullResult, SyncPushPayload, SyncPushResult } from './types.js';\n\ninterface LocalSyncState {\n workspaceId: string;\n revision: number;\n lastSyncedAt: number;\n}\n\nexport class SyncManager {\n private syncStatePath: string;\n private localConfigPath?: string;\n\n constructor(storageDir?: string, localConfigPath?: string) {\n const dir =\n storageDir ||\n process.env.CONTEXTWISE_STORAGE_DIR ||\n join(homedir(), '.contextwise');\n this.syncStatePath = join(dir, 'sync.json');\n this.localConfigPath = localConfigPath;\n }\n\n private loadSyncState(): LocalSyncState {\n if (existsSync(this.syncStatePath)) {\n try {\n const raw = readFileSync(this.syncStatePath, 'utf-8');\n return JSON.parse(raw) as LocalSyncState;\n } catch {\n // Corrupt file fallback\n }\n }\n return {\n workspaceId: 'ws_default',\n revision: 0,\n lastSyncedAt: 0,\n };\n }\n\n private saveSyncState(state: LocalSyncState): void {\n writeFileSync(this.syncStatePath, JSON.stringify(state, null, 2), {\n mode: 0o600,\n encoding: 'utf-8',\n });\n }\n\n /**\n * Pushes local configuration and encrypted vault payload to the cloud.\n */\n async push(workspaceId?: string, receipt?: string): Promise<SyncPushResult> {\n const state = this.loadSyncState();\n const wsId = workspaceId || state.workspaceId;\n const device = getOrCreateDeviceIdentity();\n const localConfig = ConfigLoader.load();\n\n const vaultPath = getDefaultVaultFilePath();\n let encryptedVault: EncryptedVaultPayload;\n\n if (existsSync(vaultPath)) {\n try {\n const rawVault = readFileSync(vaultPath, 'utf-8');\n encryptedVault = JSON.parse(rawVault) as EncryptedVaultPayload;\n } catch {\n encryptedVault = this.createEmptyEncryptedVault();\n }\n } else {\n encryptedVault = this.createEmptyEncryptedVault();\n }\n\n const payload: SyncPushPayload = {\n workspaceId: wsId,\n deviceId: device.deviceId,\n baseRevision: state.revision,\n config: localConfig,\n encryptedVault,\n timestamp: Date.now(),\n };\n\n const result = await cloudClient.pushSync(payload, receipt);\n\n if (result.status === 'committed') {\n state.workspaceId = wsId;\n state.revision = result.revision;\n state.lastSyncedAt = Date.now();\n this.saveSyncState(state);\n logger.info(`Successfully pushed revision #${result.revision} to ContextWise Cloud.`);\n }\n\n return result;\n }\n\n /**\n * Pulls the latest cloud configuration and merges upstream servers.\n */\n async pull(workspaceId?: string, receipt?: string): Promise<SyncPullResult | null> {\n const state = this.loadSyncState();\n const wsId = workspaceId || state.workspaceId;\n\n const pullResult = await cloudClient.pullSync(wsId, state.revision, receipt);\n if (!pullResult) {\n logger.info('Local configuration is already up to date.');\n return null;\n }\n\n // Merge upstreams into local contextwise.json if present\n if (pullResult.config && pullResult.config.upstreams) {\n this.mergeConfigIntoLocal(pullResult.config);\n }\n\n // Update encrypted vault if newer\n if (pullResult.encryptedVault && pullResult.encryptedVault.data) {\n const vaultPath = getDefaultVaultFilePath();\n writeFileSync(vaultPath, JSON.stringify(pullResult.encryptedVault, null, 2), {\n mode: 0o600,\n encoding: 'utf-8',\n });\n }\n\n state.revision = pullResult.revision;\n state.lastSyncedAt = Date.now();\n this.saveSyncState(state);\n\n logger.info(`Pulled and applied cloud revision #${pullResult.revision}.`);\n return pullResult;\n }\n\n /**\n * Semantically merges remote configuration into local contextwise.json.\n */\n private mergeConfigIntoLocal(remoteConfig: ContextWiseConfig): void {\n const validatedRemote = ContextWiseConfigSchema.parse(remoteConfig);\n const configPath =\n this.localConfigPath ||\n process.env.CONTEXTWISE_CONFIG ||\n join(process.cwd(), 'contextwise.json');\n let localConfig: ContextWiseConfig;\n\n if (existsSync(configPath)) {\n try {\n localConfig = ContextWiseConfigSchema.parse(JSON.parse(readFileSync(configPath, 'utf-8')));\n } catch {\n localConfig = ContextWiseConfigSchema.parse({});\n }\n } else {\n localConfig = ContextWiseConfigSchema.parse({});\n }\n\n // Security Guard: Check for new stdio commands from remote\n for (const [name, s] of Object.entries(validatedRemote.upstreams || {})) {\n if (isStdioUpstream(s) && !localConfig.upstreams[name]) {\n logger.warn(\n `[Cloud Sync Security] Remote configuration contains new stdio upstream \"${name}\" (${s.command}). ` +\n `Verify this server in contextwise.json before execution.`\n );\n }\n }\n\n // Merge upstreams: preserve local ones, add new remote ones\n const mergedUpstreams = {\n ...(validatedRemote.upstreams || {}),\n ...(localConfig.upstreams || {}),\n };\n\n const merged: ContextWiseConfig = {\n ...localConfig,\n upstreams: mergedUpstreams,\n };\n\n writeFileSync(configPath, JSON.stringify(merged, null, 2), 'utf-8');\n }\n\n private createEmptyEncryptedVault(): EncryptedVaultPayload {\n return {\n version: 1,\n kdf: {\n algorithm: 'pbkdf2-sha512',\n salt: 'empty',\n iterations: 50_000,\n },\n cipher: 'aes-256-gcm',\n iv: '',\n authTag: '',\n data: '',\n };\n }\n\n getStatus(): {\n isLoggedIn: boolean;\n userEmail?: string;\n workspaceId: string;\n revision: number;\n lastSyncedAt: number;\n deviceId: string;\n } {\n const token = cloudClient.getToken();\n const state = this.loadSyncState();\n const device = getOrCreateDeviceIdentity();\n\n return {\n isLoggedIn: cloudClient.isAuthenticated(),\n userEmail: token?.email,\n workspaceId: state.workspaceId,\n revision: state.revision,\n lastSyncedAt: state.lastSyncedAt,\n deviceId: device.deviceId,\n };\n }\n}\n\nexport const syncManager = new SyncManager();\n","import { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, resolve } from 'node:path';\nimport { logger } from '../utils/logger.js';\nimport {\n ContextWiseConfig,\n ContextWiseConfigSchema,\n UpstreamServerConfig,\n} from './schema.js';\n\nexport interface LoadConfigOptions {\n configPath?: string;\n cwd?: string;\n autoImport?: boolean;\n}\n\nexport class ConfigLoader {\n /**\n * Discovers and loads configuration from contextwise.json or imported client configs.\n */\n static load(options: LoadConfigOptions = {}): ContextWiseConfig {\n const cwd = options.cwd ?? process.cwd();\n\n // 0. Environment variable override\n if (process.env.CONTEXTWISE_CONFIG && !options.configPath) {\n const envPath = isAbsolute(process.env.CONTEXTWISE_CONFIG)\n ? process.env.CONTEXTWISE_CONFIG\n : resolve(cwd, process.env.CONTEXTWISE_CONFIG);\n if (existsSync(envPath)) {\n return this.parseConfigFile(envPath);\n }\n }\n if (options.configPath) {\n const explicitPath = isAbsolute(options.configPath)\n ? options.configPath\n : resolve(cwd, options.configPath);\n\n if (existsSync(explicitPath)) {\n return this.parseConfigFile(explicitPath);\n }\n throw new Error(`Configuration file not found at: ${explicitPath}`);\n }\n\n // 2. Look for native contextwise.json in current directory or parent\n const candidatePaths = [\n resolve(cwd, 'contextwise.json'),\n resolve(cwd, '.contextwise.json'),\n resolve(cwd, '.contextwise/config.json'),\n resolve(homedir(), '.contextwise/config.json'),\n ];\n\n for (const candidate of candidatePaths) {\n if (existsSync(candidate)) {\n logger.debug(`Loaded configuration from ${candidate}`);\n return this.parseConfigFile(candidate);\n }\n }\n\n // 3. Auto-import from known AI client configurations if requested\n if (options.autoImport !== false) {\n const imported = this.autoImportClientConfigs(cwd);\n if (imported && Object.keys(imported.upstreams).length > 0) {\n logger.info(`Auto-discovered MCP servers from existing client configurations`);\n return imported;\n }\n }\n\n // 4. Return default empty configuration\n logger.debug('No configuration file found. Using defaults.');\n return ContextWiseConfigSchema.parse({});\n }\n\n /**\n * Parses and validates a JSON file against ContextWiseConfigSchema.\n */\n static parseConfigFile(filePath: string): ContextWiseConfig {\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n return ContextWiseConfigSchema.parse(parsed);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse config at ${filePath}: ${msg}`);\n }\n }\n\n /**\n * Scans for Cursor and Claude Desktop configs to auto-import upstreams.\n */\n static autoImportClientConfigs(cwd: string): ContextWiseConfig | null {\n const upstreams: Record<string, UpstreamServerConfig> = {};\n\n // Check .cursor/mcp.json\n const cursorMcp = resolve(cwd, '.cursor', 'mcp.json');\n if (existsSync(cursorMcp)) {\n try {\n const raw = JSON.parse(readFileSync(cursorMcp, 'utf-8'));\n if (raw.mcpServers && typeof raw.mcpServers === 'object') {\n for (const [name, server] of Object.entries(raw.mcpServers)) {\n if (name === 'contextwise') continue; // Skip self\n const s = server as Record<string, unknown>;\n if (typeof s.command === 'string') {\n upstreams[name] = {\n command: s.command,\n args: Array.isArray(s.args) ? (s.args as string[]) : [],\n env: (s.env as Record<string, string>) ?? {},\n autoRestart: true,\n };\n } else if (typeof s.url === 'string') {\n upstreams[name] = {\n url: s.url,\n headers: (s.headers as Record<string, string>) ?? {},\n transport: 'auto',\n autoReconnect: true,\n };\n }\n }\n }\n } catch (err) {\n logger.warn(`Failed reading ${cursorMcp}: ${err}`);\n }\n }\n\n // Check Claude Desktop config\n const claudePath = this.getClaudeDesktopConfigPath();\n if (claudePath && existsSync(claudePath)) {\n try {\n const raw = JSON.parse(readFileSync(claudePath, 'utf-8'));\n if (raw.mcpServers && typeof raw.mcpServers === 'object') {\n for (const [name, server] of Object.entries(raw.mcpServers)) {\n if (name === 'contextwise') continue;\n const s = server as Record<string, unknown>;\n if (typeof s.command === 'string' && !upstreams[name]) {\n upstreams[name] = {\n command: s.command,\n args: Array.isArray(s.args) ? (s.args as string[]) : [],\n env: (s.env as Record<string, string>) ?? {},\n autoRestart: true,\n };\n } else if (typeof s.url === 'string' && !upstreams[name]) {\n upstreams[name] = {\n url: s.url,\n headers: (s.headers as Record<string, string>) ?? {},\n transport: 'auto',\n autoReconnect: true,\n };\n }\n }\n }\n } catch (err) {\n logger.warn(`Failed reading Claude config at ${claudePath}: ${err}`);\n }\n }\n\n if (Object.keys(upstreams).length === 0) {\n return null;\n }\n\n return ContextWiseConfigSchema.parse({\n upstreams,\n });\n }\n\n static getClaudeDesktopConfigPath(): string | null {\n const platform = process.platform;\n const home = homedir();\n\n if (platform === 'win32') {\n const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');\n return join(appData, 'Claude', 'claude_desktop_config.json');\n } else if (platform === 'darwin') {\n return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n } else {\n return join(home, '.config', 'Claude', 'claude_desktop_config.json');\n }\n }\n}\n","import { z } from 'zod';\n\nexport const StdioUpstreamConfigSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n env: z.record(z.string()).default({}),\n cwd: z.string().optional(),\n autoRestart: z.boolean().default(true),\n});\n\nexport const HttpUpstreamConfigSchema = z.object({\n url: z.string().url(),\n headers: z.record(z.string()).default({}),\n transport: z.enum(['streamable-http', 'sse', 'auto']).default('auto'),\n autoReconnect: z.boolean().default(true),\n});\n\nexport const UpstreamServerConfigSchema = z.union([\n StdioUpstreamConfigSchema,\n HttpUpstreamConfigSchema,\n]);\n\nexport function warnIfInsecureHttp(urlStr: string, headers?: Record<string, string>): void {\n try {\n const parsed = new URL(urlStr);\n if (parsed.protocol === 'http:') {\n const isLocalhost = ['localhost', '127.0.0.1', '::1', '[::1]'].includes(parsed.hostname);\n if (!isLocalhost && headers) {\n const hasAuthHeader = Object.keys(headers).some((h) =>\n ['authorization', 'cookie', 'x-api-key', 'api-key', 'token'].includes(h.toLowerCase())\n );\n if (hasAuthHeader) {\n console.warn(\n `[SECURITY WARNING] Upstream URL \"${urlStr}\" is using unencrypted HTTP with sensitive authentication headers to non-localhost destination \"${parsed.hostname}\".`\n );\n }\n }\n }\n } catch {\n // ignore invalid URL\n }\n}\n\nexport function isStdioUpstream(\n config: UpstreamServerConfig\n): config is z.infer<typeof StdioUpstreamConfigSchema> {\n return 'command' in config;\n}\n\nexport function isHttpUpstream(\n config: UpstreamServerConfig\n): config is z.infer<typeof HttpUpstreamConfigSchema> {\n return 'url' in config;\n}\n\nexport const ProxyConfigSchema = z.object({\n transport: z.enum(['stdio', 'sse', 'http']).default('stdio'),\n port: z.number().int().min(1024).max(65535).default(3456),\n logLevel: z.enum(['debug', 'info', 'warn', 'error', 'silent']).default('info'),\n});\n\nexport const RoutingConfigSchema = z.object({\n strategy: z.enum(['hybrid', 'bm25', 'vector', 'passthrough']).default('hybrid'),\n topK: z.number().int().min(1).max(50).default(5),\n similarityThreshold: z.number().min(0).max(1).default(0.45),\n pinnedTools: z.array(z.string()).default([]),\n maxActiveTools: z.number().int().min(1).max(100).default(10),\n enableBrowseServers: z.boolean().default(false),\n enableAddServer: z.boolean().default(false),\n enableCloudSync: z.boolean().default(true),\n allowCustomCommands: z.boolean().default(false),\n persistAddedServers: z.boolean().default(false),\n});\n\nexport const GuardrailsConfigSchema = z.object({\n enableCache: z.boolean().default(true),\n cacheTtlSeconds: z.number().int().min(1).default(120),\n maxCallsPerMinute: z.number().int().min(1).default(60),\n loopBreakerThreshold: z.number().int().min(1).default(3),\n callTimeoutMs: z.number().int().min(1000).default(30000),\n});\n\nexport const ContextWiseConfigSchema = z.object({\n $schema: z.string().optional(),\n version: z.string().default('1.0.0'),\n proxy: ProxyConfigSchema.default({}),\n routing: RoutingConfigSchema.default({}),\n guardrails: GuardrailsConfigSchema.default({}),\n upstreams: z.record(UpstreamServerConfigSchema).default({}),\n});\n\nexport type StdioUpstreamConfig = z.infer<typeof StdioUpstreamConfigSchema>;\nexport type HttpUpstreamConfig = z.infer<typeof HttpUpstreamConfigSchema>;\nexport type UpstreamServerConfig = z.infer<typeof UpstreamServerConfigSchema>;\nexport type ProxyConfig = z.infer<typeof ProxyConfigSchema>;\nexport type RoutingConfig = z.infer<typeof RoutingConfigSchema>;\nexport type GuardrailsConfig = z.infer<typeof GuardrailsConfigSchema>;\nexport type ContextWiseConfig = z.infer<typeof ContextWiseConfigSchema>;\n","import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { logger } from '../utils/logger.js';\nimport {\n decryptAesGcm,\n encryptAesGcm,\n getOrCreateMachineKey,\n VAULT_PBKDF2_ITERATIONS,\n} from './crypto.js';\nimport type { EncryptedVaultPayload, SecretMetadata, VaultDriver } from './types.js';\n\ninterface StoredSecretEntry {\n value: string;\n metadata: SecretMetadata;\n}\n\nexport function getDefaultVaultFilePath(): string {\n if (process.env.CONTEXTWISE_VAULT_PATH) {\n return process.env.CONTEXTWISE_VAULT_PATH;\n }\n if (process.env.CONTEXTWISE_STORAGE_DIR) {\n return join(process.env.CONTEXTWISE_STORAGE_DIR, 'vault.enc.json');\n }\n return join(homedir(), '.contextwise', 'vault.enc.json');\n}\n\nexport class EncryptedFileVaultDriver implements VaultDriver {\n readonly name = 'encrypted_file';\n private filePath: string;\n private keyPromise: Promise<Buffer>;\n private cache: Map<string, StoredSecretEntry> | null = null;\n\n constructor(filePath?: string, customKey?: Buffer) {\n this.filePath = filePath ?? getDefaultVaultFilePath();\n this.keyPromise = customKey ? Promise.resolve(customKey) : getOrCreateMachineKey();\n }\n\n async isAvailable(): Promise<boolean> {\n return true;\n }\n\n private async load(): Promise<Map<string, StoredSecretEntry>> {\n if (this.cache) {\n return this.cache;\n }\n\n this.cache = new Map();\n\n if (!existsSync(this.filePath)) {\n return this.cache;\n }\n\n try {\n const raw = readFileSync(this.filePath, 'utf-8');\n const payload = JSON.parse(raw) as EncryptedVaultPayload;\n\n if (payload.cipher !== 'aes-256-gcm' || !payload.data) {\n logger.warn(`Corrupt or incompatible vault file at ${this.filePath}`);\n return this.cache;\n }\n\n const key = await this.keyPromise;\n const decryptedJson = decryptAesGcm(payload.data, key, payload.iv, payload.authTag);\n const entries = JSON.parse(decryptedJson) as Record<string, StoredSecretEntry>;\n\n for (const [k, v] of Object.entries(entries)) {\n this.cache.set(k, v);\n }\n } catch (err) {\n logger.warn(`Failed to decrypt vault file at ${this.filePath}: ${err}`);\n }\n\n return this.cache;\n }\n\n private async persist(): Promise<void> {\n if (!this.cache) return;\n\n const dir = dirname(this.filePath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const key = await this.keyPromise;\n const entries: Record<string, StoredSecretEntry> = {};\n for (const [k, v] of this.cache.entries()) {\n entries[k] = v;\n }\n\n const plaintext = JSON.stringify(entries);\n const { iv, authTag, ciphertext } = encryptAesGcm(plaintext, key);\n\n const payload: EncryptedVaultPayload = {\n version: 1,\n kdf: {\n algorithm: 'pbkdf2-sha512',\n salt: 'machine-bound',\n iterations: VAULT_PBKDF2_ITERATIONS,\n },\n cipher: 'aes-256-gcm',\n iv,\n authTag,\n data: ciphertext,\n };\n\n const tempPath = `${this.filePath}.${Date.now()}.tmp`;\n writeFileSync(tempPath, JSON.stringify(payload, null, 2), { mode: 0o600, encoding: 'utf-8' });\n try {\n renameSync(tempPath, this.filePath);\n } catch {\n writeFileSync(this.filePath, JSON.stringify(payload, null, 2), { mode: 0o600, encoding: 'utf-8' });\n try { unlinkSync(tempPath); } catch {}\n }\n }\n\n async get(key: string): Promise<string | null> {\n const store = await this.load();\n const entry = store.get(key);\n return entry ? entry.value : null;\n }\n\n async set(\n key: string,\n value: string,\n scope: 'personal' | 'workspace' | 'team' = 'personal'\n ): Promise<void> {\n const store = await this.load();\n const now = Date.now();\n const existing = store.get(key);\n\n store.set(key, {\n value,\n metadata: {\n key,\n scope,\n backend: this.name,\n createdAt: existing?.metadata.createdAt ?? now,\n updatedAt: now,\n },\n });\n\n await this.persist();\n logger.debug(`Stored secret \"${key}\" in ${this.name} vault`);\n }\n\n async delete(key: string): Promise<boolean> {\n const store = await this.load();\n if (!store.has(key)) {\n return false;\n }\n\n store.delete(key);\n await this.persist();\n logger.debug(`Deleted secret \"${key}\" from ${this.name} vault`);\n return true;\n }\n\n async list(): Promise<SecretMetadata[]> {\n const store = await this.load();\n return Array.from(store.values()).map((e) => e.metadata);\n }\n\n /**\n * Resets and clears all cached and persisted secrets.\n */\n async clear(): Promise<void> {\n this.cache = new Map();\n if (existsSync(this.filePath)) {\n try {\n unlinkSync(this.filePath);\n } catch {}\n }\n }\n}\n","import {\n createCipheriv,\n createDecipheriv,\n createHash,\n createPrivateKey,\n createPublicKey,\n diffieHellman,\n generateKeyPairSync,\n pbkdf2,\n randomBytes,\n} from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { arch, homedir, hostname, platform, userInfo } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nexport interface AesGcmResult {\n iv: string; // base64\n authTag: string; // base64\n ciphertext: string; // base64\n}\n\n/**\n * Encrypts UTF-8 plaintext using AES-256-GCM with a 96-bit random IV.\n */\nexport function encryptAesGcm(plaintext: string, key: Buffer): AesGcmResult {\n if (key.length !== 32) {\n throw new Error(`Invalid AES-256 key length: expected 32 bytes, got ${key.length}`);\n }\n\n const iv = randomBytes(12); // 96 bits recommended for GCM\n const cipher = createCipheriv('aes-256-gcm', key, iv);\n\n let ciphertext = cipher.update(plaintext, 'utf-8', 'base64');\n ciphertext += cipher.final('base64');\n\n const authTag = cipher.getAuthTag();\n\n return {\n iv: iv.toString('base64'),\n authTag: authTag.toString('base64'),\n ciphertext,\n };\n}\n\n/**\n * Decrypts AES-256-GCM ciphertext, verifying the 128-bit authentication tag.\n */\nexport function decryptAesGcm(\n ciphertext: string,\n key: Buffer,\n ivBase64: string,\n authTagBase64: string\n): string {\n if (key.length !== 32) {\n throw new Error(`Invalid AES-256 key length: expected 32 bytes, got ${key.length}`);\n }\n\n const iv = Buffer.from(ivBase64, 'base64');\n const authTag = Buffer.from(authTagBase64, 'base64');\n const decipher = createDecipheriv('aes-256-gcm', key, iv);\n\n decipher.setAuthTag(authTag);\n\n let decrypted = decipher.update(ciphertext, 'base64', 'utf-8');\n decrypted += decipher.final('utf-8');\n\n return decrypted;\n}\n\n/**\n * Standard OWASP-recommended PBKDF2 iterations for SHA-512 key derivation.\n */\nexport const VAULT_PBKDF2_ITERATIONS = 210_000;\n\n/**\n * Derives a 256-bit encryption key from a passphrase using PBKDF2 with SHA-512.\n */\nexport function deriveKeyFromPassphrase(\n passphrase: string,\n salt: Buffer,\n iterations: number = VAULT_PBKDF2_ITERATIONS\n): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n pbkdf2(passphrase, salt, iterations, 32, 'sha512', (err, derivedKey) => {\n if (err) reject(err);\n else resolve(derivedKey);\n });\n });\n}\n\n/**\n * Derives a key for the local encrypted vault.\n *\n * Security & Threat Model:\n * By default, this derives a deterministic machine-bound key from OS user and hardware\n * parameters and a random 32-byte salt stored at ~/.contextwise/.machine_salt (mode 0600).\n * This protects secrets against accidental repository commits, copy-pasting, and unprivileged processes.\n * For defense-in-depth against local attackers with read access to ~/.contextwise, users can set\n * the CONTEXTWISE_VAULT_PASSPHRASE environment variable, which derives the key directly from\n * their secret user passphrase instead of machine identifiers.\n */\nexport async function getOrCreateMachineKey(): Promise<Buffer> {\n const saltDir = join(homedir(), '.contextwise');\n const saltPath = join(saltDir, '.machine_salt');\n\n let salt: Buffer;\n if (existsSync(saltPath)) {\n salt = readFileSync(saltPath);\n } else {\n mkdirSync(saltDir, { recursive: true });\n salt = randomBytes(32);\n writeFileSync(saltPath, salt, { mode: 0o600 });\n }\n\n // Passphrase mode takes precedence if provided\n if (process.env.CONTEXTWISE_VAULT_PASSPHRASE) {\n return deriveKeyFromPassphrase(process.env.CONTEXTWISE_VAULT_PASSPHRASE, salt, VAULT_PBKDF2_ITERATIONS);\n }\n\n const user = (() => {\n try {\n return userInfo().username;\n } catch {\n return 'default_user';\n }\n })();\n\n const machineId = `${hostname()}-${user}-${platform()}-${arch()}-contextwise-vault-v1`;\n return deriveKeyFromPassphrase(machineId, salt, VAULT_PBKDF2_ITERATIONS);\n}\n\n/**\n * Generates an asymmetric X25519 keypair for team member envelope encryption.\n */\nexport function generateKeyPairX25519(): { publicKey: string; privateKey: string } {\n const { publicKey, privateKey } = generateKeyPairSync('x25519', {\n publicKeyEncoding: { type: 'spki', format: 'pem' },\n privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n });\n return { publicKey, privateKey };\n}\n\n/**\n * Derives a shared symmetric secret using ECDH / X25519 for team secret sharing.\n */\nexport function deriveSharedSecretX25519(privateKeyPem: string, publicKeyPem: string): Buffer {\n const privateKey = createPrivateKey(privateKeyPem);\n const publicKey = createPublicKey(publicKeyPem);\n const shared = diffieHellman({\n privateKey,\n publicKey,\n });\n // Hash the raw shared secret with SHA-256 to produce an exact 32-byte AES key\n return createHash('sha256').update(shared).digest();\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { randomBytes } from 'node:crypto';\nimport {\n decryptAesGcm,\n deriveSharedSecretX25519,\n encryptAesGcm,\n generateKeyPairX25519,\n} from '../vault/crypto.js';\nimport type { EncryptedEnvelope } from './types.js';\n\nexport interface DeviceIdentity {\n deviceId: string;\n publicKey: string;\n privateKey: string;\n}\n\n/**\n * Creates an asymmetric public-key envelope (X25519 ECDH + AES-256-GCM)\n * encrypting a workspace key specifically for a recipient's device public key.\n */\nexport function createEnvelope(\n workspaceKey: Buffer,\n recipientPublicKeyPem: string\n): EncryptedEnvelope {\n // Generate a one-time ephemeral keypair\n const ephemeral = generateKeyPairX25519();\n\n // Derive shared ECDH symmetric secret\n const sharedKey = deriveSharedSecretX25519(ephemeral.privateKey, recipientPublicKeyPem);\n\n // Encrypt the workspace symmetric key using AES-256-GCM\n const { iv, authTag, ciphertext } = encryptAesGcm(\n workspaceKey.toString('base64'),\n sharedKey\n );\n\n return {\n recipientPublicKey: recipientPublicKeyPem,\n ephemeralPublicKey: ephemeral.publicKey,\n iv,\n authTag,\n ciphertext,\n };\n}\n\n/**\n * Opens an asymmetric envelope using the recipient's private key.\n */\nexport function openEnvelope(\n envelope: EncryptedEnvelope,\n recipientPrivateKeyPem: string\n): Buffer {\n const sharedKey = deriveSharedSecretX25519(\n recipientPrivateKeyPem,\n envelope.ephemeralPublicKey\n );\n\n const decryptedBase64 = decryptAesGcm(\n envelope.ciphertext,\n sharedKey,\n envelope.iv,\n envelope.authTag\n );\n\n return Buffer.from(decryptedBase64, 'base64');\n}\n\n/**\n * Retrieves or generates the local device identity and asymmetric X25519 keypair.\n */\nexport function getOrCreateDeviceIdentity(storageDir?: string): DeviceIdentity {\n const dir =\n storageDir ||\n process.env.CONTEXTWISE_STORAGE_DIR ||\n join(homedir(), '.contextwise');\n const filePath = join(dir, 'device.json');\n\n if (existsSync(filePath)) {\n try {\n const data = JSON.parse(readFileSync(filePath, 'utf-8'));\n if (data.deviceId && data.publicKey && data.privateKey) {\n return data as DeviceIdentity;\n }\n } catch {\n // Fall through to regenerate if file is corrupt\n }\n }\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const { publicKey, privateKey } = generateKeyPairX25519();\n const deviceId = `cw_dev_${randomBytes(8).toString('hex')}`;\n\n const identity: DeviceIdentity = {\n deviceId,\n publicKey,\n privateKey,\n };\n\n writeFileSync(filePath, JSON.stringify(identity, null, 2), {\n mode: 0o600,\n encoding: 'utf-8',\n });\n\n return identity;\n}\n"],"mappings":"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","names":["existsSync","readFileSync","writeFileSync","homedir","join","platform","existsSync","mkdirSync","readFileSync","writeFileSync","homedir","dirname","join","existsSync","readFileSync","homedir","join","resolve","join","homedir","existsSync","readFileSync","dirname","mkdirSync","writeFileSync","existsSync","mkdirSync","readFileSync","writeFileSync","homedir","join","randomBytes","join","homedir","existsSync","readFileSync","mkdirSync","randomBytes","writeFileSync","join","homedir","existsSync","readFileSync","writeFileSync"]}
@@ -0,0 +1,417 @@
1
+ // src/cloud/client.ts
2
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+
6
+ // src/vault/redaction.ts
7
+ var RedactionFilter = class _RedactionFilter {
8
+ static instance = null;
9
+ registeredSecrets = /* @__PURE__ */ new Set();
10
+ static getInstance() {
11
+ if (!_RedactionFilter.instance) {
12
+ _RedactionFilter.instance = new _RedactionFilter();
13
+ }
14
+ return _RedactionFilter.instance;
15
+ }
16
+ /**
17
+ * Registers a plaintext secret value to be masked across all output streams.
18
+ */
19
+ registerSecret(secret) {
20
+ if (secret && typeof secret === "string") {
21
+ const trimmed = secret.trim();
22
+ if (trimmed.length >= 4) {
23
+ this.registeredSecrets.add(trimmed);
24
+ }
25
+ }
26
+ }
27
+ registerSecrets(secrets) {
28
+ for (const s of secrets) {
29
+ this.registerSecret(s);
30
+ }
31
+ }
32
+ getRegisteredCount() {
33
+ return this.registeredSecrets.size;
34
+ }
35
+ clear() {
36
+ this.registeredSecrets.clear();
37
+ }
38
+ /**
39
+ * Redacts registered secrets and known API key patterns from an input string.
40
+ */
41
+ redact(input) {
42
+ if (!input || typeof input !== "string") {
43
+ return input;
44
+ }
45
+ let sanitized = input;
46
+ for (const secret of this.registeredSecrets) {
47
+ sanitized = sanitized.replaceAll(secret, "[REDACTED_SECRET]");
48
+ }
49
+ sanitized = sanitized.replace(/ghp_[a-zA-Z0-9]{36}/g, "ghp_[REDACTED_GITHUB_TOKEN]");
50
+ sanitized = sanitized.replace(/sk-ant-[a-zA-Z0-9_-]{20,}/g, "sk-ant-[REDACTED_ANTHROPIC_KEY]");
51
+ sanitized = sanitized.replace(/sk-[a-zA-Z0-9]{32,}/g, "sk-[REDACTED_OPENAI_KEY]");
52
+ sanitized = sanitized.replace(
53
+ /([a-zA-Z0-9+]+:\/\/[^:]+:)([^@]+)(@.+)/g,
54
+ "$1[REDACTED_PASSWORD]$3"
55
+ );
56
+ return sanitized;
57
+ }
58
+ };
59
+ var redactionFilter = RedactionFilter.getInstance();
60
+
61
+ // src/utils/logger.ts
62
+ var LOG_LEVELS = {
63
+ debug: 10,
64
+ info: 20,
65
+ warn: 30,
66
+ error: 40,
67
+ silent: 100
68
+ };
69
+ var Logger = class {
70
+ level = "info";
71
+ prefix;
72
+ constructor(prefix = "ContextWise", level = "info") {
73
+ this.prefix = prefix;
74
+ this.level = level;
75
+ }
76
+ setLevel(level) {
77
+ this.level = level;
78
+ }
79
+ getLevel() {
80
+ return this.level;
81
+ }
82
+ shouldLog(level) {
83
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
84
+ }
85
+ formatMessage(level, message, meta) {
86
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
87
+ const metaStr = meta !== void 0 ? ` ${typeof meta === "object" ? JSON.stringify(meta) : String(meta)}` : "";
88
+ const raw = `[${timestamp}] [${level.toUpperCase()}] [${this.prefix}] ${message}${metaStr}
89
+ `;
90
+ return redactionFilter.redact(raw);
91
+ }
92
+ debug(message, meta) {
93
+ if (this.shouldLog("debug")) {
94
+ process.stderr.write(this.formatMessage("DEBUG", message, meta));
95
+ }
96
+ }
97
+ info(message, meta) {
98
+ if (this.shouldLog("info")) {
99
+ process.stderr.write(this.formatMessage("INFO", message, meta));
100
+ }
101
+ }
102
+ warn(message, meta) {
103
+ if (this.shouldLog("warn")) {
104
+ process.stderr.write(this.formatMessage("WARN", message, meta));
105
+ }
106
+ }
107
+ error(message, meta) {
108
+ if (this.shouldLog("error")) {
109
+ process.stderr.write(this.formatMessage("ERROR", message, meta));
110
+ }
111
+ }
112
+ };
113
+ var logger = new Logger("ContextWise");
114
+
115
+ // src/cloud/client.ts
116
+ var ContextWiseCloudClient = class {
117
+ apiUrl;
118
+ storageDir;
119
+ allowOfflineSimulation;
120
+ token = null;
121
+ constructor(options = {}) {
122
+ this.apiUrl = options.apiUrl || process.env.CONTEXTWISE_API_URL || "https://contextwise.dev";
123
+ this.storageDir = options.storageDir || process.env.CONTEXTWISE_STORAGE_DIR || join(homedir(), ".contextwise");
124
+ this.allowOfflineSimulation = options.allowOfflineSimulation ?? false;
125
+ this.loadToken();
126
+ }
127
+ getTokenPath() {
128
+ return join(this.storageDir, "auth.json");
129
+ }
130
+ loadToken() {
131
+ const tokenPath = this.getTokenPath();
132
+ if (existsSync(tokenPath)) {
133
+ try {
134
+ const raw = readFileSync(tokenPath, "utf-8");
135
+ this.token = JSON.parse(raw);
136
+ } catch {
137
+ this.token = null;
138
+ }
139
+ }
140
+ }
141
+ saveToken(token) {
142
+ if (!existsSync(this.storageDir)) {
143
+ mkdirSync(this.storageDir, { recursive: true });
144
+ }
145
+ this.token = token;
146
+ writeFileSync(this.getTokenPath(), JSON.stringify(token, null, 2), {
147
+ mode: 384,
148
+ encoding: "utf-8"
149
+ });
150
+ }
151
+ clearToken() {
152
+ this.token = null;
153
+ const tokenPath = this.getTokenPath();
154
+ if (existsSync(tokenPath)) {
155
+ try {
156
+ unlinkSync(tokenPath);
157
+ } catch {
158
+ }
159
+ }
160
+ }
161
+ getToken() {
162
+ if (!this.token) {
163
+ this.loadToken();
164
+ }
165
+ return this.token;
166
+ }
167
+ isAuthenticated() {
168
+ const token = this.getToken();
169
+ if (!token) return false;
170
+ return token.expiresAt > Date.now();
171
+ }
172
+ /**
173
+ * Starts RFC 8628 device authorization flow.
174
+ */
175
+ async startDeviceFlow() {
176
+ const res = await fetch(`${this.apiUrl}/v1/auth/device/code`, {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json" }
179
+ });
180
+ if (!res.ok) {
181
+ throw new Error(`Failed to initiate device login: HTTP ${res.status}`);
182
+ }
183
+ return await res.json();
184
+ }
185
+ /**
186
+ * Polls device token until user approves the code in their browser.
187
+ */
188
+ async pollDeviceToken(deviceCode, intervalSeconds = 5, maxWaitMs = 15 * 60 * 1e3) {
189
+ const start = Date.now();
190
+ const intervalMs = Math.max(intervalSeconds * 1e3, 2e3);
191
+ while (Date.now() - start < maxWaitMs) {
192
+ await new Promise((r) => setTimeout(r, intervalMs));
193
+ try {
194
+ const res = await fetch(`${this.apiUrl}/v1/auth/device/token`, {
195
+ method: "POST",
196
+ headers: { "Content-Type": "application/json" },
197
+ body: JSON.stringify({ device_code: deviceCode })
198
+ });
199
+ if (res.ok) {
200
+ const data = await res.json();
201
+ const authToken = {
202
+ token: data.token,
203
+ userId: data.userId,
204
+ email: data.email,
205
+ expiresAt: data.expiresAt
206
+ };
207
+ this.saveToken(authToken);
208
+ return authToken;
209
+ }
210
+ if (res.status === 428) {
211
+ continue;
212
+ }
213
+ const errData = await res.json().catch(() => ({}));
214
+ throw new Error(errData.message || `Device authorization failed: HTTP ${res.status}`);
215
+ } catch (err) {
216
+ if (err instanceof Error && err.message.includes("Device authorization failed")) {
217
+ throw err;
218
+ }
219
+ }
220
+ }
221
+ throw new Error("Device authorization timed out. Please try logging in again.");
222
+ }
223
+ /**
224
+ * Authenticates using a user API token or personal access key.
225
+ */
226
+ async loginWithKey(apiKey) {
227
+ const trimmed = apiKey.trim();
228
+ if (!trimmed) {
229
+ throw new Error("API key cannot be empty");
230
+ }
231
+ try {
232
+ const res = await fetch(`${this.apiUrl}/v1/auth/verify`, {
233
+ method: "POST",
234
+ headers: {
235
+ "Content-Type": "application/json",
236
+ Authorization: `Bearer ${trimmed}`
237
+ }
238
+ });
239
+ if (!res.ok) {
240
+ if (res.status === 401 || res.status === 403) {
241
+ throw new Error("Invalid or expired ContextWise Cloud API key.");
242
+ }
243
+ throw new Error(`Cloud API returned HTTP ${res.status}: ${res.statusText}`);
244
+ }
245
+ const data = await res.json();
246
+ const authToken = {
247
+ token: trimmed,
248
+ userId: data.userId,
249
+ email: data.email,
250
+ expiresAt: data.expiresAt || Date.now() + 30 * 24 * 3600 * 1e3
251
+ };
252
+ this.saveToken(authToken);
253
+ return authToken;
254
+ } catch (err) {
255
+ if (this.allowOfflineSimulation && (trimmed.startsWith("cw_test_") || trimmed.startsWith("cw_live_"))) {
256
+ const dummyToken = {
257
+ token: trimmed,
258
+ userId: "usr_dev_" + trimmed.slice(-6),
259
+ email: "developer@contextwise.dev",
260
+ expiresAt: Date.now() + 30 * 24 * 3600 * 1e3
261
+ };
262
+ this.saveToken(dummyToken);
263
+ return dummyToken;
264
+ }
265
+ throw err;
266
+ }
267
+ }
268
+ /**
269
+ * Retrieves profile information for current authenticated account.
270
+ */
271
+ async whoami() {
272
+ const token = this.getToken();
273
+ if (!token) {
274
+ throw new Error('Not logged in. Run "contextwise login" first.');
275
+ }
276
+ try {
277
+ const res = await fetch(`${this.apiUrl}/v1/user/profile`, {
278
+ headers: { Authorization: `Bearer ${token.token}` }
279
+ });
280
+ if (res.ok) {
281
+ return await res.json();
282
+ }
283
+ if (!this.allowOfflineSimulation) {
284
+ throw new Error(`Failed to fetch profile: HTTP ${res.status} ${res.statusText}`);
285
+ }
286
+ } catch (err) {
287
+ if (!this.allowOfflineSimulation) {
288
+ throw err;
289
+ }
290
+ }
291
+ return {
292
+ userId: token.userId,
293
+ email: token.email,
294
+ plan: "free",
295
+ subscriptionStatus: "inactive",
296
+ currentPeriodEnd: null,
297
+ workspaces: [
298
+ {
299
+ id: "ws_default",
300
+ name: "Personal Workspace",
301
+ role: "owner",
302
+ activeRevision: 1,
303
+ updatedAt: Date.now()
304
+ }
305
+ ],
306
+ devices: []
307
+ };
308
+ }
309
+ /**
310
+ * Pushes a workspace snapshot (config + encrypted vault) to the Cloudflare Workers / D1 backend.
311
+ */
312
+ async pushSync(payload, receipt) {
313
+ const token = this.getToken();
314
+ if (!token) {
315
+ throw new Error('Not logged in. Run "contextwise login" first.');
316
+ }
317
+ const receiptHeader = receipt || (process.env.MCPAID_RECEIPT || "").trim();
318
+ try {
319
+ const res = await fetch(`${this.apiUrl}/v1/sync/push`, {
320
+ method: "POST",
321
+ headers: {
322
+ "Content-Type": "application/json",
323
+ Authorization: `Bearer ${token.token}`,
324
+ ...receiptHeader ? { "X-MCPaid-Receipt": receiptHeader } : {}
325
+ },
326
+ body: JSON.stringify(payload)
327
+ });
328
+ if (res.ok) {
329
+ return await res.json();
330
+ }
331
+ if (res.status === 402) {
332
+ const payData = await res.json().catch(() => ({}));
333
+ throw new Error(
334
+ payData.message || "Cloud sync push requires a valid MCPaid payment receipt."
335
+ );
336
+ }
337
+ if (res.status === 409) {
338
+ const conflictData = await res.json();
339
+ return {
340
+ status: "conflict",
341
+ revision: payload.baseRevision,
342
+ serverRevision: conflictData.serverRevision,
343
+ serverPayload: conflictData.serverPayload
344
+ };
345
+ }
346
+ throw new Error(`Push sync failed: HTTP ${res.status} ${res.statusText}`);
347
+ } catch (err) {
348
+ if (err instanceof Error && (err.message.includes("payment") || err.message.includes("receipt"))) {
349
+ throw err;
350
+ }
351
+ if (this.allowOfflineSimulation) {
352
+ logger.debug(`Cloud API push endpoint unreachable (${err}). Using local snapshot commit.`);
353
+ return {
354
+ status: "committed",
355
+ revision: payload.baseRevision + 1
356
+ };
357
+ }
358
+ throw err;
359
+ }
360
+ }
361
+ /**
362
+ * Pulls the latest workspace snapshot (config + encrypted vault) from the cloud backend.
363
+ */
364
+ async pullSync(workspaceId, sinceRevision = 0, receipt) {
365
+ const token = this.getToken();
366
+ if (!token) {
367
+ throw new Error('Not logged in. Run "contextwise login" first.');
368
+ }
369
+ try {
370
+ const receiptHeader = receipt || (process.env.MCPAID_RECEIPT || "").trim();
371
+ const res = await fetch(
372
+ `${this.apiUrl}/v1/sync/pull?workspaceId=${encodeURIComponent(
373
+ workspaceId
374
+ )}&since=${sinceRevision}`,
375
+ {
376
+ headers: {
377
+ Authorization: `Bearer ${token.token}`,
378
+ ...receiptHeader ? { "X-MCPaid-Receipt": receiptHeader } : {}
379
+ }
380
+ }
381
+ );
382
+ if (res.ok) {
383
+ return await res.json();
384
+ }
385
+ if (res.status === 304) {
386
+ return null;
387
+ }
388
+ if (res.status === 402) {
389
+ const payData = await res.json().catch(() => ({}));
390
+ throw new Error(
391
+ payData.message || "Cloud sync pull requires a valid MCPaid payment receipt."
392
+ );
393
+ }
394
+ throw new Error(`Pull sync failed: HTTP ${res.status} ${res.statusText}`);
395
+ } catch (err) {
396
+ if (err instanceof Error && (err.message.includes("payment") || err.message.includes("receipt") || err.message.includes("Pull sync failed"))) {
397
+ throw err;
398
+ }
399
+ if (this.allowOfflineSimulation) {
400
+ logger.debug(`Cloud API pull endpoint unreachable (${err}). Returning local state.`);
401
+ return null;
402
+ }
403
+ throw err;
404
+ }
405
+ }
406
+ };
407
+ var cloudClient = new ContextWiseCloudClient();
408
+
409
+ export {
410
+ RedactionFilter,
411
+ redactionFilter,
412
+ Logger,
413
+ logger,
414
+ ContextWiseCloudClient,
415
+ cloudClient
416
+ };
417
+ //# sourceMappingURL=chunk-WP7RH2Z2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cloud/client.ts","../src/vault/redaction.ts","../src/utils/logger.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { logger } from '../utils/logger.js';\nimport type {\n CloudAuthToken,\n CloudClientOptions,\n CloudProfileInfo,\n DeviceMetadata,\n SyncPullResult,\n SyncPushPayload,\n SyncPushResult,\n WorkspaceMetadata,\n} from './types.js';\n\nexport class ContextWiseCloudClient {\n private apiUrl: string;\n private storageDir: string;\n private allowOfflineSimulation: boolean;\n private token: CloudAuthToken | null = null;\n\n constructor(options: CloudClientOptions = {}) {\n this.apiUrl =\n options.apiUrl ||\n process.env.CONTEXTWISE_API_URL ||\n 'https://contextwise.dev';\n this.storageDir =\n options.storageDir ||\n process.env.CONTEXTWISE_STORAGE_DIR ||\n join(homedir(), '.contextwise');\n this.allowOfflineSimulation = options.allowOfflineSimulation ?? false;\n this.loadToken();\n }\n\n private getTokenPath(): string {\n return join(this.storageDir, 'auth.json');\n }\n\n private loadToken(): void {\n const tokenPath = this.getTokenPath();\n if (existsSync(tokenPath)) {\n try {\n const raw = readFileSync(tokenPath, 'utf-8');\n this.token = JSON.parse(raw) as CloudAuthToken;\n } catch {\n this.token = null;\n }\n }\n }\n\n saveToken(token: CloudAuthToken): void {\n if (!existsSync(this.storageDir)) {\n mkdirSync(this.storageDir, { recursive: true });\n }\n this.token = token;\n writeFileSync(this.getTokenPath(), JSON.stringify(token, null, 2), {\n mode: 0o600,\n encoding: 'utf-8',\n });\n }\n\n clearToken(): void {\n this.token = null;\n const tokenPath = this.getTokenPath();\n if (existsSync(tokenPath)) {\n try {\n unlinkSync(tokenPath);\n } catch {}\n }\n }\n\n getToken(): CloudAuthToken | null {\n if (!this.token) {\n this.loadToken();\n }\n return this.token;\n }\n\n isAuthenticated(): boolean {\n const token = this.getToken();\n if (!token) return false;\n return token.expiresAt > Date.now();\n }\n\n /**\n * Starts RFC 8628 device authorization flow.\n */\n async startDeviceFlow(): Promise<{\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n }> {\n const res = await fetch(`${this.apiUrl}/v1/auth/device/code`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`Failed to initiate device login: HTTP ${res.status}`);\n }\n\n return (await res.json()) as {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n };\n }\n\n /**\n * Polls device token until user approves the code in their browser.\n */\n async pollDeviceToken(\n deviceCode: string,\n intervalSeconds = 5,\n maxWaitMs = 15 * 60 * 1000\n ): Promise<CloudAuthToken> {\n const start = Date.now();\n const intervalMs = Math.max(intervalSeconds * 1000, 2000);\n\n while (Date.now() - start < maxWaitMs) {\n await new Promise((r) => setTimeout(r, intervalMs));\n\n try {\n const res = await fetch(`${this.apiUrl}/v1/auth/device/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ device_code: deviceCode }),\n });\n\n if (res.ok) {\n const data = (await res.json()) as {\n token: string;\n userId: string;\n email: string;\n expiresAt: number;\n };\n\n const authToken: CloudAuthToken = {\n token: data.token,\n userId: data.userId,\n email: data.email,\n expiresAt: data.expiresAt,\n };\n\n this.saveToken(authToken);\n return authToken;\n }\n\n if (res.status === 428) {\n // Authorization pending, continue polling\n continue;\n }\n\n const errData = (await res.json().catch(() => ({}))) as { message?: string };\n throw new Error(errData.message || `Device authorization failed: HTTP ${res.status}`);\n } catch (err) {\n if (err instanceof Error && err.message.includes('Device authorization failed')) {\n throw err;\n }\n }\n }\n\n throw new Error('Device authorization timed out. Please try logging in again.');\n }\n\n /**\n * Authenticates using a user API token or personal access key.\n */\n async loginWithKey(apiKey: string): Promise<CloudAuthToken> {\n const trimmed = apiKey.trim();\n if (!trimmed) {\n throw new Error('API key cannot be empty');\n }\n\n try {\n const res = await fetch(`${this.apiUrl}/v1/auth/verify`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${trimmed}`,\n },\n });\n\n if (!res.ok) {\n // If remote backend is not reachable or endpoint returns 401\n if (res.status === 401 || res.status === 403) {\n throw new Error('Invalid or expired ContextWise Cloud API key.');\n }\n throw new Error(`Cloud API returned HTTP ${res.status}: ${res.statusText}`);\n }\n\n const data = (await res.json()) as {\n userId: string;\n email: string;\n expiresAt: number;\n };\n\n const authToken: CloudAuthToken = {\n token: trimmed,\n userId: data.userId,\n email: data.email,\n expiresAt: data.expiresAt || Date.now() + 30 * 24 * 3600 * 1000,\n };\n\n this.saveToken(authToken);\n return authToken;\n } catch (err) {\n // In offline / staging test scenarios, provide fallback verification only if simulation is explicitly permitted\n if (this.allowOfflineSimulation && (trimmed.startsWith('cw_test_') || trimmed.startsWith('cw_live_'))) {\n const dummyToken: CloudAuthToken = {\n token: trimmed,\n userId: 'usr_dev_' + trimmed.slice(-6),\n email: 'developer@contextwise.dev',\n expiresAt: Date.now() + 30 * 24 * 3600 * 1000,\n };\n this.saveToken(dummyToken);\n return dummyToken;\n }\n throw err;\n }\n }\n\n /**\n * Retrieves profile information for current authenticated account.\n */\n async whoami(): Promise<CloudProfileInfo> {\n const token = this.getToken();\n if (!token) {\n throw new Error('Not logged in. Run \"contextwise login\" first.');\n }\n\n try {\n const res = await fetch(`${this.apiUrl}/v1/user/profile`, {\n headers: { Authorization: `Bearer ${token.token}` },\n });\n\n if (res.ok) {\n return (await res.json()) as CloudProfileInfo;\n }\n if (!this.allowOfflineSimulation) {\n throw new Error(`Failed to fetch profile: HTTP ${res.status} ${res.statusText}`);\n }\n } catch (err) {\n if (!this.allowOfflineSimulation) {\n throw err;\n }\n }\n\n return {\n userId: token.userId,\n email: token.email,\n plan: 'free',\n subscriptionStatus: 'inactive',\n currentPeriodEnd: null,\n workspaces: [\n {\n id: 'ws_default',\n name: 'Personal Workspace',\n role: 'owner',\n activeRevision: 1,\n updatedAt: Date.now(),\n },\n ],\n devices: [],\n };\n }\n\n /**\n * Pushes a workspace snapshot (config + encrypted vault) to the Cloudflare Workers / D1 backend.\n */\n async pushSync(payload: SyncPushPayload, receipt?: string): Promise<SyncPushResult> {\n const token = this.getToken();\n if (!token) {\n throw new Error('Not logged in. Run \"contextwise login\" first.');\n }\n\n // Edge Receipts: forward X-MCPaid-Receipt if provided (e.g. from an MCPaid edge gateway tool call)\n // or from MCPAID_RECEIPT environment variable.\n const receiptHeader = receipt || (process.env.MCPAID_RECEIPT || '').trim();\n\n try {\n const res = await fetch(`${this.apiUrl}/v1/sync/push`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${token.token}`,\n ...(receiptHeader ? { 'X-MCPaid-Receipt': receiptHeader } : {}),\n },\n body: JSON.stringify(payload),\n });\n\n if (res.ok) {\n return (await res.json()) as SyncPushResult;\n }\n\n if (res.status === 402) {\n const payData = (await res.json().catch(() => ({}))) as {\n error?: string;\n message?: string;\n };\n throw new Error(\n payData.message || 'Cloud sync push requires a valid MCPaid payment receipt.'\n );\n }\n\n if (res.status === 409) {\n // Conflict detected\n const conflictData = (await res.json()) as SyncPushResult;\n return {\n status: 'conflict',\n revision: payload.baseRevision,\n serverRevision: conflictData.serverRevision,\n serverPayload: conflictData.serverPayload,\n };\n }\n\n throw new Error(`Push sync failed: HTTP ${res.status} ${res.statusText}`);\n } catch (err) {\n // Payment / usage errors always surface to the user (never fall through\n // to the offline simulation below, which must not mask a 402).\n if (err instanceof Error && (err.message.includes('payment') || err.message.includes('receipt'))) {\n throw err;\n }\n\n // Offline / simulation fallback only when explicitly enabled\n if (this.allowOfflineSimulation) {\n logger.debug(`Cloud API push endpoint unreachable (${err}). Using local snapshot commit.`);\n return {\n status: 'committed',\n revision: payload.baseRevision + 1,\n };\n }\n\n throw err;\n }\n }\n\n /**\n * Pulls the latest workspace snapshot (config + encrypted vault) from the cloud backend.\n */\n async pullSync(\n workspaceId: string,\n sinceRevision: number = 0,\n receipt?: string\n ): Promise<SyncPullResult | null> {\n const token = this.getToken();\n if (!token) {\n throw new Error('Not logged in. Run \"contextwise login\" first.');\n }\n\n try {\n const receiptHeader = receipt || (process.env.MCPAID_RECEIPT || '').trim();\n const res = await fetch(\n `${this.apiUrl}/v1/sync/pull?workspaceId=${encodeURIComponent(\n workspaceId\n )}&since=${sinceRevision}`,\n {\n headers: {\n Authorization: `Bearer ${token.token}`,\n ...(receiptHeader ? { 'X-MCPaid-Receipt': receiptHeader } : {}),\n },\n }\n );\n\n if (res.ok) {\n return (await res.json()) as SyncPullResult;\n }\n if (res.status === 304) {\n return null; // Already up to date\n }\n if (res.status === 402) {\n const payData = (await res.json().catch(() => ({}))) as { message?: string };\n throw new Error(\n payData.message || 'Cloud sync pull requires a valid MCPaid payment receipt.'\n );\n }\n throw new Error(`Pull sync failed: HTTP ${res.status} ${res.statusText}`);\n } catch (err) {\n if (err instanceof Error && (err.message.includes('payment') || err.message.includes('receipt') || err.message.includes('Pull sync failed'))) {\n throw err;\n }\n\n // Offline / simulation fallback only when explicitly enabled\n if (this.allowOfflineSimulation) {\n logger.debug(`Cloud API pull endpoint unreachable (${err}). Returning local state.`);\n return null;\n }\n\n throw err;\n }\n }\n}\n\nexport const cloudClient = new ContextWiseCloudClient();\n","/**\n * Stream & Text Redaction Engine for ContextWise.\n * Prevents sensitive API keys, database passwords, and vault tokens\n * from leaking into stdout, stderr, logs, or MCP prompt outputs.\n */\nexport class RedactionFilter {\n private static instance: RedactionFilter | null = null;\n private registeredSecrets: Set<string> = new Set();\n\n static getInstance(): RedactionFilter {\n if (!RedactionFilter.instance) {\n RedactionFilter.instance = new RedactionFilter();\n }\n return RedactionFilter.instance;\n }\n\n /**\n * Registers a plaintext secret value to be masked across all output streams.\n */\n registerSecret(secret: string): void {\n if (secret && typeof secret === 'string') {\n const trimmed = secret.trim();\n // Minimum 4 characters to avoid masking common small tokens/integers\n if (trimmed.length >= 4) {\n this.registeredSecrets.add(trimmed);\n }\n }\n }\n\n registerSecrets(secrets: string[]): void {\n for (const s of secrets) {\n this.registerSecret(s);\n }\n }\n\n getRegisteredCount(): number {\n return this.registeredSecrets.size;\n }\n\n clear(): void {\n this.registeredSecrets.clear();\n }\n\n /**\n * Redacts registered secrets and known API key patterns from an input string.\n */\n redact(input: string): string {\n if (!input || typeof input !== 'string') {\n return input;\n }\n\n let sanitized = input;\n\n // 1. Redact exact registered secret values\n for (const secret of this.registeredSecrets) {\n sanitized = sanitized.replaceAll(secret, '[REDACTED_SECRET]');\n }\n\n // 2. Redact common token patterns as a secondary safety net\n // GitHub PATs\n sanitized = sanitized.replace(/ghp_[a-zA-Z0-9]{36}/g, 'ghp_[REDACTED_GITHUB_TOKEN]');\n // Anthropic API keys\n sanitized = sanitized.replace(/sk-ant-[a-zA-Z0-9_-]{20,}/g, 'sk-ant-[REDACTED_ANTHROPIC_KEY]');\n // OpenAI API keys\n sanitized = sanitized.replace(/sk-[a-zA-Z0-9]{32,}/g, 'sk-[REDACTED_OPENAI_KEY]');\n // Database connection strings containing passwords\n sanitized = sanitized.replace(\n /([a-zA-Z0-9+]+:\\/\\/[^:]+:)([^@]+)(@.+)/g,\n '$1[REDACTED_PASSWORD]$3'\n );\n\n return sanitized;\n }\n}\n\nexport const redactionFilter = RedactionFilter.getInstance();\n","/**\n * Structured Logger for ContextWise\n * \n * CRITICAL MCP REQUIREMENT:\n * In stdio mode, stdout is strictly reserved for JSON-RPC 2.0 messages.\n * Any log messages must be written to stderr or log files to prevent protocol corruption.\n */\n\nimport { redactionFilter } from '../vault/redaction.js';\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 100,\n};\n\nexport class Logger {\n private level: LogLevel = 'info';\n private prefix: string;\n\n constructor(prefix: string = 'ContextWise', level: LogLevel = 'info') {\n this.prefix = prefix;\n this.level = level;\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n getLevel(): LogLevel {\n return this.level;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n }\n\n private formatMessage(level: string, message: string, meta?: unknown): string {\n const timestamp = new Date().toISOString();\n const metaStr = meta !== undefined ? ` ${typeof meta === 'object' ? JSON.stringify(meta) : String(meta)}` : '';\n const raw = `[${timestamp}] [${level.toUpperCase()}] [${this.prefix}] ${message}${metaStr}\\n`;\n return redactionFilter.redact(raw);\n }\n\n debug(message: string, meta?: unknown): void {\n if (this.shouldLog('debug')) {\n process.stderr.write(this.formatMessage('DEBUG', message, meta));\n }\n }\n\n info(message: string, meta?: unknown): void {\n if (this.shouldLog('info')) {\n process.stderr.write(this.formatMessage('INFO', message, meta));\n }\n }\n\n warn(message: string, meta?: unknown): void {\n if (this.shouldLog('warn')) {\n process.stderr.write(this.formatMessage('WARN', message, meta));\n }\n }\n\n error(message: string, meta?: unknown): void {\n if (this.shouldLog('error')) {\n process.stderr.write(this.formatMessage('ERROR', message, meta));\n }\n }\n}\n\nexport const logger = new Logger('ContextWise');\n"],"mappings":"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","names":[]}