Feat: sdk for integrations

This commit is contained in:
Harshith Mullapudi 2025-07-09 16:02:54 +05:30
parent 54e535d57d
commit 2396c0ea57
33 changed files with 784 additions and 410 deletions

2
.gitignore vendored
View File

@ -45,3 +45,5 @@ registry/
.cursor
CLAUDE.md
.claude

View File

@ -1,14 +1,10 @@
import { EditorContent, useEditor } from "@tiptap/react";
import React, { useEffect } from "react";
import { Document } from "@tiptap/extension-document";
import HardBreak from "@tiptap/extension-hard-break";
import { History } from "@tiptap/extension-history";
import { Paragraph } from "@tiptap/extension-paragraph";
import { Text } from "@tiptap/extension-text";
import { useEffect } from "react";
import { UserTypeEnum } from "@core/types";
import { type ConversationHistory } from "@core/database";
import { cn } from "~/lib/utils";
import { extensionsForConversation } from "./editor-extensions";
interface AIConversationItemProps {
conversationHistory: ConversationHistory;
@ -24,14 +20,7 @@ export const ConversationItem = ({
const id = `a${conversationHistory.id.replace(/-/g, "")}`;
const editor = useEditor({
extensions: [
Document,
Paragraph,
Text,
HardBreak.configure({
keepMarks: true,
}),
],
extensions: [...extensionsForConversation],
editable: false,
content: conversationHistory.message,
});
@ -54,7 +43,7 @@ export const ConversationItem = ({
isUser && "bg-primary/20 max-w-[500px] rounded-md p-3",
)}
>
<EditorContent editor={editor} />
<EditorContent editor={editor} className="editor-container" />
</div>
</div>
);

View File

@ -1,4 +1,4 @@
import { useFetcher } from "@remix-run/react";
import { useFetcher, useNavigate } from "@remix-run/react";
import { useEffect, useState, useCallback, useRef } from "react";
import {
List,
@ -7,7 +7,7 @@ import {
type ListRowRenderer,
} from "react-virtualized";
import { format } from "date-fns";
import { MessageSquare, Clock } from "lucide-react";
import { MessageSquare, Clock, Plus } from "lucide-react";
import { cn } from "~/lib/utils";
import { Button } from "../ui";
@ -40,10 +40,13 @@ type ConversationListResponse = {
export const ConversationList = ({
currentConversationId,
showNewConversationCTA,
}: {
currentConversationId?: string;
showNewConversationCTA?: boolean;
}) => {
const fetcher = useFetcher<ConversationListResponse>();
const navigate = useNavigate();
const [conversations, setConversations] = useState<ConversationItem[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [hasNextPage, setHasNextPage] = useState(true);
@ -155,7 +158,7 @@ export const ConversationList = ({
return (
<div key={key} style={style}>
<div className="p-2">
<div className="p-1">
<Button
variant="ghost"
className={cn(
@ -194,6 +197,19 @@ export const ConversationList = ({
return (
<div className="flex h-full flex-col">
{showNewConversationCTA && (
<div className="flex items-center justify-start p-1 pb-0">
<Button
variant="ghost"
className="w-full justify-start gap-2 py-4"
onClick={() => {
navigate("/home/conversation");
}}
>
<Plus size={14} /> New conversation
</Button>
</div>
)}
{/* <div className="border-b">
<Input
type="text"

View File

@ -43,9 +43,7 @@ export function ConversationTextarea({
return;
}
const data = isLoading
? {}
: { message: text, title: text, conversationId };
const data = isLoading ? {} : { message: text, conversationId };
submit(data as any, {
action: isLoading

View File

@ -1,6 +1,6 @@
import { EditorRoot, EditorContent, Placeholder } from "novel";
import { useState, useRef, useCallback } from "react";
import { Form, useNavigate, useSubmit } from "@remix-run/react";
import { Form, useSubmit } from "@remix-run/react";
import { cn } from "~/lib/utils";
import { Document } from "@tiptap/extension-document";
import HardBreak from "@tiptap/extension-hard-break";
@ -21,6 +21,7 @@ export const ConversationNew = ({
user: { name: string | null };
}) => {
const [content, setContent] = useState("");
const [title, setTitle] = useState("");
const editorRef = useRef<any>(null);
const submit = useSubmit();
@ -31,13 +32,15 @@ export const ConversationNew = ({
if (!content.trim()) return;
submit(
{ message: content, title: content },
{ message: content, title },
{
action: "/home/conversation",
method: "post",
},
);
e.preventDefault();
setContent("");
setTitle("");
},
[content],
);
@ -71,9 +74,13 @@ export const ConversationNew = ({
<div className="flex h-full w-full flex-col items-start justify-start overflow-y-auto p-4">
<div className="flex w-full flex-col items-center">
<div className="w-full max-w-[90ch]">
<h1 className="mx-1 mb-4 text-left text-[32px] font-medium">
<h1 className="mx-1 text-left text-[32px] font-medium">
Hello <span className="text-primary">{user.name}</span>
</h1>
<p className="text-muted-foreground mx-1 mb-4">
Demo UI: basic conversation to showcase memory integration.
</p>
<div className="bg-background-3 border-border rounded-lg border-1 py-2">
<EditorRoot>
<EditorContent
@ -82,7 +89,7 @@ export const ConversationNew = ({
extensions={[
Placeholder.configure({
placeholder: () => {
return "Ask sol...";
return "Ask CORE...";
},
includeChildren: true,
}),
@ -113,6 +120,7 @@ export const ConversationNew = ({
);
setContent("");
setTitle("");
}
return true;
}
@ -125,7 +133,9 @@ export const ConversationNew = ({
)}
onUpdate={({ editor }: { editor: any }) => {
const html = editor.getHTML();
const text = editor.getText();
setContent(html);
setTitle(text);
}}
/>
</EditorRoot>

View File

@ -0,0 +1,148 @@
import { cx } from "class-variance-authority";
import {
StarterKit,
TiptapLink,
HorizontalRule,
Placeholder,
HighlightExtension,
AIHighlight,
} from "novel";
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
import Heading from "@tiptap/extension-heading";
import Table from "@tiptap/extension-table";
import TableCell from "@tiptap/extension-table-cell";
import TableHeader from "@tiptap/extension-table-header";
import TableRow from "@tiptap/extension-table-row";
import { all, createLowlight } from "lowlight";
import { mergeAttributes, type Extension } from "@tiptap/react";
// create a lowlight instance with all languages loaded
export const lowlight = createLowlight(all);
const tiptapLink = TiptapLink.configure({
HTMLAttributes: {
class: cx("text-primary cursor-pointer"),
},
});
const horizontalRule = HorizontalRule.configure({
HTMLAttributes: {
class: cx("my-2 border-t border-muted-foreground"),
},
});
const heading = Heading.extend({
renderHTML({ node, HTMLAttributes }) {
const hasLevel = this.options.levels.includes(node.attrs.level);
const level: 1 | 2 | 3 = hasLevel
? node.attrs.level
: this.options.levels[0];
const levelMap = { 1: "text-2xl", 2: "text-xl", 3: "text-lg" };
return [
`h${level}`,
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
class: `heading-node h${node.attrs.level}-style ${levelMap[level]} my-[1rem] font-medium`,
}),
0,
];
},
}).configure({ levels: [1, 2, 3] });
const defaultPlaceholder = Placeholder.configure({
placeholder: ({ node }) => {
if (node.type.name === "heading") {
return `Heading ${node.attrs.level}`;
}
if (node.type.name === "image" || node.type.name === "table") {
return "";
}
if (node.type.name === "codeBlock") {
return "Type in your code here...";
}
return "";
},
includeChildren: true,
});
export const getPlaceholder = (placeholder: string | Extension) => {
if (!placeholder) {
return defaultPlaceholder;
}
if (typeof placeholder === "string") {
return Placeholder.configure({
placeholder: () => {
return placeholder;
},
includeChildren: true,
});
}
return placeholder;
};
export const starterKit = StarterKit.configure({
heading: false,
history: false,
bulletList: {
HTMLAttributes: {
class: cx("list-disc list-outside pl-4 leading-1 my-1 mb-1.5"),
},
},
orderedList: {
HTMLAttributes: {
class: cx("list-decimal list-outside pl-4 leading-1 my-1"),
},
},
listItem: {
HTMLAttributes: {
class: cx("mt-1.5"),
},
},
blockquote: {
HTMLAttributes: {
class: cx("border-l-4 border-gray-400 dark:border-gray-500"),
},
},
paragraph: {
HTMLAttributes: {
class: cx("leading-[24px] mt-[1rem] paragraph-node"),
},
},
codeBlock: false,
code: {
HTMLAttributes: {
class: cx(
"rounded bg-grayAlpha-100 text-[#BF4594] px-1.5 py-1 font-mono font-medium border-none",
),
spellcheck: "false",
},
},
horizontalRule: false,
dropcursor: {
color: "#DBEAFE",
width: 4,
},
gapcursor: false,
});
export const extensionsForConversation = [
starterKit,
tiptapLink,
horizontalRule,
heading,
AIHighlight,
HighlightExtension,
Table.configure({
resizable: true,
}),
TableRow,
TableHeader,
TableCell,
CodeBlockLowlight.configure({
lowlight,
}),
];

View File

@ -1,13 +1,8 @@
import { EditorContent, useEditor } from "@tiptap/react";
import React from "react";
import { Document } from "@tiptap/extension-document";
import HardBreak from "@tiptap/extension-hard-break";
import { History } from "@tiptap/extension-history";
import { Paragraph } from "@tiptap/extension-paragraph";
import { Text } from "@tiptap/extension-text";
import { useTriggerStream } from "./use-trigger-stream";
import { Placeholder } from "novel";
import { extensionsForConversation } from "./editor-extensions";
interface StreamingConversationProps {
runId: string;
@ -41,15 +36,7 @@ export const StreamingConversation = ({
];
const messagesEditor = useEditor({
extensions: [
Placeholder,
Document,
Paragraph,
Text,
HardBreak.configure({
keepMarks: true,
}),
],
extensions: [...extensionsForConversation],
editable: false,
content: "",
});
@ -94,7 +81,7 @@ export const StreamingConversation = ({
{message ? (
<EditorContent
editor={messagesEditor}
className="text-foreground"
className="text-foreground editor-container"
/>
) : (
<div className="text-foreground italic">{loadingText}</div>

View File

@ -179,7 +179,8 @@ export const Graph = forwardRef<GraphRef, GraphProps>(
relations: [],
relationData: [],
label: "",
color: theme.link.stroke,
color: "#0000001A",
labelColor: "#0000001A",
size: 1,
};
}

View File

@ -2,6 +2,7 @@ import { LLMMappings, LLMModelEnum } from "@core/types";
import {
type CoreMessage,
type LanguageModelV1,
embed,
generateText,
streamText,
} from "ai";
@ -77,3 +78,29 @@ export async function makeModelCall(
return text;
}
export async function getEmbedding(text: string) {
const ollamaUrl = env.OLLAMA_URL;
if (!ollamaUrl) {
// Use OpenAI embedding model when explicitly requested
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: text,
});
return embedding;
}
// Default to using Ollama
const model = env.EMBEDDING_MODEL;
const ollama = createOllama({
baseURL: ollamaUrl,
});
const { embedding } = await embed({
model: ollama.embedding(model),
value: text,
});
return embedding;
}

View File

@ -138,7 +138,10 @@ export default function SingleConversation() {
collapsedSize={16}
className="border-border h-[calc(100vh_-_60px)] min-w-[200px] border-r-1"
>
<ConversationList currentConversationId={conversationId} />
<ConversationList
currentConversationId={conversationId}
showNewConversationCTA
/>
</ResizablePanel>
<ResizableHandle className="w-1" />

View File

@ -41,8 +41,8 @@ export default function Home() {
<SidebarInset className="bg-background h-[100vh] py-2 pr-2">
<div className="bg-background-2 h-full rounded-md">
<SiteHeader />
<div className="flex flex-1 flex-col">
<div className="@container/main flex flex-1 flex-col gap-2">
<div className="flex h-[calc(100vh_-_60px)] flex-col">
<div className="@container/main flex h-full flex-col gap-2">
<div className="flex h-full flex-col">
<Outlet />
</div>

View File

@ -1,5 +1,4 @@
import { openai } from "@ai-sdk/openai";
import { type CoreMessage, embed } from "ai";
import { type CoreMessage } from "ai";
import {
type AddEpisodeParams,
type EntityNode,
@ -35,39 +34,16 @@ import {
saveTriple,
searchStatementsByEmbedding,
} from "./graphModels/statement";
import { makeModelCall } from "~/lib/model.server";
import { getEmbedding, makeModelCall } from "~/lib/model.server";
import { Apps, getNodeTypes, getNodeTypesString } from "~/utils/presets/nodes";
import { normalizePrompt } from "./prompts";
import { env } from "~/env.server";
import { createOllama } from "ollama-ai-provider";
// Default number of previous episodes to retrieve for context
const DEFAULT_EPISODE_WINDOW = 5;
export class KnowledgeGraphService {
async getEmbedding(text: string, useOpenAI = false) {
if (useOpenAI) {
// Use OpenAI embedding model when explicitly requested
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: text,
});
return embedding;
}
// Default to using Ollama
const ollamaUrl = env.OLLAMA_URL;
const model = env.EMBEDDING_MODEL;
const ollama = createOllama({
baseURL: ollamaUrl,
});
const { embedding } = await embed({
model: ollama.embedding(model),
value: text,
});
return embedding;
async getEmbedding(text: string) {
return getEmbedding(text);
}
/**

View File

@ -1,6 +1,4 @@
import { openai } from "@ai-sdk/openai";
import type { StatementNode } from "@core/types";
import { embed } from "ai";
import { logger } from "./logger.service";
import { applyCrossEncoderReranking, applyWeightedRRF } from "./search/rerank";
import {
@ -9,6 +7,7 @@ import {
performBM25Search,
performVectorSearch,
} from "./search/utils";
import { getEmbedding } from "~/lib/model.server";
/**
* SearchService provides methods to search the reified + temporal knowledge graph
@ -16,12 +15,7 @@ import {
*/
export class SearchService {
async getEmbedding(text: string) {
const { embedding } = await embed({
model: openai.embedding("text-embedding-3-small"),
value: text,
});
return embedding;
return getEmbedding(text);
}
/**

View File

@ -363,8 +363,107 @@
@apply text-base;
}
p.is-editor-empty {
p.is-editor-empty:before {
@apply text-muted-foreground;
font-size: 14px !important;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
}
.title-bar-sigma {
user-select: none;
-webkit-user-select: none;
-webkit-app-region: drag;
}
.quick .header {
user-select: none;
-webkit-user-select: none;
-webkit-app-region: drag;
}
.editor-container p {
line-height: 24px;
}
.editor-container .heading-node:first-child {
margin-top: 0;
}
.editor-container .paragraph-node:first-child {
margin-top: 0;
}
.list-item--task {
list-style-type: none;
}
.tasks-component ul {
margin: 0 !important;
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
@apply bg-grayAlpha-100;
outline: none !important;
transition: background-color 0.2s;
box-shadow: none;
}
.ProseMirror:not(.dragging) .ProseMirror-selectednode.node-skill {
@apply !bg-transparent !pt-2;
}
.prosemirror-dropcursor-block {
@apply !bg-primary/50;
height: 2px !important;
}
.drag-handle {
position: fixed;
opacity: 1;
transition: opacity ease-in 0.2s;
border-radius: 0.25rem;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
background-repeat: no-repeat;
background-position: center;
width: 1.2rem;
height: 1.5rem;
z-index: 50;
cursor: grab;
&:hover {
background-color: var(--novel-stone-100);
transition: background-color 0.2s;
}
&:active {
background-color: var(--novel-stone-200);
transition: background-color 0.2s;
cursor: grabbing;
}
&.hide {
opacity: 0;
pointer-events: none;
}
@media screen and (max-width: 600px) {
display: none;
pointer-events: none;
}
}
.dark .drag-handle {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255,255,255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
}

View File

@ -23,7 +23,7 @@ export const createConversationTitle = task({
() => {},
undefined,
"",
LLMMappings.CLAUDESONNET,
LLMMappings.GPT41,
);
for await (const chunk of gen) {

View File

@ -59,6 +59,13 @@
"@tiptap/extension-history": "^2.11.9",
"@tiptap/extension-paragraph": "^2.11.9",
"@tiptap/extension-text": "^2.11.9",
"@tiptap/extension-table": "2.11.9",
"@tiptap/extension-table-cell": "2.11.9",
"@tiptap/extension-heading": "2.11.9",
"@tiptap/extension-table-header": "2.11.9",
"@tiptap/extension-table-row": "2.11.9",
"@tiptap/extension-code-block": "2.11.9",
"@tiptap/extension-code-block-lowlight": "^2.11.9",
"@tiptap/starter-kit": "2.11.9",
"@tiptap/react": "^2.11.9",
"@tiptap/pm": "^2.11.9",
@ -89,6 +96,7 @@
"isbot": "^4.1.0",
"jose": "^5.2.3",
"lucide-react": "^0.511.0",
"lowlight": "^3.3.0",
"morgan": "^1.10.0",
"nanoid": "3.3.8",
"neo4j-driver": "^5.28.1",

View File

@ -1,21 +0,0 @@
{
"name": "@echo/core-types",
"version": "1.0.0",
"description": "Core types for Echo integrations",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"devDependencies": {
"typescript": "^5.0.0"
},
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}

View File

@ -1 +0,0 @@
export * from './integration';

View File

@ -1,64 +0,0 @@
export enum IntegrationEventType {
/**
* Setting up or creating an integration account
*/
SETUP = "setup",
/**
* Processing incoming data from the integration
*/
PROCESS = "process",
/**
* Identifying which account a webhook belongs to
*/
IDENTIFY = "identify",
/**
* Scheduled synchronization of data
*/
SYNC = "sync",
}
export interface IntegrationEventPayload {
event: IntegrationEventType;
[x: string]: any;
}
export interface Spec {
name: string;
key: string;
description: string;
icon: string;
mcp?: {
command: string;
args: string[];
env: Record<string, string>;
};
auth?: {
OAuth2?: {
token_url: string;
authorization_url: string;
scopes: string[];
scope_identifier?: string;
scope_separator?: string;
};
};
}
export interface Config {
access_token: string;
[key: string]: any;
}
export interface Identifier {
id: string;
type?: string;
}
export type MessageType = 'spec' | 'data' | 'identifier';
export interface Message {
type: MessageType;
data: any;
}

View File

@ -1,18 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*", "*.ts"],
"exclude": ["node_modules", "dist"]
}

View File

@ -1,2 +1,3 @@
bin
node_modules

View File

@ -66,6 +66,6 @@
"commander": "^12.0.0",
"openai": "^4.0.0",
"react-query": "^3.39.3",
"@echo/core-types": "workspace:*"
"@redplanethq/sdk": "0.1.0"
}
}

View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
'@redplanethq/sdk':
specifier: 0.1.0
version: 0.1.0
axios:
specifier: ^1.7.9
version: 1.9.0
@ -489,6 +492,10 @@ packages:
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@redplanethq/sdk@0.1.0':
resolution: {integrity: sha512-RmPfT9XESjTSMLlAMkolZEF28PvGo5hlwrG75JQy1tAZkvaTHzC7A2mEAMbsBvOMrJuUztL3NtCmVF//C/C/+A==}
engines: {node: '>=18.0.0'}
'@rollup/plugin-commonjs@28.0.3':
resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==}
engines: {node: '>=16.0.0 || 14 >= 14.17'}
@ -901,6 +908,10 @@ packages:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'}
commander@14.0.0:
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
engines: {node: '>=20'}
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@ -2894,6 +2905,10 @@ snapshots:
'@pkgr/core@0.2.4': {}
'@redplanethq/sdk@0.1.0':
dependencies:
commander: 14.0.0
'@rollup/plugin-commonjs@28.0.3(rollup@4.40.2)':
dependencies:
'@rollup/pluginutils': 5.1.4(rollup@4.40.2)
@ -3320,6 +3335,8 @@ snapshots:
commander@12.1.0: {}
commander@14.0.0: {}
commander@2.20.3: {}
commander@4.1.1: {}

View File

@ -1,65 +0,0 @@
# IntegrationCLI Base Class
This is a common CLI base class that can be moved to the SDK and used by all integrations.
## Usage
### 1. Create your integration-specific CLI class:
```typescript
import { IntegrationCLI, IntegrationEventPayload } from './common/IntegrationCLI';
export class MyIntegrationCLI extends IntegrationCLI {
constructor() {
super('my-integration', '1.0.0');
}
protected async handleEvent(eventPayload: IntegrationEventPayload): Promise<any> {
// Your integration-specific logic here
return await processMyIntegrationEvent(eventPayload);
}
}
```
### 2. Create your CLI entry point:
```typescript
#!/usr/bin/env node
import { MyIntegrationCLI } from './MyIntegrationCLI';
const cli = new MyIntegrationCLI();
cli.parse();
```
### 3. Update your package.json:
```json
{
"bin": {
"my-integration": "./dist/cli.js"
},
"dependencies": {
"commander": "^12.0.0"
}
}
```
## Available Commands
The base class provides these commands automatically:
- `account create --oauth-response <json> --integration-definition <json>`
- `account delete --account-id <id>`
- `process --event-data <json> --integration-account <json>`
- `identify --webhook-data <json>`
- `sync --integration-account <json>`
## Moving to SDK
To move this to the SDK:
1. Move `IntegrationCLI.ts` to `@redplanethq/sol-sdk/src/cli/`
2. Export it from the SDK's index
3. Update imports in integrations to use the SDK version
4. Add commander as a dependency to the SDK

View File

@ -2,23 +2,24 @@
import { integrationCreate } from './account-create';
import { createActivityEvent } from './create-activity';
import { IntegrationCLI } from './common/IntegrationCLI';
import { IntegrationEventPayload, Spec } from '@echo/core-types';
import {
IntegrationCLI,
IntegrationEventPayload,
IntegrationEventType,
Spec,
} from '@redplanethq/sdk';
export async function run(eventPayload: IntegrationEventPayload) {
switch (eventPayload.event) {
case 'SETUP':
case IntegrationEventType.SETUP:
return await integrationCreate(eventPayload.eventBody, eventPayload.integrationDefinition);
case 'IDENTIFY':
case IntegrationEventType.IDENTIFY:
return eventPayload.eventBody.event.user;
case 'PROCESS':
case IntegrationEventType.PROCESS:
return createActivityEvent(eventPayload.eventBody, eventPayload.config);
case 'SYNC':
return { message: 'Scheduled sync completed successfully' };
default:
return {
message: `The event payload type is ${eventPayload.event}`,
@ -38,45 +39,45 @@ class SlackCLI extends IntegrationCLI {
protected async getSpec(): Promise<Spec> {
return {
name: "Slack extension",
key: "slack",
description: "Connect your workspace to Slack. Run your workflows from slack bookmarks",
icon: "slack",
name: 'Slack extension',
key: 'slack',
description: 'Connect your workspace to Slack. Run your workflows from slack bookmarks',
icon: 'slack',
mcp: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-slack"],
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-slack'],
env: {
"SLACK_BOT_TOKEN": "${config:access_token}",
"SLACK_TEAM_ID": "${config:team_id}",
"SLACK_CHANNEL_IDS": "${config:channel_ids}"
}
SLACK_BOT_TOKEN: '${config:access_token}',
SLACK_TEAM_ID: '${config:team_id}',
SLACK_CHANNEL_IDS: '${config:channel_ids}',
},
},
auth: {
OAuth2: {
token_url: "https://slack.com/api/oauth.v2.access",
authorization_url: "https://slack.com/oauth/v2/authorize",
token_url: 'https://slack.com/api/oauth.v2.access',
authorization_url: 'https://slack.com/oauth/v2/authorize',
scopes: [
"stars:read",
"team:read",
"stars:write",
"users:read",
"channels:read",
"groups:read",
"im:read",
"im:history",
"mpim:read",
"mpim:write",
"mpim:history",
"channels:history",
"chat:write",
"reactions:read",
"reactions:write",
"users.profile:read"
'stars:read',
'team:read',
'stars:write',
'users:read',
'channels:read',
'groups:read',
'im:read',
'im:history',
'mpim:read',
'mpim:write',
'mpim:history',
'channels:history',
'chat:write',
'reactions:read',
'reactions:write',
'users.profile:read',
],
scope_identifier: "user_scope",
scope_separator: ","
}
}
scope_identifier: 'user_scope',
scope_separator: ',',
},
},
};
}
}

203
packages/sdk/README.md Normal file
View File

@ -0,0 +1,203 @@
# Echo SDK
The Echo SDK provides tools and utilities for building integrations with the Echo platform.
## Integration System
The Echo integration system uses a CLI-based approach where each integration is a command-line tool that responds to specific events. This makes integrations portable, testable, and easy to debug.
### Integration Event Types
Each integration CLI handles 5 core event types:
#### 1. `spec`
Returns the integration's metadata and configuration.
**Usage:**
```bash
my-integration spec
```
**Returns:** Integration specification including name, description, auth config, etc.
#### 2. `setup`
Processes authentication data and returns tokens/credentials to be saved.
**Usage:**
```bash
my-integration setup --event-body '{"code":"oauth_code","state":"state"}' --integration-definition '{}'
```
**Returns:** Configuration data (tokens, credentials) to be stored for the account.
#### 3. `identify`
Extracts accountId from webhook data to route webhooks to the correct account.
**Usage:**
```bash
my-integration identify --webhook-data '{"team_id":"T123","event":{}}'
```
**Returns:** Account identifier for webhook routing.
#### 4. `process`
Handles webhook events and returns activity data.
**Usage:**
```bash
my-integration process --event-data '{"type":"reaction_added","reaction":"=M"}' --config '{"access_token":"token"}'
```
**Returns:** Activity messages representing user actions.
#### 5. `sync`
Performs scheduled data synchronization for integrations that don't support webhooks.
**Usage:**
```bash
my-integration sync --config '{"access_token":"token","last_sync":"2023-01-01T00:00:00Z"}'
```
**Returns:** Activity messages and updated state for next sync.
### Message Types
All integration responses are wrapped in a `Message` object with a `type` field:
- **`spec`** - Integration metadata and configuration
- **`activity`** - User actions/events from the integration
- **`state`** - Sync state for polling integrations
- **`identifier`** - Account identification for webhook routing
### Building an Integration
1. **Install the SDK:**
```bash
npm install @echo/core-sdk
```
2. **Create your integration class:**
```typescript
import { IntegrationCLI } from '@echo/core-sdk';
class MyIntegration extends IntegrationCLI {
constructor() {
super('my-integration', '1.0.0');
}
protected async handleEvent(eventPayload: IntegrationEventPayload): Promise<any> {
switch (eventPayload.event) {
case 'SETUP':
return this.handleSetup(eventPayload);
case 'PROCESS':
return this.handleProcess(eventPayload);
case 'IDENTIFY':
return this.handleIdentify(eventPayload);
case 'SYNC':
return this.handleSync(eventPayload);
default:
throw new Error(`Unknown event type: ${eventPayload.event}`);
}
}
protected async getSpec(): Promise<Spec> {
return {
name: 'My Integration',
key: 'my-integration',
description: 'Integration with My Service',
icon: 'https://example.com/icon.png',
auth: {
OAuth2: {
token_url: 'https://api.example.com/oauth/token',
authorization_url: 'https://api.example.com/oauth/authorize',
scopes: ['read', 'write']
}
}
};
}
private async handleSetup(eventPayload: IntegrationEventPayload): Promise<any> {
// Process OAuth response and return tokens to save
const { code } = eventPayload.eventBody;
// Exchange code for tokens...
return {
access_token: 'token',
refresh_token: 'refresh_token',
expires_at: Date.now() + 3600000
};
}
private async handleProcess(eventPayload: IntegrationEventPayload): Promise<any> {
// Handle webhook events
const { eventData } = eventPayload.eventBody;
// Process event and return activity...
return {
type: 'message',
user: 'user123',
content: 'Hello world',
timestamp: new Date()
};
}
private async handleIdentify(eventPayload: IntegrationEventPayload): Promise<any> {
// Extract account ID from webhook
const { team_id } = eventPayload.eventBody;
return { id: team_id };
}
private async handleSync(eventPayload: IntegrationEventPayload): Promise<any> {
// Perform scheduled sync
const { config } = eventPayload;
// Fetch data since last sync...
return {
activities: [/* activity data */],
state: { last_sync: new Date().toISOString() }
};
}
}
// CLI entry point
const integration = new MyIntegration();
integration.parse();
```
3. **Build and package your integration:**
```bash
npm run build
npm pack
```
### Integration Development
The `IntegrationCLI` base class provides:
- **Automatic CLI setup** with all required commands
- **JSON input/output handling** for all event types
- **Error handling** with proper exit codes
- **Consistent message formatting** for all responses
### Testing
Test your integration by running commands directly:
```bash
# Test spec
node dist/index.js spec
# Test setup
node dist/index.js setup --event-body '{"code":"test"}' --integration-definition '{}'
# Test webhook processing
node dist/index.js process --event-data '{"type":"test"}' --config '{"token":"test"}'
```
### Best Practices
1. **Always validate input data** before processing
2. **Handle errors gracefully** with meaningful error messages
3. **Use consistent data structures** for activities
4. **Include proper timestamps** in all activity data
5. **Store minimal state** for sync operations
6. **Test all event types** thoroughly
For more examples, see the integrations in the `integrations/` directory.

View File

@ -1,7 +1,7 @@
{
"name": "@redplanethq/sol-sdk",
"version": "0.2.18",
"description": "Sol Node.JS SDK",
"name": "@redplanethq/sdk",
"version": "0.1.0",
"description": "CORE Node.JS SDK",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"module": "./dist/index.mjs",
@ -29,6 +29,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"commander": "14.0.0"
},
"devDependencies": {
"@core/types": "workspace:*",
@ -44,6 +45,5 @@
},
"engines": {
"node": ">=18.0.0"
},
"packageManager": "pnpm@10.3.0"
}
}

View File

@ -1 +1,2 @@
export * from '@core/types';
export * from './integrations';

View File

@ -0,0 +1 @@
export { IntegrationCLI } from './integration_cli';

View File

@ -2,10 +2,9 @@ import { Command } from 'commander';
import {
IntegrationEventPayload,
Spec,
Config,
Identifier,
Message
} from '@echo/core-types';
Message,
IntegrationEventType,
} from '@core/types';
export abstract class IntegrationCLI {
protected program: Command;
@ -32,28 +31,35 @@ export abstract class IntegrationCLI {
}
private setupAccountCommands(): void {
const accountCmd = this.program
.command('account')
.description(`Manage ${this.integrationName} integration accounts`);
accountCmd
.command('create')
.description(`Create a new ${this.integrationName} integration account`)
.requiredOption('--oauth-response <response>', 'OAuth response JSON')
this.program
.command('setup')
.description(`Set up a new ${this.integrationName} integration account`)
.requiredOption(
'--event-body <body>',
'Event body JSON (e.g. OAuth response or setup data)',
)
.requiredOption(
'--integration-definition <definition>',
'Integration definition JSON',
)
.action(async (options) => {
try {
const oauthResponse = JSON.parse(options.oauthResponse);
const integrationDefinition = JSON.parse(options.integrationDefinition);
const eventBody = JSON.parse(options.eventBody);
const integrationDefinition = JSON.parse(
options.integrationDefinition,
);
const result = await this.handleEvent({
event: 'INTEGRATION_ACCOUNT_CREATED',
eventBody: { oauthResponse },
const messages: Message[] = await this.handleEvent({
event: IntegrationEventType.SETUP,
eventBody,
integrationDefinition,
});
console.log('Account created successfully:', JSON.stringify(result, null, 2));
for (const message of messages) {
console.log(JSON.stringify(message, null, 2));
}
} catch (error) {
console.error('Error creating account:', error);
console.error('Error during setup:', error);
process.exit(1);
}
});
@ -70,17 +76,15 @@ export abstract class IntegrationCLI {
const eventData = JSON.parse(options.eventData);
const config = JSON.parse(options.config);
const result = await this.handleEvent({
event: 'PROCESS',
const messages: Message[] = await this.handleEvent({
event: IntegrationEventType.PROCESS,
eventBody: { eventData },
config,
});
const message: Message = {
type: 'data',
data: result
};
console.log(JSON.stringify(message, null, 2));
for (const message of messages) {
console.log(JSON.stringify(message, null, 2));
}
} catch (error) {
console.error('Error processing data:', error);
process.exit(1);
@ -95,16 +99,14 @@ export abstract class IntegrationCLI {
try {
const webhookData = JSON.parse(options.webhookData);
const result = await this.handleEvent({
event: 'IDENTIFY',
const messages: Message[] = await this.handleEvent({
event: IntegrationEventType.IDENTIFY,
eventBody: webhookData,
});
const message: Message = {
type: 'identifier',
data: result
};
console.log(JSON.stringify(message, null, 2));
for (const message of messages) {
console.log(JSON.stringify(message, null, 2));
}
} catch (error) {
console.error('Error identifying account:', error);
process.exit(1);
@ -121,8 +123,9 @@ export abstract class IntegrationCLI {
const spec = await this.getSpec();
const message: Message = {
type: 'spec',
data: spec
data: spec,
};
// For spec, we keep the single message output for compatibility
console.log(JSON.stringify(message, null, 2));
} catch (error) {
console.error('Error getting spec:', error);
@ -136,21 +139,22 @@ export abstract class IntegrationCLI {
.command('sync')
.description('Perform scheduled sync')
.requiredOption('--config <config>', 'Integration configuration JSON')
.option('--state <state>', 'Integration state JSON', '{}')
.action(async (options) => {
try {
const config = JSON.parse(options.config);
const state = options.state ? JSON.parse(options.state) : {};
const result = await this.handleEvent({
event: 'SYNC',
const messages: Message[] = await this.handleEvent({
event: IntegrationEventType.SYNC,
eventBody: {},
config,
state,
});
const message: Message = {
type: 'data',
data: result
};
console.log(JSON.stringify(message, null, 2));
for (const message of messages) {
console.log(JSON.stringify(message, null, 2));
}
} catch (error) {
console.error('Error during sync:', error);
process.exit(1);
@ -161,8 +165,11 @@ export abstract class IntegrationCLI {
/**
* Abstract method that must be implemented by each integration
* This method should handle the integration-specific logic for each event type
* and return an array of Message objects.
*/
protected abstract handleEvent(eventPayload: IntegrationEventPayload): Promise<any>;
protected abstract handleEvent(
eventPayload: IntegrationEventPayload,
): Promise<Message[]>;
/**
* Abstract method that must be implemented by each integration

View File

@ -1,70 +1,63 @@
import { Spec } from "./oauth";
import { APIKeyParams, OAuth2Params } from "./oauth";
export enum IntegrationPayloadEventType {
export enum IntegrationEventType {
/**
* When a webhook is received, this event is triggered to identify which integration
* account the webhook belongs to
* Processes authentication data and returns tokens/credentials to be saved
*/
IDENTIFY_WEBHOOK_ACCOUNT = "identify_webhook_account",
SETUP = "setup",
/**
* Lifecycle events for integration accounts
* Processing incoming data from the integration
*/
INTEGRATION_ACCOUNT_CREATED = "integration_account_created",
PROCESS = "process",
/**
* When data is received from the integration source (e.g. new Slack message)
* Identifying which account a webhook belongs to
*/
INTEGRATION_DATA_RECEIVED = "integration_data_received",
IDENTIFY = "identify",
/**
* For integrations without webhook support, this event is triggered at the
* configured frequency to sync data
* Scheduled synchronization of data
*/
SCHEDULED_SYNC = "scheduled_sync",
SYNC = "sync",
/**
* For returning integration metadata/config
*/
SPEC = "spec",
}
export interface IntegrationEventPayload {
event: IntegrationPayloadEventType;
event: IntegrationEventType;
[x: string]: any;
}
export interface Activity {
id: string;
type: string;
timestamp: string;
data: any;
export class Spec {
name: string;
key: string;
description: string;
icon: string;
mcp?: {
command: string;
args: string[];
env: Record<string, string>;
};
auth?: Record<string, OAuth2Params | APIKeyParams>;
}
export interface IntegrationAccountConfig {
export interface Config {
access_token: string;
team_id?: string;
channel_ids?: string;
[key: string]: any;
}
export interface IntegrationAccountIdentifier {
identifier: string;
type: string;
export interface Identifier {
id: string;
type?: string;
}
export interface IntegrationAccountSettings {
[key: string]: any;
}
export type MessageType =
| "Spec"
| "Activity"
| "IntegrationAccountConfig"
| "IntegrationAccountIdentifier"
| "IntegrationAccountSettings";
export type MessageType = "spec" | "activity" | "state" | "identifier";
export interface Message {
type: MessageType;
data:
| Spec
| Activity
| IntegrationAccountConfig
| IntegrationAccountIdentifier
| IntegrationAccountSettings;
data: any;
}

View File

@ -18,8 +18,3 @@ export class APIKeyParams {
"header_name": string;
"format": string;
}
export class Spec {
auth: Record<string, OAuth2Params | APIKeyParams>;
other_data?: any;
}

106
pnpm-lock.yaml generated
View File

@ -156,18 +156,39 @@ importers:
'@tanstack/react-table':
specifier: ^8.13.2
version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tiptap/extension-code-block':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-code-block-lowlight':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-document':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-hard-break':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-heading':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-history':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-paragraph':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-table':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-table-cell':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-table-header':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-table-row':
specifier: 2.11.9
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-text':
specifier: ^2.11.9
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
@ -258,6 +279,9 @@ importers:
jose:
specifier: ^5.2.3
version: 5.10.0
lowlight:
specifier: ^3.3.0
version: 3.3.0
lucide-react:
specifier: ^0.511.0
version: 0.511.0(react@18.3.1)
@ -275,7 +299,7 @@ importers:
version: 1.0.4
novel:
specifier: ^1.0.2
version: 1.0.2(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 1.0.2(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
ollama-ai-provider:
specifier: 1.2.0
version: 1.2.0(zod@3.23.8)
@ -461,12 +485,6 @@ importers:
specifier: ^4.2.1
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@18.19.115)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
core/types:
devDependencies:
typescript:
specifier: ^5.0.0
version: 5.8.3
packages/database:
dependencies:
'@prisma/client':
@ -521,6 +539,10 @@ importers:
version: 18.2.69
packages/sdk:
dependencies:
commander:
specifier: 14.0.0
version: 14.0.0
devDependencies:
'@core/types':
specifier: workspace:*
@ -3769,8 +3791,8 @@ packages:
highlight.js: ^11
lowlight: ^2 || ^3
'@tiptap/extension-code-block@2.25.0':
resolution: {integrity: sha512-T4kXbZNZ/NyklzQ/FWmUnjD4hgmJPrIBazzCZ/E/rF/Ag2IvUsztBT0PN3vTa+DAZ+IbM61TjlIpyJs1R7OdbQ==}
'@tiptap/extension-code-block@2.11.9':
resolution: {integrity: sha512-brwvt/SdP65DpchPv5rkhjEjjIIgE1+9ySw8kCTiyXWUrmZA0kK/iwp5zPpHfsoWT8Sa9+fh2uraVZGOF9Dcqw==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
@ -3814,8 +3836,8 @@ packages:
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-heading@2.25.0':
resolution: {integrity: sha512-IrRKRRr7Bhpnq5aue1v5/e5N/eNdVV/THsgqqpLZO48pgN8Wv+TweOZe1Ntg/v8L4QSBC8iGMxxhiJZT8AzSkA==}
'@tiptap/extension-heading@2.11.9':
resolution: {integrity: sha512-Z84Vbw26bnMyIyZ7hc8/xXDD5uAcr4GA1zs0HPs4Er9wROOqkZnlgE54LaObXn2YbMKuDZ24cmCU8LFy0etN+w==}
peerDependencies:
'@tiptap/core': ^2.7.0
@ -3878,6 +3900,27 @@ packages:
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-table-cell@2.11.9':
resolution: {integrity: sha512-YlM7y4UlAcHZuW8p6gkAi1DJa4Vc/8F5BiL2fiW/lot2awE05mI14jjpCZLqJ2wrO9aLguOJbN2VRXEFcTQO7Q==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-table-header@2.11.9':
resolution: {integrity: sha512-6bLZDywhLaBlgy4Zp26yB28256F2lyjgoUO90w1doU4c19qlS1pkAwt3clYNlqQgMVVVjIZObbt8gZYma/8svA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-table-row@2.11.9':
resolution: {integrity: sha512-so/rP4KTabeoQtvnPFYrVFqMi/QJAihBa5InZPDEjT4pue0yPQbOnTcGRgKiMNYLIEwAC9nw6i1zWlkY5Eic+w==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/extension-table@2.11.9':
resolution: {integrity: sha512-4VWflJs7B9hgt1uG0SUdFtXJHlHbggIUtjX0tqd1BU9AsYspPXREALicG8Rz9Dm0eOX6dR30+I3LvL3K15XhTA==}
peerDependencies:
'@tiptap/core': ^2.7.0
'@tiptap/pm': ^2.7.0
'@tiptap/extension-task-item@2.25.0':
resolution: {integrity: sha512-8F7Z7jbsyGrPLHQCn+n39zdqIgxwR1kJ1nL5ZwhEW3ZhJgkFF0WMJSv36mwIJwL08p8um/c6g72AYB/e8CD7eA==}
peerDependencies:
@ -5036,6 +5079,10 @@ packages:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
commander@14.0.0:
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
engines: {node: '>=20'}
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
@ -13534,15 +13581,15 @@ snapshots:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/pm': 2.25.0
'@tiptap/extension-code-block-lowlight@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)':
'@tiptap/extension-code-block-lowlight@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-code-block': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-code-block': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/pm': 2.25.0
highlight.js: 11.11.1
lowlight: 3.3.0
'@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
'@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/pm': 2.25.0
@ -13580,7 +13627,7 @@ snapshots:
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-heading@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
'@tiptap/extension-heading@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
@ -13633,6 +13680,23 @@ snapshots:
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-table-cell@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-table-header@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-table-row@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-table@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/pm': 2.25.0
'@tiptap/extension-task-item@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
dependencies:
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
@ -13698,12 +13762,12 @@ snapshots:
'@tiptap/extension-bold': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-bullet-list': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-code': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-code-block': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-code-block': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-document': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-dropcursor': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-gapcursor': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-hard-break': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-heading': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-heading': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-history': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-horizontal-rule': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-italic': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
@ -14003,7 +14067,7 @@ snapshots:
'@types/hast@3.0.4':
dependencies:
'@types/unist': 2.0.11
'@types/unist': 3.0.3
'@types/http-errors@2.0.5': {}
@ -15084,6 +15148,8 @@ snapshots:
commander@11.1.0: {}
commander@14.0.0: {}
commander@2.20.3: {}
commander@4.1.1: {}
@ -18311,12 +18377,12 @@ snapshots:
normalize.css@8.0.1: {}
novel@1.0.2(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
novel@1.0.2(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@radix-ui/react-slot': 1.2.3(@types/react@18.2.69)(react@18.3.1)
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
'@tiptap/extension-character-count': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
'@tiptap/extension-code-block-lowlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-code-block-lowlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-color': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-text-style@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0)))
'@tiptap/extension-highlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
'@tiptap/extension-horizontal-rule': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)