diff --git a/.gitignore b/.gitignore index 790b1d6..f1f5f62 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,6 @@ clickhouse/ registry/ .cursor -CLAUDE.md \ No newline at end of file +CLAUDE.md + +.claude \ No newline at end of file diff --git a/apps/webapp/app/components/conversation/conversation-item.client.tsx b/apps/webapp/app/components/conversation/conversation-item.client.tsx index aa9ab73..5e26217 100644 --- a/apps/webapp/app/components/conversation/conversation-item.client.tsx +++ b/apps/webapp/app/components/conversation/conversation-item.client.tsx @@ -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", )} > - + ); diff --git a/apps/webapp/app/components/conversation/conversation-list.tsx b/apps/webapp/app/components/conversation/conversation-list.tsx index 5fc8fd6..eee3fb9 100644 --- a/apps/webapp/app/components/conversation/conversation-list.tsx +++ b/apps/webapp/app/components/conversation/conversation-list.tsx @@ -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(); + const navigate = useNavigate(); const [conversations, setConversations] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [hasNextPage, setHasNextPage] = useState(true); @@ -155,7 +158,7 @@ export const ConversationList = ({ return (
-
+
+
+ )} {/*
{ const [content, setContent] = useState(""); + const [title, setTitle] = useState(""); const editorRef = useRef(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 = ({
-

+

Hello {user.name}

+ +

+ Demo UI: basic conversation to showcase memory integration. +

{ - 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); }} /> diff --git a/apps/webapp/app/components/conversation/editor-extensions.tsx b/apps/webapp/app/components/conversation/editor-extensions.tsx new file mode 100644 index 0000000..3282e38 --- /dev/null +++ b/apps/webapp/app/components/conversation/editor-extensions.tsx @@ -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, + }), +]; diff --git a/apps/webapp/app/components/conversation/streaming-conversation.client.tsx b/apps/webapp/app/components/conversation/streaming-conversation.client.tsx index c9d17f5..860b4fe 100644 --- a/apps/webapp/app/components/conversation/streaming-conversation.client.tsx +++ b/apps/webapp/app/components/conversation/streaming-conversation.client.tsx @@ -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 ? ( ) : (
{loadingText}
diff --git a/apps/webapp/app/components/graph/graph.tsx b/apps/webapp/app/components/graph/graph.tsx index b277ab5..ff93858 100644 --- a/apps/webapp/app/components/graph/graph.tsx +++ b/apps/webapp/app/components/graph/graph.tsx @@ -179,7 +179,8 @@ export const Graph = forwardRef( relations: [], relationData: [], label: "", - color: theme.link.stroke, + color: "#0000001A", + labelColor: "#0000001A", size: 1, }; } diff --git a/apps/webapp/app/lib/model.server.ts b/apps/webapp/app/lib/model.server.ts index 3d10903..8cb1ab1 100644 --- a/apps/webapp/app/lib/model.server.ts +++ b/apps/webapp/app/lib/model.server.ts @@ -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; +} diff --git a/apps/webapp/app/routes/home.conversation.$conversationId.tsx b/apps/webapp/app/routes/home.conversation.$conversationId.tsx index c9197e0..e2fbf4b 100644 --- a/apps/webapp/app/routes/home.conversation.$conversationId.tsx +++ b/apps/webapp/app/routes/home.conversation.$conversationId.tsx @@ -138,7 +138,10 @@ export default function SingleConversation() { collapsedSize={16} className="border-border h-[calc(100vh_-_60px)] min-w-[200px] border-r-1" > - + diff --git a/apps/webapp/app/routes/home.tsx b/apps/webapp/app/routes/home.tsx index bdc46ac..18c2ed2 100644 --- a/apps/webapp/app/routes/home.tsx +++ b/apps/webapp/app/routes/home.tsx @@ -41,8 +41,8 @@ export default function Home() {
-
-
+
+
diff --git a/apps/webapp/app/services/knowledgeGraph.server.ts b/apps/webapp/app/services/knowledgeGraph.server.ts index efe2da1..1b2aeda 100644 --- a/apps/webapp/app/services/knowledgeGraph.server.ts +++ b/apps/webapp/app/services/knowledgeGraph.server.ts @@ -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); } /** diff --git a/apps/webapp/app/services/search.server.ts b/apps/webapp/app/services/search.server.ts index 335762c..eded062 100644 --- a/apps/webapp/app/services/search.server.ts +++ b/apps/webapp/app/services/search.server.ts @@ -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); } /** diff --git a/apps/webapp/app/tailwind.css b/apps/webapp/app/tailwind.css index 2a093b3..88743f9 100644 --- a/apps/webapp/app/tailwind.css +++ b/apps/webapp/app/tailwind.css @@ -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"); +} + diff --git a/apps/webapp/app/trigger/conversation/create-conversation-title.ts b/apps/webapp/app/trigger/conversation/create-conversation-title.ts index ed66966..93ab7ec 100644 --- a/apps/webapp/app/trigger/conversation/create-conversation-title.ts +++ b/apps/webapp/app/trigger/conversation/create-conversation-title.ts @@ -23,7 +23,7 @@ export const createConversationTitle = task({ () => {}, undefined, "", - LLMMappings.CLAUDESONNET, + LLMMappings.GPT41, ); for await (const chunk of gen) { diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 093076f..765eb37 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -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", diff --git a/core/types/package.json b/core/types/package.json deleted file mode 100644 index 8b9198c..0000000 --- a/core/types/package.json +++ /dev/null @@ -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" - } - } -} \ No newline at end of file diff --git a/core/types/src/index.ts b/core/types/src/index.ts deleted file mode 100644 index 1081f03..0000000 --- a/core/types/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './integration'; \ No newline at end of file diff --git a/core/types/src/integration.ts b/core/types/src/integration.ts deleted file mode 100644 index 3089e22..0000000 --- a/core/types/src/integration.ts +++ /dev/null @@ -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; - }; - 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; -} \ No newline at end of file diff --git a/core/types/tsconfig.json b/core/types/tsconfig.json deleted file mode 100644 index b5482c0..0000000 --- a/core/types/tsconfig.json +++ /dev/null @@ -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"] -} \ No newline at end of file diff --git a/integrations/slack/.gitignore b/integrations/slack/.gitignore index 9b60c0a..611892f 100644 --- a/integrations/slack/.gitignore +++ b/integrations/slack/.gitignore @@ -1,2 +1,3 @@ bin -node_modules \ No newline at end of file +node_modules + diff --git a/integrations/slack/package.json b/integrations/slack/package.json index dba603f..d291834 100644 --- a/integrations/slack/package.json +++ b/integrations/slack/package.json @@ -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" } } \ No newline at end of file diff --git a/integrations/slack/pnpm-lock.yaml b/integrations/slack/pnpm-lock.yaml index 657304d..e0bca70 100644 --- a/integrations/slack/pnpm-lock.yaml +++ b/integrations/slack/pnpm-lock.yaml @@ -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: {} diff --git a/integrations/slack/src/common/README.md b/integrations/slack/src/common/README.md deleted file mode 100644 index efd21bc..0000000 --- a/integrations/slack/src/common/README.md +++ /dev/null @@ -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 { - // 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 --integration-definition ` -- `account delete --account-id ` -- `process --event-data --integration-account ` -- `identify --webhook-data ` -- `sync --integration-account ` - -## 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 \ No newline at end of file diff --git a/integrations/slack/src/index.ts b/integrations/slack/src/index.ts index d66ee50..8e5f84d 100644 --- a/integrations/slack/src/index.ts +++ b/integrations/slack/src/index.ts @@ -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 { 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: ',', + }, + }, }; } } diff --git a/packages/sdk/README.md b/packages/sdk/README.md new file mode 100644 index 0000000..5d8867c --- /dev/null +++ b/packages/sdk/README.md @@ -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 { + 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 { + 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 { + // 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 { + // 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 { + // Extract account ID from webhook + const { team_id } = eventPayload.eventBody; + return { id: team_id }; + } + + private async handleSync(eventPayload: IntegrationEventPayload): Promise { + // 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. \ No newline at end of file diff --git a/packages/sdk/package.json b/packages/sdk/package.json index fed7b69..5ab5b14 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -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" + } } \ No newline at end of file diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index d33aaa0..7e22831 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1 +1,2 @@ export * from '@core/types'; +export * from './integrations'; diff --git a/packages/sdk/src/integrations/index.ts b/packages/sdk/src/integrations/index.ts new file mode 100644 index 0000000..bdaf855 --- /dev/null +++ b/packages/sdk/src/integrations/index.ts @@ -0,0 +1 @@ +export { IntegrationCLI } from './integration_cli'; \ No newline at end of file diff --git a/integrations/slack/src/common/IntegrationCLI.ts b/packages/sdk/src/integrations/integration_cli.ts similarity index 64% rename from integrations/slack/src/common/IntegrationCLI.ts rename to packages/sdk/src/integrations/integration_cli.ts index 093438d..462271c 100644 --- a/integrations/slack/src/common/IntegrationCLI.ts +++ b/packages/sdk/src/integrations/integration_cli.ts @@ -1,11 +1,10 @@ import { Command } from 'commander'; -import { - IntegrationEventPayload, - Spec, - Config, - Identifier, - Message -} from '@echo/core-types'; +import { + IntegrationEventPayload, + Spec, + 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 ', 'OAuth response JSON') + this.program + .command('setup') + .description(`Set up a new ${this.integrationName} integration account`) + .requiredOption( + '--event-body ', + 'Event body JSON (e.g. OAuth response or setup data)', + ) + .requiredOption( + '--integration-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 ', 'Integration configuration JSON') + .option('--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; + protected abstract handleEvent( + eventPayload: IntegrationEventPayload, + ): Promise; /** * Abstract method that must be implemented by each integration diff --git a/packages/types/src/integration.ts b/packages/types/src/integration.ts index e9036d8..2848217 100644 --- a/packages/types/src/integration.ts +++ b/packages/types/src/integration.ts @@ -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; + }; + auth?: Record; } -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; } diff --git a/packages/types/src/oauth/params.ts b/packages/types/src/oauth/params.ts index f2c423c..026dc07 100644 --- a/packages/types/src/oauth/params.ts +++ b/packages/types/src/oauth/params.ts @@ -18,8 +18,3 @@ export class APIKeyParams { "header_name": string; "format": string; } - -export class Spec { - auth: Record; - other_data?: any; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aa64c5..8355be4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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)