UNPKG

deco-cli

Version:

CLI for managing decocms.com apps & projects

387 lines (384 loc) 11.8 kB
import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; import process3 from 'process'; import { dirname, join } from 'path'; import { homedir } from 'os'; import { promises, statSync } from 'fs'; import { decodeJwt } from 'jose'; import { z } from 'zod'; import { parseCookieHeader, createServerClient, serializeCookieHeader } from '@supabase/ssr'; import { parse, stringify } from 'smol-toml'; import { fileURLToPath } from 'url'; import { createHash } from 'crypto'; // src/lib/mcp.ts var LOCAL_DEBUGGER = process3.env.VITE_USE_LOCAL_BACKEND === "true"; var SUPABASE_URL = "https://auth.deco.cx"; var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im96a3NnZG15cnFjeGN3aG5iZXBnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTY3NzU3MDEsImV4cCI6MjA3MjM1MTcwMX0.X1SIxXbivIa2dEkWGfn6xigoHCms9Kri9SLu8N-VWck"; var DECO_CMS_WEB_URL = LOCAL_DEBUGGER ? "http://localhost:3000" : "https://admin.decocms.com"; var DECO_CMS_API_PROD = "https://api.decocms.com"; var DECO_CMS_API_LOCAL = "http://localhost:3001"; var AUTH_PORT_CLI = 3457; var DECO_CMS_LOGIN_URL = new URL("/login?cli", DECO_CMS_WEB_URL).href; function createClient(requestHeaders = new Headers()) { const cookies = parseCookieHeader(requestHeaders.get("cookie") ?? ""); const filteredCookies = cookies.filter( (cookie) => !!cookie.value ); const responseHeaders = new Headers(); const client = createServerClient(SUPABASE_URL, SUPABASE_ANON_KEY, { cookies: { getAll: () => filteredCookies, setAll(cookies2) { cookies2.forEach((cookie) => { responseHeaders.append( "Set-Cookie", serializeCookieHeader(cookie.name, cookie.value, cookie.options) ); }); } } }); return { client, responseHeaders }; } var SessionSchema = z.object({ access_token: z.string().optional(), refresh_token: z.string().optional(), workspace: z.string().optional(), api_token: z.string().optional() }); var token; function setToken(t) { token = t; } function getToken() { return token; } function getSessionPath() { return join(homedir(), ".deco_auth_session.json"); } async function saveSession(data) { const { session, user } = data; const sessionPath = getSessionPath(); await promises.writeFile( sessionPath, JSON.stringify( { ...session, workspace: user ? `/users/${user.id}` : void 0 }, null, 2 ) ); if (process3.platform !== "win32") { try { await promises.chmod(sessionPath, 384); } catch (error) { console.warn( "Warning: Could not set file permissions on session file:", error instanceof Error ? error.message : String(error) ); } } } async function readSession() { const token2 = getToken(); if (token2) { return { workspace: decodeJwt(token2).aud, api_token: token2 }; } try { const sessionPath = getSessionPath(); const content = await promises.readFile(sessionPath, "utf-8"); return SessionSchema.safeParse(JSON.parse(content)).data ?? null; } catch { return null; } } async function deleteSession() { const sessionPath = getSessionPath(); const { client } = createClient(); try { await promises.unlink(sessionPath); } catch { console.warn("Session file not found"); } await client.auth.signOut(); } async function getRequestAuthHeaders() { const session = await readSession(); if (session?.api_token) { return { Authorization: `Bearer ${session.api_token}` }; } if (!session) { throw new Error("Session not found. Please login again."); } const { access_token, refresh_token } = session; if (!access_token || !refresh_token) { throw new Error("Session expired. Please login again."); } const { client: supabase, responseHeaders } = createClient(); const { data, error } = await supabase.auth.setSession({ access_token, refresh_token }); if (error) { throw new Error("Session expired. Please login again."); } await saveSession(data); const setCookie = responseHeaders.getSetCookie(); if (!setCookie.length) { throw new Error("Session expired. Please login again."); } const cookies = setCookie.map((cookie) => cookie.split(";")[0]).join("; "); return { cookie: cookies }; } var __filename2 = fileURLToPath(import.meta.url); dirname(__filename2); function md5Hash(input) { const hash = createHash("sha1"); hash.update(input); return hash.digest("hex"); } var CONFIG_FILE = "wrangler.toml"; var requiredErrorForProp = (prop) => `Property ${prop} is required. Please provide an inline value using --${prop} or configure it using 'deco configure'.`; var DecoBindingSchema = z.union([ z.object({ name: z.string().min(1), type: z.string().min(1), integration_id: z.string().min(1) }), z.object({ name: z.string().min(1), type: z.string().min(1), integration_name: z.string().min(1) }), z.object({ name: z.string().min(1), type: z.literal("contract"), contract: z.object({ body: z.string().min(1), clauses: z.array( z.object({ id: z.string().min(1), price: z.union([z.string(), z.number()]), description: z.string().optional() }) ).min(1) }) }) ]); var decoConfigSchema = z.object({ workspace: z.string({ required_error: requiredErrorForProp("workspace") }), bindings: z.array(DecoBindingSchema).optional().default([]), local: z.boolean().optional().default(false), enable_workflows: z.boolean().optional().default(true) }); var local; var setLocal = (l) => { local = l; }; var getLocal = () => { return local; }; var readWranglerConfig = async (cwd) => { const configPath = getConfigFilePath(cwd || process3.cwd()); if (!configPath) { return {}; } try { const config = await promises.readFile(configPath, "utf-8"); return parse(config); } catch { return {}; } }; var readConfigFile = async (cwd) => { const wranglerConfig = await readWranglerConfig(cwd); const decoConfig = wranglerConfig.deco ?? {}; return decoConfig; }; var addSchemaNotation = (stringified) => { return `#:schema node_modules/@deco/workers-runtime/config-schema.json ${stringified}`; }; var writeWranglerConfig = async (config, cwd) => { const targetCwd = cwd || process3.cwd(); const currentConfig = await readWranglerConfig(targetCwd); const mergedConfig = { ...currentConfig, ...config }; mergedConfig.scope ??= mergedConfig.scope ?? mergedConfig?.deco?.workspace; const configPath = getConfigFilePath(targetCwd) ?? join(targetCwd, CONFIG_FILE); await promises.writeFile(configPath, addSchemaNotation(stringify(mergedConfig))); console.log(`\u2705 Wrangler configuration written to: ${configPath}`); }; var writeConfigFile = async (config, cwd, merge = true) => { const targetCwd = cwd || process3.cwd(); const wranglerConfig = await readWranglerConfig(targetCwd); const current = wranglerConfig.deco ?? {}; const mergedConfig = merge ? { ...current, ...config } : config; const configPath = getConfigFilePath(targetCwd) ?? join(targetCwd, CONFIG_FILE); await promises.writeFile( configPath, addSchemaNotation( stringify({ ...wranglerConfig, deco: mergedConfig }) ) ); console.log(`\u2705 Deco configuration written to: ${configPath}`); }; var getConfig = async ({ inlineOptions = {}, cwd } = {}) => { const config = await readConfigFile(cwd); const merged = { ...config, ...Object.fromEntries( Object.entries(inlineOptions).filter( ([_key, value]) => value !== void 0 ) ) }; if (!merged.workspace) { const session = await readSession(); merged.workspace = session?.workspace; } merged.local = getLocal() ?? merged.local; return decoConfigSchema.parse(merged); }; var getConfigFilePath = (cwd) => { const directPath = join(cwd, CONFIG_FILE); try { const stat = statSync(directPath); if (stat.isFile()) { return directPath; } } catch { } const dirs = cwd.split(/[/\\]/); const maxDepth = dirs.length; for (let i = maxDepth; i >= 1; i--) { const path = dirs.slice(0, i).join("/") || "/"; const configPath = join(path, CONFIG_FILE); try { const stat = statSync(configPath); if (stat.isFile()) { return configPath; } } catch { } } return null; }; var getAppUUID = (workspace = "default", app = "my-app") => { try { const combined = `${workspace}-${app}`; const hash = md5Hash(combined); return hash.slice(0, 8); } catch (error) { console.warn( "Could not generate hash for UUID, using random fallback:", error ); return crypto.randomUUID().slice(0, 8); } }; var getAppDomain = (workspace, app) => { const appUUID = getAppUUID(workspace, app); return `localhost-${appUUID}.deco.host`; }; function getMCPConfig(workspace, app) { const appDomain = getAppDomain(workspace, app); return { mcpServers: { [app]: { type: "http", url: `https://${appDomain}/mcp` } } }; } var getMCPConfigVersion = () => md5Hash(getMCPConfig.toString()); var getRulesConfig = async () => { return {}; }; // src/lib/mcp.ts var workspaceClientParams = async ({ workspace, local: local2, integrationId, pathname }) => { pathname ??= "/mcp"; const headers = await getRequestAuthHeaders(); const api = local2 ? DECO_CMS_API_LOCAL : DECO_CMS_API_PROD; let path; if (integrationId && workspace) { const workspacePath = workspace.startsWith("/") ? workspace : `/shared/${workspace}`; path = `${workspacePath}/${integrationId}${pathname}`; } else { path = !workspace || workspace.startsWith("/") ? `${workspace ?? ""}${pathname}` : `/shared/${workspace}${pathname}`; } const url = new URL(path, api); return { headers, url }; }; var createWorkspaceClient = async ({ workspace, local: local2, integrationId }) => { const client = new Client({ name: "deco-chat-cli", version: "1.0.0" }); const { headers, url } = await workspaceClientParams({ workspace, local: local2 ?? getLocal(), integrationId }); await client.connect( new StreamableHTTPClientTransport(url, { requestInit: { headers } }) ); return client; }; var createWorkspaceClientStub = async ({ workspace, local: local2, integrationId }) => { const { headers, url } = await workspaceClientParams({ workspace, local: local2, integrationId, pathname: "/tools/call" }); return { callTool: async ({ name, arguments: props }) => { const toolUrl = url.href + `/${name}`; const response = await fetch(toolUrl, { method: "POST", headers, body: JSON.stringify(props) }); const textResponse = await response.text().catch(() => null); if (!textResponse) { return { isError: true, content: [{ text: `Error: ${response.status}` }] }; } if (!response.ok) { return { isError: true, content: [{ text: textResponse }] }; } return { structuredContent: JSON.parse(textResponse).data }; } }; }; export { AUTH_PORT_CLI, DECO_CMS_API_LOCAL, DECO_CMS_LOGIN_URL, createClient, createWorkspaceClient, createWorkspaceClientStub, deleteSession, getAppDomain, getAppUUID, getConfig, getConfigFilePath, getLocal, getMCPConfig, getMCPConfigVersion, getRulesConfig, readSession, readWranglerConfig, saveSession, setLocal, setToken, workspaceClientParams, writeConfigFile, writeWranglerConfig }; //# sourceMappingURL=chunk-KFMYHDGF.js.map //# sourceMappingURL=chunk-KFMYHDGF.js.map