@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 http from "http"; | ||
| import https from "https"; | ||
| import fs2 from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| // src/utils/safe-fs.ts | ||
| init_esm_shims(); | ||
| import fs from "fs"; | ||
| function isUnsafeSymlink(filePath) { | ||
| try { | ||
| return fs.lstatSync(filePath).isSymbolicLink(); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function safeWriteFileSync(filePath, data, options) { | ||
| if (isUnsafeSymlink(filePath)) { | ||
| console.error(`[DeerDawn] Refusing to write through symlink at ${filePath} \u2014 remove it manually if this is expected.`); | ||
| return false; | ||
| } | ||
| fs.writeFileSync(filePath, data, options); | ||
| return true; | ||
| } | ||
| // src/client.ts | ||
| 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() { | ||
| fs2.mkdirSync(getDeerdawnHome(), { recursive: true }); | ||
| } | ||
| function readQueueState() { | ||
| try { | ||
| return JSON.parse(fs2.readFileSync(getQueuePath(), "utf8")); | ||
| } catch { | ||
| return { entries: [] }; | ||
| } | ||
| } | ||
| function writeQueueState(state) { | ||
| ensureDeerdawnHome(); | ||
| safeWriteFileSync(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, | ||
| // Reuse TCP+TLS connections across calls. Without this, axios opens a | ||
| // fresh socket per request and pays a full handshake (~50ms) every time — | ||
| // multiplied across the several sequential calls a single tool can make. | ||
| httpAgent: new http.Agent({ keepAlive: true, maxSockets: 16 }), | ||
| httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 16 }) | ||
| }); | ||
| 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; | ||
| } | ||
| /** | ||
| * The project's raw structured context — ALWAYS the flat context object | ||
| * (`{project_id, project_name, updated_at, ...context}`), whichever endpoint | ||
| * served it. | ||
| * | ||
| * These two branches used to return different shapes: the projectId path | ||
| * unwrapped `data.context`, while the no-projectId path returned the whole | ||
| * `/active` envelope, which has no `context` key at all. Callers therefore had | ||
| * to guess, and `manage_todos` guessed wrong in the one direction that fails | ||
| * silently — reading `ctx.context.todo_items` off a shape where `.context` is | ||
| * undefined yields no error, just a permanently empty board. | ||
| * | ||
| * One shape, decided here, so no caller has to branch on it again. | ||
| */ | ||
| async getContextJson(projectId) { | ||
| const path3 = projectId ? `/api/v1/context/projects/${encodeURIComponent(projectId)}/inject` : "/api/v1/context/active"; | ||
| const { data } = await this.client.get(path3, { params: { format: "json" } }); | ||
| if (data && typeof data === "object" && "context" in data) return data.context ?? {}; | ||
| 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 buildSubagentBrief(params) { | ||
| const { data } = await this.client.post("/api/v1/context/subagent-brief", params); | ||
| return data.brief || 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; | ||
| } | ||
| // Import a shared skill by pasting a GitHub repo URL. The API fetches the | ||
| // repo's SKILL.md, parses its frontmatter, and creates the shared skill. | ||
| async importSkillFromGithub(repoUrl, projectId) { | ||
| const body = { repo_url: repoUrl }; | ||
| if (projectId) body.project_id = projectId; | ||
| const { data } = await this.client.post("/api/v1/context/skills/import-github", body); | ||
| return data; | ||
| } | ||
| }; | ||
| export { | ||
| __require, | ||
| __commonJS, | ||
| __toESM, | ||
| init_esm_shims, | ||
| safeWriteFileSync, | ||
| DeerdawnClient | ||
| }; |
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| import { | ||
| DeerdawnClient | ||
| } from "./chunk-AGVTTUZL.js"; | ||
| export { | ||
| DeerdawnClient | ||
| }; |
+1
-1
| { | ||
| "name": "@deerdawn/mcp-server", | ||
| "mcpName": "com.deerdawn/deerdawn", | ||
| "version": "1.0.39", | ||
| "version": "1.0.40", | ||
| "description": "MCP server for DeerDawn — AI session memory that briefs every new AI session", | ||
@@ -6,0 +6,0 @@ "homepage": "https://deerdawn.com", |
| #!/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 http from "http"; | ||
| import https from "https"; | ||
| import fs2 from "fs"; | ||
| import path2 from "path"; | ||
| import os from "os"; | ||
| // src/utils/safe-fs.ts | ||
| init_esm_shims(); | ||
| import fs from "fs"; | ||
| function isUnsafeSymlink(filePath) { | ||
| try { | ||
| return fs.lstatSync(filePath).isSymbolicLink(); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| function safeWriteFileSync(filePath, data, options) { | ||
| if (isUnsafeSymlink(filePath)) { | ||
| console.error(`[DeerDawn] Refusing to write through symlink at ${filePath} \u2014 remove it manually if this is expected.`); | ||
| return false; | ||
| } | ||
| fs.writeFileSync(filePath, data, options); | ||
| return true; | ||
| } | ||
| // src/client.ts | ||
| 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() { | ||
| fs2.mkdirSync(getDeerdawnHome(), { recursive: true }); | ||
| } | ||
| function readQueueState() { | ||
| try { | ||
| return JSON.parse(fs2.readFileSync(getQueuePath(), "utf8")); | ||
| } catch { | ||
| return { entries: [] }; | ||
| } | ||
| } | ||
| function writeQueueState(state) { | ||
| ensureDeerdawnHome(); | ||
| safeWriteFileSync(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, | ||
| // Reuse TCP+TLS connections across calls. Without this, axios opens a | ||
| // fresh socket per request and pays a full handshake (~50ms) every time — | ||
| // multiplied across the several sequential calls a single tool can make. | ||
| httpAgent: new http.Agent({ keepAlive: true, maxSockets: 16 }), | ||
| httpsAgent: new https.Agent({ keepAlive: true, maxSockets: 16 }) | ||
| }); | ||
| 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 buildSubagentBrief(params) { | ||
| const { data } = await this.client.post("/api/v1/context/subagent-brief", params); | ||
| return data.brief || 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; | ||
| } | ||
| // Import a shared skill by pasting a GitHub repo URL. The API fetches the | ||
| // repo's SKILL.md, parses its frontmatter, and creates the shared skill. | ||
| async importSkillFromGithub(repoUrl, projectId) { | ||
| const body = { repo_url: repoUrl }; | ||
| if (projectId) body.project_id = projectId; | ||
| const { data } = await this.client.post("/api/v1/context/skills/import-github", body); | ||
| return data; | ||
| } | ||
| }; | ||
| export { | ||
| __require, | ||
| __commonJS, | ||
| __toESM, | ||
| init_esm_shims, | ||
| safeWriteFileSync, | ||
| DeerdawnClient | ||
| }; |
| #!/usr/bin/env node | ||
| import { createRequire } from "module"; | ||
| const require = createRequire(import.meta.url); | ||
| import { | ||
| DeerdawnClient | ||
| } from "./chunk-P2F6BVEV.js"; | ||
| export { | ||
| DeerdawnClient | ||
| }; |
Sorry, the diff of this file is too big to display
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.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
390725
1.05%8214
1.29%95
-1.04%