@deerdawn/mcp-server
Advanced tools
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { | ||
| get: (a, b) => (typeof require !== "undefined" ? require : a)[b] | ||
| }) : x)(function(x) { | ||
| if (typeof require !== "undefined") return require.apply(this, arguments); | ||
| throw Error('Dynamic require of "' + x + '" is not supported'); | ||
| }); | ||
| var __esm = (fn, res) => function __init() { | ||
| return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; | ||
| }; | ||
| var __commonJS = (cb, mod) => function __require2() { | ||
| return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| // node_modules/tsup/assets/esm_shims.js | ||
| import path from "path"; | ||
| import { fileURLToPath } from "url"; | ||
| var init_esm_shims = __esm({ | ||
| "node_modules/tsup/assets/esm_shims.js"() { | ||
| "use strict"; | ||
| } | ||
| }); | ||
| // src/client.ts | ||
| init_esm_shims(); | ||
| import axios from "axios"; | ||
| import fs from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| function isLocalApiUrl(apiUrl) { | ||
| try { | ||
| const host = new URL(apiUrl).hostname; | ||
| return host === "localhost" || host === "127.0.0.1" || host === "::1"; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function getDeerdawnHome() { | ||
| return process.env.DEERDAWN_HOME || path2.join(os.homedir(), ".deerdawn"); | ||
| } | ||
| function getQueuePath() { | ||
| return path2.join(getDeerdawnHome(), "cloud-write-queue.json"); | ||
| } | ||
| function ensureDeerdawnHome() { | ||
| fs.mkdirSync(getDeerdawnHome(), { recursive: true }); | ||
| } | ||
| function readQueueState() { | ||
| try { | ||
| return JSON.parse(fs.readFileSync(getQueuePath(), "utf8")); | ||
| } catch { | ||
| return { entries: [] }; | ||
| } | ||
| } | ||
| function writeQueueState(state) { | ||
| ensureDeerdawnHome(); | ||
| fs.writeFileSync(getQueuePath(), JSON.stringify(state, null, 2)); | ||
| } | ||
| function mapOrgUsageToSettings(data) { | ||
| const plan = data?.plan || {}; | ||
| return { | ||
| org_id: plan.org_id || "", | ||
| name: data?.org_name || "", | ||
| plan: typeof plan.tier === "string" ? plan.tier : "free", | ||
| fail_mode: plan.fail_mode === "open" ? "open" : "closed", | ||
| redaction_mode: plan.redaction_mode === "permissive" ? "permissive" : "strict", | ||
| is_locked: !!plan.is_locked, | ||
| shadow_mode: !!plan.shadow_mode, | ||
| redaction_keys: Array.isArray(plan.redaction_keys) ? plan.redaction_keys : [], | ||
| created_at: (/* @__PURE__ */ new Date()).toISOString(), | ||
| usage: data?.usage, | ||
| limits: data?.limits | ||
| }; | ||
| } | ||
| var DeerdawnClient = class { | ||
| client; | ||
| cache; | ||
| config; | ||
| MAX_CACHE_SIZE = 100; | ||
| MAX_CACHE_ITEM_SIZE_MB = 5; | ||
| constructor(config) { | ||
| if (!config.apiKey || config.apiKey.trim() !== config.apiKey) { | ||
| throw new Error("Invalid API key: cannot be empty or contain whitespace"); | ||
| } | ||
| if (!isLocalApiUrl(config.apiUrl) && !config.apiKey.startsWith("dd_")) { | ||
| throw new Error("Invalid API key format: must start with dd_ prefix"); | ||
| } | ||
| this.config = config; | ||
| this.cache = /* @__PURE__ */ new Map(); | ||
| this.client = axios.create({ | ||
| baseURL: config.apiUrl, | ||
| headers: { | ||
| "x-api-key": config.apiKey, | ||
| "content-type": "application/json" | ||
| }, | ||
| timeout: 3e4 | ||
| }); | ||
| this.client.interceptors.response.use( | ||
| (response) => response, | ||
| async (error) => { | ||
| const cfg = error.config; | ||
| const response = error.response; | ||
| if (!cfg) { | ||
| throw error; | ||
| } | ||
| if (!cfg.__retryCount) { | ||
| cfg.__retryCount = 0; | ||
| cfg.__firstRetryTimestamp = Date.now(); | ||
| } | ||
| const totalRetryTime = Date.now() - (cfg.__firstRetryTimestamp || Date.now()); | ||
| if (totalRetryTime > 6e4) { | ||
| throw new Error("Retry timeout exceeded (60s)"); | ||
| } | ||
| const shouldRetry = (response?.status === 429 || response?.status && response.status >= 500) && cfg.__retryCount < this.config.maxRetries; | ||
| if (shouldRetry) { | ||
| cfg.__retryCount++; | ||
| const delay = Math.min( | ||
| this.config.retryBackoffMs * Math.pow(2, cfg.__retryCount - 1), | ||
| 3e4 | ||
| ); | ||
| await new Promise((resolve) => setTimeout(resolve, delay)); | ||
| return this.client.request(cfg); | ||
| } | ||
| throw error; | ||
| } | ||
| ); | ||
| } | ||
| async recordProductEvent(event) { | ||
| const { data } = await this.client.post("/api/v1/product-events", event); | ||
| return data; | ||
| } | ||
| async recordContextEvent(event) { | ||
| try { | ||
| const { data } = await this.client.post("/api/v1/context/events", event); | ||
| return data; | ||
| } catch (error) { | ||
| const status = error?.response?.status; | ||
| const shouldQueue = !status || status >= 500 || error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND"; | ||
| if (!shouldQueue) throw error; | ||
| const state = readQueueState(); | ||
| state.entries.push({ | ||
| id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, | ||
| method: "POST", | ||
| path: "/api/v1/context/events", | ||
| body: event, | ||
| created_at: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| writeQueueState(state); | ||
| return { queued: true, queue_size: state.entries.length, event_type: event.event_type }; | ||
| } | ||
| } | ||
| async flushQueuedContextWrites() { | ||
| const state = readQueueState(); | ||
| if (!state.entries.length) return { flushed: 0, remaining: 0 }; | ||
| const remaining = []; | ||
| let flushed = 0; | ||
| for (const entry of state.entries) { | ||
| try { | ||
| await this.client.post(entry.path, entry.body); | ||
| flushed += 1; | ||
| } catch { | ||
| remaining.push(entry); | ||
| } | ||
| } | ||
| writeQueueState({ entries: remaining }); | ||
| return { flushed, remaining: remaining.length }; | ||
| } | ||
| async getOrganizationSettings() { | ||
| const { data } = await this.client.get("/api/v1/orgs/usage"); | ||
| return mapOrgUsageToSettings(data); | ||
| } | ||
| async getContextGraphProject(projectId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/projects/${encodeURIComponent(projectId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async getContextGraphSession(sessionId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/sessions/${encodeURIComponent(sessionId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async getContextGraphNode(nodeId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/nodes/${encodeURIComponent(nodeId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async queryContextGraph(input) { | ||
| const { data } = await this.client.post("/api/v1/context/graph/query", input); | ||
| return data.graph || data; | ||
| } | ||
| async updateOrganizationSettings(settings) { | ||
| const { data } = await this.client.patch("/api/v1/orgs/settings", settings); | ||
| return data; | ||
| } | ||
| async getAuditLogs(_params = {}) { | ||
| const { data } = await this.client.get("/api/v1/audit"); | ||
| const logs = data.logs || []; | ||
| return { logs, total: logs.length }; | ||
| } | ||
| async getActionRegistry() { | ||
| const cacheKey = "registry:actions"; | ||
| const cached = this.getFromCache(cacheKey); | ||
| if (cached) return cached; | ||
| try { | ||
| const { data } = await this.client.get("/api/v1/registry/actions"); | ||
| const actions = data.actions || data; | ||
| if (Array.isArray(actions) && actions.length > 0) { | ||
| this.setInCache(cacheKey, actions, 3e5); | ||
| return actions; | ||
| } | ||
| } catch { | ||
| } | ||
| return this.getDefaultActionRegistry(); | ||
| } | ||
| getDefaultActionRegistry() { | ||
| return [ | ||
| { | ||
| action_type: "auth.login", | ||
| category: "authentication", | ||
| description: "User login attempt", | ||
| risk_level: "medium" | ||
| }, | ||
| { | ||
| action_type: "auth.password_reset", | ||
| category: "authentication", | ||
| description: "Password reset request", | ||
| risk_level: "medium" | ||
| }, | ||
| { | ||
| action_type: "payment.process", | ||
| category: "financial", | ||
| description: "Payment processing", | ||
| risk_level: "high" | ||
| }, | ||
| { | ||
| action_type: "user.delete", | ||
| category: "data_management", | ||
| description: "User account deletion", | ||
| risk_level: "high" | ||
| }, | ||
| { | ||
| action_type: "data.export", | ||
| category: "data_management", | ||
| description: "Data export request", | ||
| risk_level: "medium" | ||
| } | ||
| ]; | ||
| } | ||
| getFromCache(key) { | ||
| if (!this.config.cacheEnabled) return null; | ||
| const entry = this.cache.get(key); | ||
| if (!entry) return null; | ||
| if (Date.now() > entry.expires) { | ||
| this.cache.delete(key); | ||
| return null; | ||
| } | ||
| return entry.data; | ||
| } | ||
| setInCache(key, data, ttlMs) { | ||
| if (!this.config.cacheEnabled) return; | ||
| const estimatedSize = JSON.stringify(data).length; | ||
| const estimatedSizeMB = estimatedSize / (1024 * 1024); | ||
| if (estimatedSizeMB > this.MAX_CACHE_ITEM_SIZE_MB) { | ||
| console.error(`[CACHE] Skipping large object (${estimatedSizeMB.toFixed(2)}MB)`); | ||
| return; | ||
| } | ||
| if (this.cache.size >= this.MAX_CACHE_SIZE) { | ||
| const firstKey = this.cache.keys().next().value; | ||
| if (firstKey) { | ||
| this.cache.delete(firstKey); | ||
| } | ||
| } | ||
| this.cache.set(key, { | ||
| data, | ||
| expires: Date.now() + ttlMs | ||
| }); | ||
| } | ||
| invalidateCache(prefix) { | ||
| for (const key of this.cache.keys()) { | ||
| if (key.startsWith(prefix)) { | ||
| this.cache.delete(key); | ||
| } | ||
| } | ||
| } | ||
| // Context sync methods | ||
| async getActiveContext(projectId, tool = "claude_code", verbosity = "standard") { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId, | ||
| last_updated: null | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity } }); | ||
| return data; | ||
| } | ||
| // Brief shape (?shape=spine) — the product-voice 4-slot view (SHIPPED / DECIDED | ||
| // / OPEN / LANDMINE). Returns the 4-slot TEXT body plus the structured | ||
| // brief_spine (per-item trust). Opt-in: never changes the default brief text. | ||
| async getActiveBrief(projectId, tool = "claude_code", verbosity = "standard") { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity, shape: "spine" } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| brief_spine: data2.payload?.brief_spine ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId, | ||
| last_updated: null | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, shape: "spine" } }); | ||
| return data; | ||
| } | ||
| async getActiveContextByCwd(cwd, tool = "claude_code", verbosity = "standard", markSession = false, workspaceKey = null) { | ||
| const params = { cwd, tool, verbosity }; | ||
| if (markSession) params.mark_session = 1; | ||
| if (workspaceKey) params.workspace_key = workspaceKey; | ||
| const { data } = await this.client.get("/api/v1/context/active", { params }); | ||
| return data; | ||
| } | ||
| async pushContextUpdate(projectId, summary, tool = "claude_code", commands) { | ||
| const sessionId = `mcp-${Date.now()}`; | ||
| const [extractionResult] = await Promise.all([ | ||
| this.client.post("/api/v1/context/extraction", { | ||
| session_id: sessionId, | ||
| tool, | ||
| project_id: projectId, | ||
| chunks: [{ role: "assistant", text: summary }] | ||
| }).then((r) => r.data), | ||
| commands?.length ? this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, { commands }).catch((err) => { | ||
| process.stderr.write(`[DeerDawn] commands not saved: ${err?.message ?? "unknown"} | ||
| `); | ||
| return null; | ||
| }) : Promise.resolve(null) | ||
| ]); | ||
| return extractionResult; | ||
| } | ||
| async listContextProjects() { | ||
| const { data } = await this.client.get("/api/v1/context/projects"); | ||
| return data; | ||
| } | ||
| async listContextProjectTree() { | ||
| const { data } = await this.client.get("/api/v1/context/projects/tree"); | ||
| return data; | ||
| } | ||
| async createContextProject(projectName, description, parentProjectId) { | ||
| const { data } = await this.client.post("/api/v1/context/projects", { | ||
| project_name: projectName, | ||
| description, | ||
| ...parentProjectId ? { parent_project_id: parentProjectId } : {} | ||
| }); | ||
| return data; | ||
| } | ||
| async switchSubproject(projectId, workspaceKey) { | ||
| const { data } = await this.client.post("/api/v1/context/subproject/switch", { | ||
| project_id: projectId, | ||
| workspace_key: workspaceKey | ||
| }); | ||
| return data; | ||
| } | ||
| async archiveProject(projectId, unarchive = false) { | ||
| const { data } = await this.client.post( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/archive`, | ||
| { unarchive } | ||
| ); | ||
| return data; | ||
| } | ||
| async setProjectParent(projectId, parentProjectId) { | ||
| const { data } = await this.client.post( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/parent`, | ||
| { parent_project_id: parentProjectId ?? null } | ||
| ); | ||
| return data; | ||
| } | ||
| async renameProject(projectId, projectName) { | ||
| const { data } = await this.client.post( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/rename`, | ||
| { project_name: projectName } | ||
| ); | ||
| return data; | ||
| } | ||
| async getContextSections(projectId, sections, tool = "claude_code", verbosity = "standard") { | ||
| const sectionsParam = sections.join(","); | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity, sections: sectionsParam } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, sections: sectionsParam } }); | ||
| return data; | ||
| } | ||
| async getContextMap(mcpAvailable = true, cwd, workspaceKey) { | ||
| const params = { mcp: mcpAvailable ? "true" : "false" }; | ||
| if (cwd) params.cwd = cwd; | ||
| if (workspaceKey) params.workspace_key = workspaceKey; | ||
| const { data } = await this.client.get("/api/v1/context/map", { params }); | ||
| return data; | ||
| } | ||
| async getContextJson(projectId) { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { format: "json" } } | ||
| ); | ||
| return data2.context ?? data2; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { format: "json" } }); | ||
| return data; | ||
| } | ||
| async searchContext(query, limit = 5) { | ||
| const { data } = await this.client.post("/api/v1/context/search", { query, limit }); | ||
| return data; | ||
| } | ||
| async getWorkspaceOverview(params = {}) { | ||
| const query = {}; | ||
| if (params.mode) query.mode = params.mode; | ||
| if (params.verbosity) query.verbosity = params.verbosity; | ||
| if (params.include?.length) query.include = params.include.join(","); | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/overview", { params: query }); | ||
| return data.overview || data; | ||
| } | ||
| async getWorkspaceEntities(params = {}) { | ||
| const query = {}; | ||
| if (params.types?.length) query.types = params.types.join(","); | ||
| if (params.query) query.query = params.query; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (params.related_project_id) query.related_project_id = params.related_project_id; | ||
| if (params.scope) query.scope = params.scope; | ||
| if (params.surface) query.surface = params.surface; | ||
| if (params.status) query.status = params.status; | ||
| if (typeof params.include_inferred === "boolean") query.include_inferred = params.include_inferred ? 1 : 0; | ||
| if (typeof params.include_resolved === "boolean") query.include_resolved = params.include_resolved ? 1 : 0; | ||
| if (typeof params.curated_only === "boolean") query.curated_only = params.curated_only ? 1 : 0; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/entities", { params: query }); | ||
| return data; | ||
| } | ||
| async getProjectMap(params = {}) { | ||
| const projectId = params.project_id; | ||
| if (!projectId) { | ||
| const { data: data2 } = await this.client.get("/api/v1/context/active"); | ||
| if (!data2?.project_id) return null; | ||
| params.project_id = data2.project_id; | ||
| } | ||
| const query = {}; | ||
| if (params.verbosity) query.verbosity = params.verbosity; | ||
| if (params.format) query.format = params.format; | ||
| const resolvedProjectId = String(params.project_id); | ||
| const { data } = await this.client.get(`/api/v1/context/projects/${encodeURIComponent(resolvedProjectId)}/map`, { params: query }); | ||
| return data.project_map || data; | ||
| } | ||
| async findKnownPaths(params) { | ||
| const query = { query: params.query }; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/known-paths", { params: query }); | ||
| return data; | ||
| } | ||
| async getNextBestContext(params = {}) { | ||
| const query = {}; | ||
| if (params.tool) query.tool = params.tool; | ||
| if (params.cwd) query.cwd = params.cwd; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/next", { params: query }); | ||
| return data.result || data; | ||
| } | ||
| async upsertWorkspaceEntities(input) { | ||
| const { data } = await this.client.post("/api/v1/context/workspace/entities", input); | ||
| return data; | ||
| } | ||
| async recordHotPaths(projectId, items) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/hot-paths`, { items }); | ||
| return data; | ||
| } | ||
| async recordDebugFinding(projectId, input) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/debug-findings`, input); | ||
| return data; | ||
| } | ||
| async confirmClaim(projectId, input) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/verify`, input); | ||
| return data; | ||
| } | ||
| async updateGlobalContext(patch) { | ||
| const { data } = await this.client.patch("/api/v1/context/global", patch); | ||
| return data; | ||
| } | ||
| async patchProjectContext(projectId, delta) { | ||
| const { data } = await this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, delta); | ||
| return data; | ||
| } | ||
| // ── Shared skills ────────────────────────────────────────────────────────── | ||
| async listSkills(projectId) { | ||
| const params = {}; | ||
| if (projectId) params.project_id = projectId; | ||
| const { data } = await this.client.get("/api/v1/context/skills", { params }); | ||
| return data?.skills ?? []; | ||
| } | ||
| async getSkill(idOrSlug) { | ||
| const { data } = await this.client.get(`/api/v1/context/skills/${encodeURIComponent(idOrSlug)}`); | ||
| return data?.skill ?? null; | ||
| } | ||
| async upsertSkill(input) { | ||
| const { data } = await this.client.post("/api/v1/context/skills", input); | ||
| return data?.skill ?? data; | ||
| } | ||
| }; | ||
| export { | ||
| __require, | ||
| __commonJS, | ||
| __toESM, | ||
| init_esm_shims, | ||
| DeerdawnClient | ||
| }; |
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| import { | ||
| DeerdawnClient | ||
| } from "./chunk-ZXMJDM3M.js"; | ||
| export { | ||
| DeerdawnClient | ||
| }; |
+1
-1
| { | ||
| "name": "@deerdawn/mcp-server", | ||
| "mcpName": "com.deerdawn/deerdawn", | ||
| "version": "1.0.34", | ||
| "version": "1.0.35", | ||
| "description": "MCP server for DeerDawn — AI session memory that briefs every new AI session", | ||
@@ -6,0 +6,0 @@ "type": "module", |
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { | ||
| get: (a, b) => (typeof require !== "undefined" ? require : a)[b] | ||
| }) : x)(function(x) { | ||
| if (typeof require !== "undefined") return require.apply(this, arguments); | ||
| throw Error('Dynamic require of "' + x + '" is not supported'); | ||
| }); | ||
| var __esm = (fn, res) => function __init() { | ||
| return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; | ||
| }; | ||
| var __commonJS = (cb, mod) => function __require2() { | ||
| return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( | ||
| // If the importer is in node compatibility mode or this is not an ESM | ||
| // file that has been converted to a CommonJS file using a Babel- | ||
| // compatible transform (i.e. "__esModule" has not been set), then set | ||
| // "default" to the CommonJS "module.exports" for node compatibility. | ||
| isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, | ||
| mod | ||
| )); | ||
| // node_modules/tsup/assets/esm_shims.js | ||
| import path from "path"; | ||
| import { fileURLToPath } from "url"; | ||
| var init_esm_shims = __esm({ | ||
| "node_modules/tsup/assets/esm_shims.js"() { | ||
| "use strict"; | ||
| } | ||
| }); | ||
| // src/client.ts | ||
| init_esm_shims(); | ||
| import axios from "axios"; | ||
| import fs from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| function isLocalApiUrl(apiUrl) { | ||
| try { | ||
| const host = new URL(apiUrl).hostname; | ||
| return host === "localhost" || host === "127.0.0.1" || host === "::1"; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function getDeerdawnHome() { | ||
| return process.env.DEERDAWN_HOME || path2.join(os.homedir(), ".deerdawn"); | ||
| } | ||
| function getQueuePath() { | ||
| return path2.join(getDeerdawnHome(), "cloud-write-queue.json"); | ||
| } | ||
| function ensureDeerdawnHome() { | ||
| fs.mkdirSync(getDeerdawnHome(), { recursive: true }); | ||
| } | ||
| function readQueueState() { | ||
| try { | ||
| return JSON.parse(fs.readFileSync(getQueuePath(), "utf8")); | ||
| } catch { | ||
| return { entries: [] }; | ||
| } | ||
| } | ||
| function writeQueueState(state) { | ||
| ensureDeerdawnHome(); | ||
| fs.writeFileSync(getQueuePath(), JSON.stringify(state, null, 2)); | ||
| } | ||
| function mapOrgUsageToSettings(data) { | ||
| const plan = data?.plan || {}; | ||
| return { | ||
| org_id: plan.org_id || "", | ||
| name: data?.org_name || "", | ||
| plan: typeof plan.tier === "string" ? plan.tier : "free", | ||
| fail_mode: plan.fail_mode === "open" ? "open" : "closed", | ||
| redaction_mode: plan.redaction_mode === "permissive" ? "permissive" : "strict", | ||
| is_locked: !!plan.is_locked, | ||
| shadow_mode: !!plan.shadow_mode, | ||
| redaction_keys: Array.isArray(plan.redaction_keys) ? plan.redaction_keys : [], | ||
| created_at: (/* @__PURE__ */ new Date()).toISOString(), | ||
| usage: data?.usage, | ||
| limits: data?.limits | ||
| }; | ||
| } | ||
| var DeerdawnClient = class { | ||
| client; | ||
| cache; | ||
| config; | ||
| MAX_CACHE_SIZE = 100; | ||
| MAX_CACHE_ITEM_SIZE_MB = 5; | ||
| constructor(config) { | ||
| if (!config.apiKey || config.apiKey.trim() !== config.apiKey) { | ||
| throw new Error("Invalid API key: cannot be empty or contain whitespace"); | ||
| } | ||
| if (!isLocalApiUrl(config.apiUrl) && !config.apiKey.startsWith("dd_")) { | ||
| throw new Error("Invalid API key format: must start with dd_ prefix"); | ||
| } | ||
| this.config = config; | ||
| this.cache = /* @__PURE__ */ new Map(); | ||
| this.client = axios.create({ | ||
| baseURL: config.apiUrl, | ||
| headers: { | ||
| "x-api-key": config.apiKey, | ||
| "content-type": "application/json" | ||
| }, | ||
| timeout: 3e4 | ||
| }); | ||
| this.client.interceptors.response.use( | ||
| (response) => response, | ||
| async (error) => { | ||
| const cfg = error.config; | ||
| const response = error.response; | ||
| if (!cfg) { | ||
| throw error; | ||
| } | ||
| if (!cfg.__retryCount) { | ||
| cfg.__retryCount = 0; | ||
| cfg.__firstRetryTimestamp = Date.now(); | ||
| } | ||
| const totalRetryTime = Date.now() - (cfg.__firstRetryTimestamp || Date.now()); | ||
| if (totalRetryTime > 6e4) { | ||
| throw new Error("Retry timeout exceeded (60s)"); | ||
| } | ||
| const shouldRetry = (response?.status === 429 || response?.status && response.status >= 500) && cfg.__retryCount < this.config.maxRetries; | ||
| if (shouldRetry) { | ||
| cfg.__retryCount++; | ||
| const delay = Math.min( | ||
| this.config.retryBackoffMs * Math.pow(2, cfg.__retryCount - 1), | ||
| 3e4 | ||
| ); | ||
| await new Promise((resolve) => setTimeout(resolve, delay)); | ||
| return this.client.request(cfg); | ||
| } | ||
| throw error; | ||
| } | ||
| ); | ||
| } | ||
| async recordProductEvent(event) { | ||
| const { data } = await this.client.post("/api/v1/product-events", event); | ||
| return data; | ||
| } | ||
| async recordContextEvent(event) { | ||
| try { | ||
| const { data } = await this.client.post("/api/v1/context/events", event); | ||
| return data; | ||
| } catch (error) { | ||
| const status = error?.response?.status; | ||
| const shouldQueue = !status || status >= 500 || error?.code === "ECONNREFUSED" || error?.code === "ENOTFOUND"; | ||
| if (!shouldQueue) throw error; | ||
| const state = readQueueState(); | ||
| state.entries.push({ | ||
| id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, | ||
| method: "POST", | ||
| path: "/api/v1/context/events", | ||
| body: event, | ||
| created_at: (/* @__PURE__ */ new Date()).toISOString() | ||
| }); | ||
| writeQueueState(state); | ||
| return { queued: true, queue_size: state.entries.length, event_type: event.event_type }; | ||
| } | ||
| } | ||
| async flushQueuedContextWrites() { | ||
| const state = readQueueState(); | ||
| if (!state.entries.length) return { flushed: 0, remaining: 0 }; | ||
| const remaining = []; | ||
| let flushed = 0; | ||
| for (const entry of state.entries) { | ||
| try { | ||
| await this.client.post(entry.path, entry.body); | ||
| flushed += 1; | ||
| } catch { | ||
| remaining.push(entry); | ||
| } | ||
| } | ||
| writeQueueState({ entries: remaining }); | ||
| return { flushed, remaining: remaining.length }; | ||
| } | ||
| async getOrganizationSettings() { | ||
| const { data } = await this.client.get("/api/v1/orgs/usage"); | ||
| return mapOrgUsageToSettings(data); | ||
| } | ||
| async getContextGraphProject(projectId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/projects/${encodeURIComponent(projectId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async getContextGraphSession(sessionId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/sessions/${encodeURIComponent(sessionId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async getContextGraphNode(nodeId) { | ||
| const { data } = await this.client.get(`/api/v1/context/graph/nodes/${encodeURIComponent(nodeId)}`); | ||
| return data.graph || data; | ||
| } | ||
| async queryContextGraph(input) { | ||
| const { data } = await this.client.post("/api/v1/context/graph/query", input); | ||
| return data.graph || data; | ||
| } | ||
| async updateOrganizationSettings(settings) { | ||
| const { data } = await this.client.patch("/api/v1/orgs/settings", settings); | ||
| return data; | ||
| } | ||
| async getAuditLogs(_params = {}) { | ||
| const { data } = await this.client.get("/api/v1/audit"); | ||
| const logs = data.logs || []; | ||
| return { logs, total: logs.length }; | ||
| } | ||
| async getActionRegistry() { | ||
| const cacheKey = "registry:actions"; | ||
| const cached = this.getFromCache(cacheKey); | ||
| if (cached) return cached; | ||
| try { | ||
| const { data } = await this.client.get("/api/v1/registry/actions"); | ||
| const actions = data.actions || data; | ||
| if (Array.isArray(actions) && actions.length > 0) { | ||
| this.setInCache(cacheKey, actions, 3e5); | ||
| return actions; | ||
| } | ||
| } catch { | ||
| } | ||
| return this.getDefaultActionRegistry(); | ||
| } | ||
| getDefaultActionRegistry() { | ||
| return [ | ||
| { | ||
| action_type: "auth.login", | ||
| category: "authentication", | ||
| description: "User login attempt", | ||
| risk_level: "medium" | ||
| }, | ||
| { | ||
| action_type: "auth.password_reset", | ||
| category: "authentication", | ||
| description: "Password reset request", | ||
| risk_level: "medium" | ||
| }, | ||
| { | ||
| action_type: "payment.process", | ||
| category: "financial", | ||
| description: "Payment processing", | ||
| risk_level: "high" | ||
| }, | ||
| { | ||
| action_type: "user.delete", | ||
| category: "data_management", | ||
| description: "User account deletion", | ||
| risk_level: "high" | ||
| }, | ||
| { | ||
| action_type: "data.export", | ||
| category: "data_management", | ||
| description: "Data export request", | ||
| risk_level: "medium" | ||
| } | ||
| ]; | ||
| } | ||
| getFromCache(key) { | ||
| if (!this.config.cacheEnabled) return null; | ||
| const entry = this.cache.get(key); | ||
| if (!entry) return null; | ||
| if (Date.now() > entry.expires) { | ||
| this.cache.delete(key); | ||
| return null; | ||
| } | ||
| return entry.data; | ||
| } | ||
| setInCache(key, data, ttlMs) { | ||
| if (!this.config.cacheEnabled) return; | ||
| const estimatedSize = JSON.stringify(data).length; | ||
| const estimatedSizeMB = estimatedSize / (1024 * 1024); | ||
| if (estimatedSizeMB > this.MAX_CACHE_ITEM_SIZE_MB) { | ||
| console.error(`[CACHE] Skipping large object (${estimatedSizeMB.toFixed(2)}MB)`); | ||
| return; | ||
| } | ||
| if (this.cache.size >= this.MAX_CACHE_SIZE) { | ||
| const firstKey = this.cache.keys().next().value; | ||
| if (firstKey) { | ||
| this.cache.delete(firstKey); | ||
| } | ||
| } | ||
| this.cache.set(key, { | ||
| data, | ||
| expires: Date.now() + ttlMs | ||
| }); | ||
| } | ||
| invalidateCache(prefix) { | ||
| for (const key of this.cache.keys()) { | ||
| if (key.startsWith(prefix)) { | ||
| this.cache.delete(key); | ||
| } | ||
| } | ||
| } | ||
| // Context sync methods | ||
| async getActiveContext(projectId, tool = "claude_code", verbosity = "standard") { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId, | ||
| last_updated: null | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity } }); | ||
| return data; | ||
| } | ||
| // Brief shape (?shape=spine) — the product-voice 4-slot view (SHIPPED / DECIDED | ||
| // / OPEN / LANDMINE). Returns the 4-slot TEXT body plus the structured | ||
| // brief_spine (per-item trust). Opt-in: never changes the default brief text. | ||
| async getActiveBrief(projectId, tool = "claude_code", verbosity = "standard") { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity, shape: "spine" } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| brief_spine: data2.payload?.brief_spine ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId, | ||
| last_updated: null | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, shape: "spine" } }); | ||
| return data; | ||
| } | ||
| async getActiveContextByCwd(cwd, tool = "claude_code", verbosity = "standard", markSession = false, workspaceKey = null) { | ||
| const params = { cwd, tool, verbosity }; | ||
| if (markSession) params.mark_session = 1; | ||
| if (workspaceKey) params.workspace_key = workspaceKey; | ||
| const { data } = await this.client.get("/api/v1/context/active", { params }); | ||
| return data; | ||
| } | ||
| async pushContextUpdate(projectId, summary, tool = "claude_code", commands) { | ||
| const sessionId = `mcp-${Date.now()}`; | ||
| const [extractionResult] = await Promise.all([ | ||
| this.client.post("/api/v1/context/extraction", { | ||
| session_id: sessionId, | ||
| tool, | ||
| project_id: projectId, | ||
| chunks: [{ role: "assistant", text: summary }] | ||
| }).then((r) => r.data), | ||
| commands?.length ? this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, { commands }).catch((err) => { | ||
| process.stderr.write(`[DeerDawn] commands not saved: ${err?.message ?? "unknown"} | ||
| `); | ||
| return null; | ||
| }) : Promise.resolve(null) | ||
| ]); | ||
| return extractionResult; | ||
| } | ||
| async listContextProjects() { | ||
| const { data } = await this.client.get("/api/v1/context/projects"); | ||
| return data; | ||
| } | ||
| async createContextProject(projectName, description) { | ||
| const { data } = await this.client.post("/api/v1/context/projects", { | ||
| project_name: projectName, | ||
| description | ||
| }); | ||
| return data; | ||
| } | ||
| async getContextSections(projectId, sections, tool = "claude_code", verbosity = "standard") { | ||
| const sectionsParam = sections.join(","); | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { tool, verbosity, sections: sectionsParam } } | ||
| ); | ||
| return { | ||
| brief: data2.payload?.body ?? null, | ||
| project_name: projectId, | ||
| project_id: projectId | ||
| }; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { tool, verbosity, sections: sectionsParam } }); | ||
| return data; | ||
| } | ||
| async getContextMap(mcpAvailable = true, cwd, workspaceKey) { | ||
| const params = { mcp: mcpAvailable ? "true" : "false" }; | ||
| if (cwd) params.cwd = cwd; | ||
| if (workspaceKey) params.workspace_key = workspaceKey; | ||
| const { data } = await this.client.get("/api/v1/context/map", { params }); | ||
| return data; | ||
| } | ||
| async getContextJson(projectId) { | ||
| if (projectId) { | ||
| const { data: data2 } = await this.client.get( | ||
| `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject`, | ||
| { params: { format: "json" } } | ||
| ); | ||
| return data2.context ?? data2; | ||
| } | ||
| const { data } = await this.client.get("/api/v1/context/active", { params: { format: "json" } }); | ||
| return data; | ||
| } | ||
| async searchContext(query, limit = 5) { | ||
| const { data } = await this.client.post("/api/v1/context/search", { query, limit }); | ||
| return data; | ||
| } | ||
| async getWorkspaceOverview(params = {}) { | ||
| const query = {}; | ||
| if (params.mode) query.mode = params.mode; | ||
| if (params.verbosity) query.verbosity = params.verbosity; | ||
| if (params.include?.length) query.include = params.include.join(","); | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/overview", { params: query }); | ||
| return data.overview || data; | ||
| } | ||
| async getWorkspaceEntities(params = {}) { | ||
| const query = {}; | ||
| if (params.types?.length) query.types = params.types.join(","); | ||
| if (params.query) query.query = params.query; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (params.related_project_id) query.related_project_id = params.related_project_id; | ||
| if (params.scope) query.scope = params.scope; | ||
| if (params.surface) query.surface = params.surface; | ||
| if (params.status) query.status = params.status; | ||
| if (typeof params.include_inferred === "boolean") query.include_inferred = params.include_inferred ? 1 : 0; | ||
| if (typeof params.include_resolved === "boolean") query.include_resolved = params.include_resolved ? 1 : 0; | ||
| if (typeof params.curated_only === "boolean") query.curated_only = params.curated_only ? 1 : 0; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/entities", { params: query }); | ||
| return data; | ||
| } | ||
| async getProjectMap(params = {}) { | ||
| const projectId = params.project_id; | ||
| if (!projectId) { | ||
| const { data: data2 } = await this.client.get("/api/v1/context/active"); | ||
| if (!data2?.project_id) return null; | ||
| params.project_id = data2.project_id; | ||
| } | ||
| const query = {}; | ||
| if (params.verbosity) query.verbosity = params.verbosity; | ||
| if (params.format) query.format = params.format; | ||
| const resolvedProjectId = String(params.project_id); | ||
| const { data } = await this.client.get(`/api/v1/context/projects/${encodeURIComponent(resolvedProjectId)}/map`, { params: query }); | ||
| return data.project_map || data; | ||
| } | ||
| async findKnownPaths(params) { | ||
| const query = { query: params.query }; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| if (params.format) query.format = params.format; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/known-paths", { params: query }); | ||
| return data; | ||
| } | ||
| async getNextBestContext(params = {}) { | ||
| const query = {}; | ||
| if (params.tool) query.tool = params.tool; | ||
| if (params.cwd) query.cwd = params.cwd; | ||
| if (params.project_id) query.project_id = params.project_id; | ||
| if (typeof params.limit === "number") query.limit = params.limit; | ||
| const { data } = await this.client.get("/api/v1/context/workspace/next", { params: query }); | ||
| return data.result || data; | ||
| } | ||
| async upsertWorkspaceEntities(input) { | ||
| const { data } = await this.client.post("/api/v1/context/workspace/entities", input); | ||
| return data; | ||
| } | ||
| async recordHotPaths(projectId, items) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/hot-paths`, { items }); | ||
| return data; | ||
| } | ||
| async recordDebugFinding(projectId, input) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/debug-findings`, input); | ||
| return data; | ||
| } | ||
| async confirmClaim(projectId, input) { | ||
| const { data } = await this.client.post(`/api/v1/context/projects/${encodeURIComponent(projectId)}/verify`, input); | ||
| return data; | ||
| } | ||
| async updateGlobalContext(patch) { | ||
| const { data } = await this.client.patch("/api/v1/context/global", patch); | ||
| return data; | ||
| } | ||
| async patchProjectContext(projectId, delta) { | ||
| const { data } = await this.client.patch(`/api/v1/context/${encodeURIComponent(projectId)}/context`, delta); | ||
| return data; | ||
| } | ||
| // ── Shared skills ────────────────────────────────────────────────────────── | ||
| async listSkills(projectId) { | ||
| const params = {}; | ||
| if (projectId) params.project_id = projectId; | ||
| const { data } = await this.client.get("/api/v1/context/skills", { params }); | ||
| return data?.skills ?? []; | ||
| } | ||
| async getSkill(idOrSlug) { | ||
| const { data } = await this.client.get(`/api/v1/context/skills/${encodeURIComponent(idOrSlug)}`); | ||
| return data?.skill ?? null; | ||
| } | ||
| async upsertSkill(input) { | ||
| const { data } = await this.client.post("/api/v1/context/skills", input); | ||
| return data?.skill ?? data; | ||
| } | ||
| }; | ||
| export { | ||
| __require, | ||
| __commonJS, | ||
| __toESM, | ||
| init_esm_shims, | ||
| DeerdawnClient | ||
| }; |
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| import { | ||
| DeerdawnClient | ||
| } from "./chunk-SWBO3RSY.js"; | ||
| export { | ||
| DeerdawnClient | ||
| }; |
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
343480
5.73%7368
5.02%90
-1.1%