deco-cli
Version:
CLI for managing decocms.com apps & projects
387 lines (384 loc) • 11.8 kB
JavaScript
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