From df193ddd359402e671e7fe8816fc74b11d0d5095 Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Sat, 12 Apr 2025 03:33:21 +0530 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=A6=20NEW:=20Pipe=20and=20Memory?= =?UTF-8?q?=20in=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 71 ++++++++++++ packages/cli/package.json | 5 +- packages/cli/src/auth/index.ts | 1 - packages/cli/src/index.ts | 58 +++++++++- packages/cli/src/memory/create.ts | 105 ++++++++++++++++++ packages/cli/src/memory/delete.ts | 50 +++++++++ packages/cli/src/memory/embed-doc.ts | 58 ++++++++++ packages/cli/src/memory/list-docs.ts | 50 +++++++++ packages/cli/src/memory/list.ts | 34 ++++++ packages/cli/src/memory/retrive.ts | 72 ++++++++++++ packages/cli/src/memory/upload-docs.ts | 94 ++++++++++++++++ packages/cli/src/pipe/create.ts | 75 +++++++++++++ packages/cli/src/pipe/list.ts | 34 ++++++ packages/cli/src/pipe/run.ts | 103 +++++++++++++++++ packages/cli/src/pipe/update.ts | 71 ++++++++++++ packages/cli/src/utils/cli.ts | 59 ++++++++++ .../cli/src/utils/get-langbase-api-key.ts | 34 ++++++ packages/cli/types/memory.ts | 10 ++ packages/cli/types/pipe.ts | 12 ++ 19 files changed, 993 insertions(+), 3 deletions(-) create mode 100644 packages/cli/src/memory/create.ts create mode 100644 packages/cli/src/memory/delete.ts create mode 100644 packages/cli/src/memory/embed-doc.ts create mode 100644 packages/cli/src/memory/list-docs.ts create mode 100644 packages/cli/src/memory/list.ts create mode 100644 packages/cli/src/memory/retrive.ts create mode 100644 packages/cli/src/memory/upload-docs.ts create mode 100644 packages/cli/src/pipe/create.ts create mode 100644 packages/cli/src/pipe/list.ts create mode 100644 packages/cli/src/pipe/run.ts create mode 100644 packages/cli/src/pipe/update.ts create mode 100644 packages/cli/src/utils/get-langbase-api-key.ts create mode 100644 packages/cli/types/memory.ts create mode 100644 packages/cli/types/pipe.ts diff --git a/packages/cli/README.md b/packages/cli/README.md index caee3b7..ec99e78 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -57,6 +57,77 @@ Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. } ``` + +## CLI Commands + +Get started with the Langbase CLI by running the following command: + +```bash +npx @langbase/cli help +``` + +### Pipe Agent +The CLI provides commands to manage your Langbase pipes. + +- Create a new pipe agent +```bash +npx @langbase/cli pipe +``` + +- Run a pipe agent +```bash +npx @langbase/cli pipe --run +``` + +- List all pipe agents +```bash +npx @langbase/cli pipe --listPipes +``` + +- Update a pipe agent +```bash +npx @langbase/cli pipe --update +``` + + +### Memory +The CLI provides commands to manage your Langbase memories. + +- Create a new memory +```bash +npx @langbase/cli memory +``` + +- Upload a document to memory +```bash +npx @langbase/cli memory --upload +``` + +- List all memories +```bash +npx @langbase/cli memory --listMemories +``` + +- Retrieve chunks from memory +```bash +npx @langbase/cli memory --retrieve +``` + +- List all documents in memory +```bash +npx @langbase/cli memory --listDocs +``` + +- Retry embedding of a document in a memory +```bash +npx @langbase/cli memory --embed +``` + +- Delete a memory +```bash +npx @langbase/cli memory --delete +``` + ## Next steps - Read the [Langbase SDK documentation](https://langbase.com/docs/sdk) for more details diff --git a/packages/cli/package.json b/packages/cli/package.json index 393e810..84f2d29 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -51,6 +51,7 @@ "@clack/prompts": "^0.7.0", "@hono/node-server": "^1.13.1", "@hono/zod-openapi": "^0.16.0", + "@langbase/cli": "^0.1.4", "@modelcontextprotocol/sdk": "^1.8.0", "@sindresorhus/slugify": "^2.2.1", "camelcase": "^8.0.0", @@ -73,6 +74,7 @@ "hono": "^4.5.11", "js-tiktoken": "^1.0.14", "jsdom": "^24.1.0", + "langbase": "^1.1.55", "log-symbols": "^7.0.0", "lowdb": "^7.0.1", "meow": "^13.2.0", @@ -87,7 +89,8 @@ "uuid": "^10.0.0", "xlsx": "^0.18.5", "zod": "^3.23.8", - "zod-error": "^1.5.0" + "zod-error": "^1.5.0", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@langbase/eslint-config": "workspace:*", diff --git a/packages/cli/src/auth/index.ts b/packages/cli/src/auth/index.ts index f12c48b..631e653 100644 --- a/packages/cli/src/auth/index.ts +++ b/packages/cli/src/auth/index.ts @@ -112,5 +112,4 @@ export async function auth() { `Authentication successful. API key is stored in ${envFile}` ) ); - process.exit(0); } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index aef60f3..8b94d72 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -3,10 +3,20 @@ import { auth } from './auth'; import { build } from './build'; import { deploy } from './deploy'; import { docsMcpServer } from './docs-mcp-server'; +import { createPipe } from './pipe/create'; +import { runPipe } from './pipe/run'; +import { updatePipe } from './pipe/update'; +import { listPipes } from './pipe/list'; import cli from './utils/cli'; import debugMode from './utils/debug-mode'; import cliInit from './utils/init'; - +import { createMemory } from './memory/create'; +import { listMemories } from './memory/list'; +import { deleteMemory } from './memory/delete'; +import { uploadDocs } from './memory/upload-docs'; +import { embedDoc } from './memory/embed-doc'; +import { retriveFromMemory } from './memory/retrive'; +import { listDocs } from './memory/list-docs'; const { flags, input, showHelp } = cli; const { clear, debug } = flags; @@ -50,4 +60,50 @@ const flag = (flg: string): boolean => Boolean(flags[flg]); if (command('docs-mcp-server')) { await docsMcpServer(); } + + if (command('pipe') && !flag('run') && !flag('update') && !flag('listPipes')) { + await createPipe(); + } + + if (command('pipe') && flag('run')) { + await runPipe(); + } + + if (command('pipe') && flag('update')) { + await updatePipe(); + } + + if (command('pipe') && flag('listPipes')) { + await listPipes(); + } + + + if (command('memory') && !flag('upload') && !flag('embed') && !flag('retrieve') && !flag('listDocs') && !flag('delete') && !flag('listMemories')) { + await createMemory(); + } + + if (command('memory') && flag('listMemories')) { + await listMemories(); + } + + if (command('memory') && flag('delete')) { + await deleteMemory(); + } + + if (command('memory') && flag('upload')) { + await uploadDocs(); + } + + if (command('memory') && flag('embed')) { + await embedDoc(); + } + + if (command('memory') && flag('retrieve')) { + await retriveFromMemory(); + } + + if (command('memory') && flag('listDocs')) { + await listDocs(); + } + })(); diff --git a/packages/cli/src/memory/create.ts b/packages/cli/src/memory/create.ts new file mode 100644 index 0000000..18e1c14 --- /dev/null +++ b/packages/cli/src/memory/create.ts @@ -0,0 +1,105 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase, EmbeddingModels } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { memoryNameSchema } from 'types/memory'; +import { fromZodError } from 'zod-validation-error'; + +export async function createMemory() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'Create a new memory' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }), + description: () => + p.text({ + message: 'Description of the memory', + placeholder: 'This is a CLI memory' + }), + model: () => + p.select({ + message: 'Embedding Model', + options: [ + { value: 'openai:text-embedding-3-large', label: 'OpenAI Text Embedding 3 Large' }, + { value: 'cohere:embed-multilingual-v3.0', label: 'Cohere Embed Multilingual v3.0' }, + { value: 'cohere:embed-multilingual-light-v3.0', label: 'Cohere Embed Multilingual Light v3.0' }, + { value: 'google:text-embedding-004', label: 'Google Text Embedding 004' } + ] + }) as Promise, + topK: () => + p.text({ + message: 'Top K', + placeholder: '10', + validate: value => { + if (Number(value) < 1 || Number(value) > 100) { + return 'Top K must be between 1 and 100'; + } + return; + } + }), + chunkSize: () => + p.text({ + message: 'Chunk size', + placeholder: '10000', + validate: value => { + if (Number(value) < 1024 || Number(value) > 300000) { + return 'Chunk size must be between 1024 and 300000'; + } + return; + } + }), + chunkOverlap: () => + p.text({ + message: 'Chunk overlap', + placeholder: '1000', + validate: value => { + if (Number(value) < 100 || Number(value) > 1000) { + return 'Chunk overlap must be between 100 and 1000'; + } + return; + } + }), + + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, description, model, topK, chunkSize, chunkOverlap }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Memory is being created...'); + + const memory = await langbase.memories.create({ + name, + description, + top_k: Number(topK), + chunk_size: Number(chunkSize), + chunk_overlap: Number(chunkOverlap), + embedding_model: model as EmbeddingModels, + }); + + spinner.stop('Memory created successfully!'); + p.outro(heading({ text: 'MEMORY', sub: `${memory.name} created successfully` })); + }); +} diff --git a/packages/cli/src/memory/delete.ts b/packages/cli/src/memory/delete.ts new file mode 100644 index 0000000..c58fc2e --- /dev/null +++ b/packages/cli/src/memory/delete.ts @@ -0,0 +1,50 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { fromZodError } from 'zod-validation-error'; +import { memoryNameSchema } from 'types/memory'; + + +export async function deleteMemory() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'Delete a memory' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Memory is being deleted...'); + + await langbase.memories.delete({name}); + + spinner.stop('Memory deleted successfully!'); + p.outro(heading({ text: 'MEMORY DELETED', sub: `${name} deleted successfully` })); + }); +} diff --git a/packages/cli/src/memory/embed-doc.ts b/packages/cli/src/memory/embed-doc.ts new file mode 100644 index 0000000..969c8a1 --- /dev/null +++ b/packages/cli/src/memory/embed-doc.ts @@ -0,0 +1,58 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { fromZodError } from 'zod-validation-error'; +import { memoryNameSchema } from 'types/memory'; + + +export async function embedDoc() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'Retry embedding a document' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }), + documentName: () => + p.text({ + message: 'Name of the document', + placeholder: 'cli-document', + }), + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, documentName }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Document is being embedded...'); + + const response = await langbase.memories.documents.embeddings.retry({ + memoryName: name, + documentName: documentName + }); + + spinner.stop('Document embedded successfully!'); + console.log(response); + }); +} diff --git a/packages/cli/src/memory/list-docs.ts b/packages/cli/src/memory/list-docs.ts new file mode 100644 index 0000000..ecd8993 --- /dev/null +++ b/packages/cli/src/memory/list-docs.ts @@ -0,0 +1,50 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { fromZodError } from 'zod-validation-error'; +import { memoryNameSchema } from 'types/memory'; + + +export async function listDocs() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'List all documents in a memory' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Listing all the documents...'); + + const response = await langbase.memories.documents.list({memoryName: name}); + + spinner.stop('Documents listed successfully!'); + p.outro(heading({ text: `List of documents in ${name} memory`, sub: `${response.map((doc) => doc.name).join(", ")}` })); + }); +} diff --git a/packages/cli/src/memory/list.ts b/packages/cli/src/memory/list.ts new file mode 100644 index 0000000..1052a15 --- /dev/null +++ b/packages/cli/src/memory/list.ts @@ -0,0 +1,34 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; + + +export async function listMemories() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'List all memories' })); + + try { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Listing all the memories...'); + + const response = await langbase.memories.list(); + + spinner.stop('Listed all the memories successfully!'); + + if (response.length === 0) { + console.log('No memories found.'); + } else { + p.outro(heading({ text: 'List of memories', sub: `${response.map((memory) => memory.name).join(", ")}` })); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + p.cancel(`Error listing memories: ${errorMessage}`); + process.exit(1); + } +} diff --git a/packages/cli/src/memory/retrive.ts b/packages/cli/src/memory/retrive.ts new file mode 100644 index 0000000..50a37a2 --- /dev/null +++ b/packages/cli/src/memory/retrive.ts @@ -0,0 +1,72 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { fromZodError } from 'zod-validation-error'; +import { memoryNameSchema } from 'types/memory'; + + +export async function retriveFromMemory() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'Retrive chunks from a memory' })); + await p.group( + { + query: () => + p.text({ + message: 'Query', + placeholder: 'What is the capital of France?', + }), + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }), + topK: () => + p.text({ + message: 'Top K', + placeholder: '5', + validate: value => { + if (Number(value) < 1 || Number(value) > 100) { + return 'Top K must be between 1 and 100'; + } + return; + } + }), + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, query, topK }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Memory is being retrived...'); + + const response = await langbase.memories.retrieve({ + query: query, + memory: [{ + name: name, + }], + topK: Number(topK) + }); + + spinner.stop('Memory retrived successfully!'); + console.log(response); + }); +} diff --git a/packages/cli/src/memory/upload-docs.ts b/packages/cli/src/memory/upload-docs.ts new file mode 100644 index 0000000..80cfdfd --- /dev/null +++ b/packages/cli/src/memory/upload-docs.ts @@ -0,0 +1,94 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; +import { fromZodError } from 'zod-validation-error'; +import { memoryNameSchema } from 'types/memory'; +import fs from 'fs'; +import path from 'path'; +import { ContentType } from 'langbase'; + + +export async function uploadDocs() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'MEMORY', sub: 'Upload a document' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; + } + }), + documentName: () => + p.text({ + message: 'Name of the document', + placeholder: 'cli-document', + }), + filePath: () => + p.text({ + message: 'Path to the document', + placeholder: '/path/to/your/file', + validate: value => { + if (!fs.existsSync(value)) { + return 'File does not exist'; + } + return; + } + }), + + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, documentName, filePath }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Document is being uploaded...'); + + // Read the file + const document = fs.readFileSync(filePath); + + // Determine content type based on file extension + const ext = path.extname(filePath).toLowerCase(); + let contentType: ContentType = 'text/plain'; + + // Map common extensions to content types + const contentTypeMap: Record = { + '.txt': 'text/plain', + '.md': 'text/markdown', + '.pdf': 'application/pdf', + '.csv': 'text/csv', + '.xls': 'application/vnd.ms-excel', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }; + + contentType = contentTypeMap[ext] || 'text/plain'; + + await langbase.memories.documents.upload({ + memoryName: name, + documentName: documentName, + document: document, + contentType: contentType + }); + + spinner.stop('Document uploaded successfully!'); + console.log(`${documentName} uploaded successfully!`); + }); +} diff --git a/packages/cli/src/pipe/create.ts b/packages/cli/src/pipe/create.ts new file mode 100644 index 0000000..cf200d7 --- /dev/null +++ b/packages/cli/src/pipe/create.ts @@ -0,0 +1,75 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { pipeNameSchema, PipeStatus } from '../../types/pipe'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; + + +export async function createPipe() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'PIPE AGENT', sub: 'Create a new pipe agent' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; + } + }), + description: () => + p.text({ + message: 'Description of the pipe', + placeholder: 'This is a CLI pipe agent' + }), + status: () => + p.select({ + message: 'Status of the pipe', + options: [ + { value: 'public', label: 'Public' }, + { value: 'private', label: 'Private' } + ] + }) as Promise, + systemPrompt: () => + p.text({ + message: 'System prompt', + placeholder: 'You are a helpful AI assistant.', + initialValue: 'You are a helpful AI assistant.' + }), + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, description, systemPrompt, status }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Pipe is being created...'); + + const pipe = await langbase.pipes.create({ + name, + status, + description, + messages: [ + { + role: 'system', + content: systemPrompt + } + ] + }); + + spinner.stop('Pipe created successfully!'); + p.outro(heading({ text: 'PIPE', sub: `${pipe.name} created successfully` })); + }); +} diff --git a/packages/cli/src/pipe/list.ts b/packages/cli/src/pipe/list.ts new file mode 100644 index 0000000..2148b4d --- /dev/null +++ b/packages/cli/src/pipe/list.ts @@ -0,0 +1,34 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; + + +export async function listPipes() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'PIPE AGENT', sub: 'List all pipe agents' })); + + try { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Listing all the pipe agents...'); + + const response = await langbase.pipes.list(); + + spinner.stop('Listed all the pipe agents successfully!'); + + if (response.length === 0) { + console.log('No pipe agents found.'); + } else { + p.outro(heading({ text: 'List of pipe agents', sub: `${response.map((pipe) => pipe.name).join(", ")}` })); + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + p.cancel(`Error listing pipe agents: ${errorMessage}`); + process.exit(1); + } +} diff --git a/packages/cli/src/pipe/run.ts b/packages/cli/src/pipe/run.ts new file mode 100644 index 0000000..0bac07b --- /dev/null +++ b/packages/cli/src/pipe/run.ts @@ -0,0 +1,103 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { pipeNameSchema } from '../../types/pipe'; +import { getRunner, Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; + + +export async function runPipe() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'PIPE AGENT', sub: 'Run a pipe agent' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; + } + }), + stream: () => + p.select({ + message: 'Stream', + options: [ + { value: true, label: 'True' }, + { value: false, label: 'False' } + ] + }) as Promise, + prompt: () => + p.text({ + message: 'Prompt', + placeholder: 'What is the capital of France?' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, prompt, stream }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Running the pipe agent...'); + + if (stream) { + const { stream: streamResponse } = await langbase.pipes.run({ + name, + stream, + messages: [ + { + role: 'user', + content: prompt + } + ] + }); + + const runner = getRunner(streamResponse); + runner.on('connect', () => { + console.log('Stream started.\n'); + }); + + runner.on('content', content => { + process.stdout.write(content); + }); + + runner.on('end', () => { + console.log('\nStream ended.'); + }); + + runner.on('error', error => { + console.error(error); + }); + + spinner.stop('Pipe agent run successfully!'); + } + else { + const response = await langbase.pipes.run({ + name, + stream, + messages: [ + { + role: 'user', + content: prompt + } + ] + }); + + spinner.stop('Pipe agent run successfully!'); + console.log(response.completion); + } + + + }); +} diff --git a/packages/cli/src/pipe/update.ts b/packages/cli/src/pipe/update.ts new file mode 100644 index 0000000..d38f967 --- /dev/null +++ b/packages/cli/src/pipe/update.ts @@ -0,0 +1,71 @@ +import { heading } from '@/utils/heading'; +import * as p from '@clack/prompts'; +import { pipeNameSchema } from '../../types/pipe'; +import { Langbase } from 'langbase'; +import { getApiKey } from '@/utils/get-langbase-api-key'; + + +export async function updatePipe() { + const apiKey = await getApiKey(); + + p.intro(heading({ text: 'PIPE AGENT', sub: 'Update a pipe agent' })); + await p.group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; + } + }), + description: () => + p.text({ + message: 'Description of the pipe', + placeholder: 'This is a CLI pipe agent' + }), + systemPrompt: () => + p.text({ + message: 'System prompt', + placeholder: 'You are a helpful AI assistant.' + }), + temperature: () => + p.text({ + message: 'Temperature', + placeholder: '0.5' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } + } + ).then(async ({ name, description, systemPrompt, temperature }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); + + const spinner = p.spinner(); + spinner.start('Updating the pipe agent...'); + + const pipe = await langbase.pipes.update({ + name, + description, + messages: [ + { + role: 'system', + content: systemPrompt + } + ], + temperature: Number(temperature) + }); + + spinner.stop('Pipe updated successfully!'); + p.outro(heading({ text: 'PIPE', sub: `${pipe.name} updated successfully` })); + }); +} diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index bbedfa2..032a1d8 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -13,7 +13,53 @@ const flags = { default: false, shortFlag: `d`, desc: `Print debug info` + }, + run: { + type: `boolean`, + default: false, + desc: `Run a pipe agent` + }, + update: { + type: `boolean`, + default: false, + desc: `Update a pipe agent` + }, + listPipes: { + type: `boolean`, + default: false, + desc: `List all pipe agents` + }, + listMemories: { + type: `boolean`, + default: false, + desc: `List all memories` + }, + upload: { + type: `boolean`, + default: false, + desc: `Upload a document to memory` + }, + retrieve: { + type: `boolean`, + default: false, + desc: `Retrieve chunks from memory` + }, + listDocs: { + type: `boolean`, + default: false, + desc: `List all documents in memory` + }, + embed: { + type: `boolean`, + default: false, + desc: `Retry embedding of a document in a memory` + }, + delete: { + type: `boolean`, + default: false, + desc: `Delete a memory` } + // agent: { // type: `string`, // shortFlag: `a`, @@ -32,6 +78,19 @@ const flags = { }; const commands = { + 'pipe': { + desc: `Create a pipe agent`, + flags: { + create: { + type: `boolean`, + default: false, + desc: `Create a pipe agent` + } + } + }, + 'memory': { + desc: `Create a memory`, + }, 'docs-mcp-server': { desc: `Start the Langbase docs MCP server` }, diff --git a/packages/cli/src/utils/get-langbase-api-key.ts b/packages/cli/src/utils/get-langbase-api-key.ts new file mode 100644 index 0000000..871a0b8 --- /dev/null +++ b/packages/cli/src/utils/get-langbase-api-key.ts @@ -0,0 +1,34 @@ +import { auth } from "@/auth"; +import fs from "fs/promises"; +import path from "path"; +import { findEnvFile } from "./find-env"; + +export async function getApiKey(): Promise { + const envFileName = await findEnvFile(); + if (!envFileName) { + await auth(); + // After auth, try to find the env file again + const newEnvFileName = await findEnvFile(); + if (!newEnvFileName) { + console.error("Failed to find .env file after authentication"); + process.exit(1); + } + + return getApiKey(); + } + + // Read the content of the env file + const envFilePath = path.join(process.cwd(), envFileName); + const envContent = await fs.readFile(envFilePath, 'utf-8'); + + // Extract the API key from the env content + const apiKeyMatch = envContent.match(/LANGBASE_API_KEY=([^\n]+)/); + if (!apiKeyMatch) { + console.error("LANGBASE_API_KEY not found in .env file"); + process.exit(1); + } + + const apiKey = apiKeyMatch[1]; + + return apiKey; +} \ No newline at end of file diff --git a/packages/cli/types/memory.ts b/packages/cli/types/memory.ts new file mode 100644 index 0000000..edebe20 --- /dev/null +++ b/packages/cli/types/memory.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const memoryNameSchema = z + .string() + .min(3, 'Memory name must be at least 3 characters long') + .max(50, 'Memory name must not exceed 50 characters') + .regex( + /^[a-zA-Z0-9.-]+$/, + 'Memory name can only contain letters, numbers, dots, and hyphens' + ); \ No newline at end of file diff --git a/packages/cli/types/pipe.ts b/packages/cli/types/pipe.ts new file mode 100644 index 0000000..33f8cdb --- /dev/null +++ b/packages/cli/types/pipe.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +export type PipeStatus = 'public' | 'private'; + +export const pipeNameSchema = z + .string() + .min(3, 'Pipe name must be at least 3 characters long') + .max(50, 'Pipe name must not exceed 50 characters') + .regex( + /^[a-zA-Z0-9.-]+$/, + 'Pipe name can only contain letters, numbers, dots, and hyphens' + ); \ No newline at end of file From de8607a1238bd3898a36169840df6c4703d5a7f3 Mon Sep 17 00:00:00 2001 From: arre_ankit Date: Tue, 22 Apr 2025 03:38:44 +0530 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/README.md | 61 ++++-- packages/cli/src/index.ts | 21 +- packages/cli/src/memory/create.ts | 205 ++++++++++-------- packages/cli/src/memory/delete.ts | 75 ++++--- packages/cli/src/memory/embed-doc.ts | 88 ++++---- packages/cli/src/memory/list-docs.ts | 77 ++++--- packages/cli/src/memory/list.ts | 15 +- packages/cli/src/memory/retrive.ts | 116 +++++----- packages/cli/src/memory/upload-docs.ts | 150 ++++++------- packages/cli/src/pipe/create.ts | 126 ++++++----- packages/cli/src/pipe/list.ts | 15 +- packages/cli/src/pipe/run.ts | 168 +++++++------- packages/cli/src/pipe/update.ts | 118 +++++----- packages/cli/src/utils/cli.ts | 8 +- .../cli/src/utils/get-langbase-api-key.ts | 56 ++--- packages/cli/types/memory.ts | 2 +- packages/cli/types/pipe.ts | 4 +- 17 files changed, 705 insertions(+), 600 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index ec99e78..1dfe1d6 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -9,55 +9,60 @@ The Langbase CLI is a command-line interface for Langbase. It provides a set of Integrate the Langbase Docs MCP server into your IDEs and Claude Desktop. #### Cursor + - Open Cursor settings - Navigate to the MCP settings - Click on the `+` button to add a new global MCP server - Paste the following configuration in the `mcp.json` file: + ```json { - "mcpServers": { - "Langbase": { - "command": "npx", - "args": ["@langbase/cli","docs-mcp-server"] - } - } + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli", "docs-mcp-server"] + } + } } ``` #### Windsurf + - Navigate to Windsurf - Settings > Advanced Settings - You will find the option to Add Server - Click “Add custom server +” - Paste the following configuration in the `mcp_config.json` file: + ```json { - "mcpServers": { - "Langbase": { - "command": "npx", - "args": ["@langbase/cli", "docs-mcp-server"] - } - } + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli", "docs-mcp-server"] + } + } } ``` #### Claude Desktop + - Open Claude Desktop File Menu - Navigate to the settings - Go to Developer Tab - Click on the Edit Config button - Paste the following configuration in the `claude_desktop_config.json` file: + ```json { - "mcpServers": { - "Langbase": { - "command": "npx", - "args": ["@langbase/cli", "docs-mcp-server"] - } - } + "mcpServers": { + "Langbase": { + "command": "npx", + "args": ["@langbase/cli", "docs-mcp-server"] + } + } } ``` - ## CLI Commands Get started with the Langbase CLI by running the following command: @@ -67,63 +72,75 @@ npx @langbase/cli help ``` ### Pipe Agent + The CLI provides commands to manage your Langbase pipes. -- Create a new pipe agent +- Create a new pipe agent + ```bash -npx @langbase/cli pipe +npx @langbase/cli pipe ``` - Run a pipe agent + ```bash npx @langbase/cli pipe --run ``` - List all pipe agents + ```bash npx @langbase/cli pipe --listPipes ``` - Update a pipe agent + ```bash npx @langbase/cli pipe --update ``` - ### Memory + The CLI provides commands to manage your Langbase memories. - Create a new memory + ```bash npx @langbase/cli memory ``` - Upload a document to memory + ```bash npx @langbase/cli memory --upload ``` - List all memories + ```bash npx @langbase/cli memory --listMemories ``` - Retrieve chunks from memory + ```bash npx @langbase/cli memory --retrieve ``` - List all documents in memory + ```bash npx @langbase/cli memory --listDocs ``` - Retry embedding of a document in a memory + ```bash npx @langbase/cli memory --embed ``` - Delete a memory + ```bash npx @langbase/cli memory --delete ``` diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 8b94d72..6832ea2 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -61,7 +61,12 @@ const flag = (flg: string): boolean => Boolean(flags[flg]); await docsMcpServer(); } - if (command('pipe') && !flag('run') && !flag('update') && !flag('listPipes')) { + if ( + command('pipe') && + !flag('run') && + !flag('update') && + !flag('listPipes') + ) { await createPipe(); } @@ -77,15 +82,22 @@ const flag = (flg: string): boolean => Boolean(flags[flg]); await listPipes(); } - - if (command('memory') && !flag('upload') && !flag('embed') && !flag('retrieve') && !flag('listDocs') && !flag('delete') && !flag('listMemories')) { + if ( + command('memory') && + !flag('upload') && + !flag('embed') && + !flag('retrieve') && + !flag('listDocs') && + !flag('delete') && + !flag('listMemories') + ) { await createMemory(); } if (command('memory') && flag('listMemories')) { await listMemories(); } - + if (command('memory') && flag('delete')) { await deleteMemory(); } @@ -105,5 +117,4 @@ const flag = (flg: string): boolean => Boolean(flags[flg]); if (command('memory') && flag('listDocs')) { await listDocs(); } - })(); diff --git a/packages/cli/src/memory/create.ts b/packages/cli/src/memory/create.ts index 18e1c14..76cf38b 100644 --- a/packages/cli/src/memory/create.ts +++ b/packages/cli/src/memory/create.ts @@ -3,103 +3,134 @@ import * as p from '@clack/prompts'; import { Langbase, EmbeddingModels } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; import { memoryNameSchema } from 'types/memory'; -import { fromZodError } from 'zod-validation-error'; +import { fromZodError } from 'zod-validation-error'; export async function createMemory() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'Create a new memory' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }), - description: () => - p.text({ - message: 'Description of the memory', - placeholder: 'This is a CLI memory' - }), - model: () => - p.select({ - message: 'Embedding Model', - options: [ - { value: 'openai:text-embedding-3-large', label: 'OpenAI Text Embedding 3 Large' }, - { value: 'cohere:embed-multilingual-v3.0', label: 'Cohere Embed Multilingual v3.0' }, - { value: 'cohere:embed-multilingual-light-v3.0', label: 'Cohere Embed Multilingual Light v3.0' }, - { value: 'google:text-embedding-004', label: 'Google Text Embedding 004' } - ] - }) as Promise, - topK: () => - p.text({ - message: 'Top K', - placeholder: '10', - validate: value => { - if (Number(value) < 1 || Number(value) > 100) { - return 'Top K must be between 1 and 100'; + }), + description: () => + p.text({ + message: 'Description of the memory', + placeholder: 'This is a CLI memory' + }), + model: () => + p.select({ + message: 'Embedding Model', + options: [ + { + value: 'openai:text-embedding-3-large', + label: 'OpenAI Text Embedding 3 Large' + }, + { + value: 'cohere:embed-multilingual-v3.0', + label: 'Cohere Embed Multilingual v3.0' + }, + { + value: 'cohere:embed-multilingual-light-v3.0', + label: 'Cohere Embed Multilingual Light v3.0' + }, + { + value: 'google:text-embedding-004', + label: 'Google Text Embedding 004' + } + ] + }) as Promise, + topK: () => + p.text({ + message: 'Top K', + placeholder: '10', + validate: value => { + if (Number(value) < 1 || Number(value) > 100) { + return 'Top K must be between 1 and 100'; + } + return; } - return; - } - }), - chunkSize: () => - p.text({ - message: 'Chunk size', - placeholder: '10000', - validate: value => { - if (Number(value) < 1024 || Number(value) > 300000) { - return 'Chunk size must be between 1024 and 300000'; + }), + chunkSize: () => + p.text({ + message: 'Chunk size', + placeholder: '10000', + validate: value => { + if ( + Number(value) < 1024 || + Number(value) > 300000 + ) { + return 'Chunk size must be between 1024 and 300000'; + } + return; } - return; - } - }), - chunkOverlap: () => - p.text({ - message: 'Chunk overlap', - placeholder: '1000', - validate: value => { - if (Number(value) < 100 || Number(value) > 1000) { - return 'Chunk overlap must be between 100 and 1000'; + }), + chunkOverlap: () => + p.text({ + message: 'Chunk overlap', + placeholder: '1000', + validate: value => { + if (Number(value) < 100 || Number(value) > 1000) { + return 'Chunk overlap must be between 100 and 1000'; + } + return; } - return; - } - }), - - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, description, model, topK, chunkSize, chunkOverlap }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then( + async ({ + name, + description, + model, + topK, + chunkSize, + chunkOverlap + }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Memory is being created...'); + const spinner = p.spinner(); + spinner.start('Memory is being created...'); - const memory = await langbase.memories.create({ - name, - description, - top_k: Number(topK), - chunk_size: Number(chunkSize), - chunk_overlap: Number(chunkOverlap), - embedding_model: model as EmbeddingModels, - }); + const memory = await langbase.memories.create({ + name, + description, + top_k: Number(topK), + chunk_size: Number(chunkSize), + chunk_overlap: Number(chunkOverlap), + embedding_model: model as EmbeddingModels + }); - spinner.stop('Memory created successfully!'); - p.outro(heading({ text: 'MEMORY', sub: `${memory.name} created successfully` })); - }); + spinner.stop('Memory created successfully!'); + p.outro( + heading({ + text: 'MEMORY', + sub: `${memory.name} created successfully` + }) + ); + } + ); } diff --git a/packages/cli/src/memory/delete.ts b/packages/cli/src/memory/delete.ts index c58fc2e..3a269d9 100644 --- a/packages/cli/src/memory/delete.ts +++ b/packages/cli/src/memory/delete.ts @@ -5,46 +5,53 @@ import { getApiKey } from '@/utils/get-langbase-api-key'; import { fromZodError } from 'zod-validation-error'; import { memoryNameSchema } from 'types/memory'; - export async function deleteMemory() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'Delete a memory' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }) - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Memory is being deleted...'); + const spinner = p.spinner(); + spinner.start('Memory is being deleted...'); - await langbase.memories.delete({name}); + await langbase.memories.delete({ name }); - spinner.stop('Memory deleted successfully!'); - p.outro(heading({ text: 'MEMORY DELETED', sub: `${name} deleted successfully` })); - }); + spinner.stop('Memory deleted successfully!'); + p.outro( + heading({ + text: 'MEMORY DELETED', + sub: `${name} deleted successfully` + }) + ); + }); } diff --git a/packages/cli/src/memory/embed-doc.ts b/packages/cli/src/memory/embed-doc.ts index 969c8a1..d5fdc1f 100644 --- a/packages/cli/src/memory/embed-doc.ts +++ b/packages/cli/src/memory/embed-doc.ts @@ -5,54 +5,58 @@ import { getApiKey } from '@/utils/get-langbase-api-key'; import { fromZodError } from 'zod-validation-error'; import { memoryNameSchema } from 'types/memory'; - export async function embedDoc() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'Retry embedding a document' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }), - documentName: () => - p.text({ - message: 'Name of the document', - placeholder: 'cli-document', - }), - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + documentName: () => + p.text({ + message: 'Name of the document', + placeholder: 'cli-document' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, documentName }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, documentName }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Document is being embedded...'); + const spinner = p.spinner(); + spinner.start('Document is being embedded...'); - const response = await langbase.memories.documents.embeddings.retry({ - memoryName: name, - documentName: documentName - }); + const response = await langbase.memories.documents.embeddings.retry( + { + memoryName: name, + documentName: documentName + } + ); - spinner.stop('Document embedded successfully!'); - console.log(response); - }); + spinner.stop('Document embedded successfully!'); + console.log(response); + }); } diff --git a/packages/cli/src/memory/list-docs.ts b/packages/cli/src/memory/list-docs.ts index ecd8993..515807a 100644 --- a/packages/cli/src/memory/list-docs.ts +++ b/packages/cli/src/memory/list-docs.ts @@ -5,46 +5,55 @@ import { getApiKey } from '@/utils/get-langbase-api-key'; import { fromZodError } from 'zod-validation-error'; import { memoryNameSchema } from 'types/memory'; - export async function listDocs() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'List all documents in a memory' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }) - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Listing all the documents...'); + const spinner = p.spinner(); + spinner.start('Listing all the documents...'); - const response = await langbase.memories.documents.list({memoryName: name}); + const response = await langbase.memories.documents.list({ + memoryName: name + }); - spinner.stop('Documents listed successfully!'); - p.outro(heading({ text: `List of documents in ${name} memory`, sub: `${response.map((doc) => doc.name).join(", ")}` })); - }); + spinner.stop('Documents listed successfully!'); + p.outro( + heading({ + text: `List of documents in ${name} memory`, + sub: `${response.map(doc => doc.name).join(', ')}` + }) + ); + }); } diff --git a/packages/cli/src/memory/list.ts b/packages/cli/src/memory/list.ts index 1052a15..9543c27 100644 --- a/packages/cli/src/memory/list.ts +++ b/packages/cli/src/memory/list.ts @@ -3,10 +3,9 @@ import * as p from '@clack/prompts'; import { Langbase } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; - export async function listMemories() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'List all memories' })); try { @@ -20,14 +19,20 @@ export async function listMemories() { const response = await langbase.memories.list(); spinner.stop('Listed all the memories successfully!'); - + if (response.length === 0) { console.log('No memories found.'); } else { - p.outro(heading({ text: 'List of memories', sub: `${response.map((memory) => memory.name).join(", ")}` })); + p.outro( + heading({ + text: 'List of memories', + sub: `${response.map(memory => memory.name).join(', ')}` + }) + ); } } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + const errorMessage = + err instanceof Error ? err.message : 'Unknown error occurred'; p.cancel(`Error listing memories: ${errorMessage}`); process.exit(1); } diff --git a/packages/cli/src/memory/retrive.ts b/packages/cli/src/memory/retrive.ts index 50a37a2..ef37615 100644 --- a/packages/cli/src/memory/retrive.ts +++ b/packages/cli/src/memory/retrive.ts @@ -5,68 +5,72 @@ import { getApiKey } from '@/utils/get-langbase-api-key'; import { fromZodError } from 'zod-validation-error'; import { memoryNameSchema } from 'types/memory'; - export async function retriveFromMemory() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'Retrive chunks from a memory' })); - await p.group( - { - query: () => - p.text({ - message: 'Query', - placeholder: 'What is the capital of France?', - }), - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + query: () => + p.text({ + message: 'Query', + placeholder: 'What is the capital of France?' + }), + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }), - topK: () => - p.text({ - message: 'Top K', - placeholder: '5', - validate: value => { - if (Number(value) < 1 || Number(value) > 100) { - return 'Top K must be between 1 and 100'; - } - return; - } - }), - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + topK: () => + p.text({ + message: 'Top K', + placeholder: '5', + validate: value => { + if (Number(value) < 1 || Number(value) > 100) { + return 'Top K must be between 1 and 100'; + } + return; + } + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, query, topK }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, query, topK }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Memory is being retrived...'); + const spinner = p.spinner(); + spinner.start('Memory is being retrived...'); - const response = await langbase.memories.retrieve({ - query: query, - memory: [{ - name: name, - }], - topK: Number(topK) - }); + const response = await langbase.memories.retrieve({ + query: query, + memory: [ + { + name: name + } + ], + topK: Number(topK) + }); - spinner.stop('Memory retrived successfully!'); - console.log(response); - }); + spinner.stop('Memory retrived successfully!'); + console.log(response); + }); } diff --git a/packages/cli/src/memory/upload-docs.ts b/packages/cli/src/memory/upload-docs.ts index 80cfdfd..7647f8b 100644 --- a/packages/cli/src/memory/upload-docs.ts +++ b/packages/cli/src/memory/upload-docs.ts @@ -8,87 +8,89 @@ import fs from 'fs'; import path from 'path'; import { ContentType } from 'langbase'; - export async function uploadDocs() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'MEMORY', sub: 'Upload a document' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the memory', - placeholder: 'cli-memory', - validate: value => { - const validatedName = memoryNameSchema.safeParse(value); - if (!validatedName.success) { - const validationError = fromZodError( - validatedName.error - ).toString(); - return validationError; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the memory', + placeholder: 'cli-memory', + validate: value => { + const validatedName = + memoryNameSchema.safeParse(value); + if (!validatedName.success) { + const validationError = fromZodError( + validatedName.error + ).toString(); + return validationError; + } + return; } - return; - } - }), - documentName: () => - p.text({ - message: 'Name of the document', - placeholder: 'cli-document', - }), - filePath: () => - p.text({ - message: 'Path to the document', - placeholder: '/path/to/your/file', - validate: value => { - if (!fs.existsSync(value)) { - return 'File does not exist'; - } - return; - } - }), - - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + documentName: () => + p.text({ + message: 'Name of the document', + placeholder: 'cli-document' + }), + filePath: () => + p.text({ + message: 'Path to the document', + placeholder: '/path/to/your/file', + validate: value => { + if (!fs.existsSync(value)) { + return 'File does not exist'; + } + return; + } + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, documentName, filePath }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, documentName, filePath }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Document is being uploaded...'); + const spinner = p.spinner(); + spinner.start('Document is being uploaded...'); - // Read the file - const document = fs.readFileSync(filePath); - - // Determine content type based on file extension - const ext = path.extname(filePath).toLowerCase(); - let contentType: ContentType = 'text/plain'; - - // Map common extensions to content types - const contentTypeMap: Record = { - '.txt': 'text/plain', - '.md': 'text/markdown', - '.pdf': 'application/pdf', - '.csv': 'text/csv', - '.xls': 'application/vnd.ms-excel', - '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - }; - - contentType = contentTypeMap[ext] || 'text/plain'; + // Read the file + const document = fs.readFileSync(filePath); - await langbase.memories.documents.upload({ - memoryName: name, - documentName: documentName, - document: document, - contentType: contentType - }); + // Determine content type based on file extension + const ext = path.extname(filePath).toLowerCase(); + let contentType: ContentType = 'text/plain'; - spinner.stop('Document uploaded successfully!'); - console.log(`${documentName} uploaded successfully!`); - }); + // Map common extensions to content types + const contentTypeMap: Record = { + '.txt': 'text/plain', + '.md': 'text/markdown', + '.pdf': 'application/pdf', + '.csv': 'text/csv', + '.xls': 'application/vnd.ms-excel', + '.xlsx': + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }; + + contentType = contentTypeMap[ext] || 'text/plain'; + + await langbase.memories.documents.upload({ + memoryName: name, + documentName: documentName, + document: document, + contentType: contentType + }); + + spinner.stop('Document uploaded successfully!'); + console.log(`${documentName} uploaded successfully!`); + }); } diff --git a/packages/cli/src/pipe/create.ts b/packages/cli/src/pipe/create.ts index cf200d7..de44488 100644 --- a/packages/cli/src/pipe/create.ts +++ b/packages/cli/src/pipe/create.ts @@ -4,72 +4,78 @@ import { pipeNameSchema, PipeStatus } from '../../types/pipe'; import { Langbase } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; - export async function createPipe() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'PIPE AGENT', sub: 'Create a new pipe agent' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the pipe agent', - placeholder: 'cli-pipe-agent', - validate: value => { - const result = pipeNameSchema.safeParse(value); - if (!result.success) { - return result.error.issues[0].message; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; } - return; - } - }), - description: () => - p.text({ - message: 'Description of the pipe', - placeholder: 'This is a CLI pipe agent' - }), - status: () => - p.select({ - message: 'Status of the pipe', - options: [ - { value: 'public', label: 'Public' }, - { value: 'private', label: 'Private' } - ] - }) as Promise, - systemPrompt: () => - p.text({ - message: 'System prompt', - placeholder: 'You are a helpful AI assistant.', - initialValue: 'You are a helpful AI assistant.' - }), - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + description: () => + p.text({ + message: 'Description of the pipe', + placeholder: 'This is a CLI pipe agent' + }), + status: () => + p.select({ + message: 'Status of the pipe', + options: [ + { value: 'public', label: 'Public' }, + { value: 'private', label: 'Private' } + ] + }) as Promise, + systemPrompt: () => + p.text({ + message: 'System prompt', + placeholder: 'You are a helpful AI assistant.', + initialValue: 'You are a helpful AI assistant.' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, description, systemPrompt, status }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, description, systemPrompt, status }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Pipe is being created...'); + const spinner = p.spinner(); + spinner.start('Pipe is being created...'); - const pipe = await langbase.pipes.create({ - name, - status, - description, - messages: [ - { - role: 'system', - content: systemPrompt - } - ] - }); + const pipe = await langbase.pipes.create({ + name, + status, + description, + messages: [ + { + role: 'system', + content: systemPrompt + } + ] + }); - spinner.stop('Pipe created successfully!'); - p.outro(heading({ text: 'PIPE', sub: `${pipe.name} created successfully` })); - }); + spinner.stop('Pipe created successfully!'); + p.outro( + heading({ + text: 'PIPE', + sub: `${pipe.name} created successfully` + }) + ); + }); } diff --git a/packages/cli/src/pipe/list.ts b/packages/cli/src/pipe/list.ts index 2148b4d..bb8b184 100644 --- a/packages/cli/src/pipe/list.ts +++ b/packages/cli/src/pipe/list.ts @@ -3,10 +3,9 @@ import * as p from '@clack/prompts'; import { Langbase } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; - export async function listPipes() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'PIPE AGENT', sub: 'List all pipe agents' })); try { @@ -20,14 +19,20 @@ export async function listPipes() { const response = await langbase.pipes.list(); spinner.stop('Listed all the pipe agents successfully!'); - + if (response.length === 0) { console.log('No pipe agents found.'); } else { - p.outro(heading({ text: 'List of pipe agents', sub: `${response.map((pipe) => pipe.name).join(", ")}` })); + p.outro( + heading({ + text: 'List of pipe agents', + sub: `${response.map(pipe => pipe.name).join(', ')}` + }) + ); } } catch (err) { - const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + const errorMessage = + err instanceof Error ? err.message : 'Unknown error occurred'; p.cancel(`Error listing pipe agents: ${errorMessage}`); process.exit(1); } diff --git a/packages/cli/src/pipe/run.ts b/packages/cli/src/pipe/run.ts index 0bac07b..ea14a1f 100644 --- a/packages/cli/src/pipe/run.ts +++ b/packages/cli/src/pipe/run.ts @@ -4,100 +4,98 @@ import { pipeNameSchema } from '../../types/pipe'; import { getRunner, Langbase } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; - export async function runPipe() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'PIPE AGENT', sub: 'Run a pipe agent' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the pipe agent', - placeholder: 'cli-pipe-agent', - validate: value => { - const result = pipeNameSchema.safeParse(value); - if (!result.success) { - return result.error.issues[0].message; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; } - return; - } - }), - stream: () => - p.select({ - message: 'Stream', - options: [ - { value: true, label: 'True' }, - { value: false, label: 'False' } - ] - }) as Promise, - prompt: () => - p.text({ - message: 'Prompt', - placeholder: 'What is the capital of France?' - }) - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + stream: () => + p.select({ + message: 'Stream', + options: [ + { value: true, label: 'True' }, + { value: false, label: 'False' } + ] + }) as Promise, + prompt: () => + p.text({ + message: 'Prompt', + placeholder: 'What is the capital of France?' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, prompt, stream }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, prompt, stream }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Running the pipe agent...'); + const spinner = p.spinner(); + spinner.start('Running the pipe agent...'); + + if (stream) { + const { stream: streamResponse } = await langbase.pipes.run({ + name, + stream, + messages: [ + { + role: 'user', + content: prompt + } + ] + }); - if (stream) { - const { stream: streamResponse } = await langbase.pipes.run({ - name, - stream, - messages: [ - { - role: 'user', - content: prompt - } - ] - }); - - const runner = getRunner(streamResponse); - runner.on('connect', () => { - console.log('Stream started.\n'); - }); - - runner.on('content', content => { - process.stdout.write(content); - }); + const runner = getRunner(streamResponse); + runner.on('connect', () => { + console.log('Stream started.\n'); + }); - runner.on('end', () => { - console.log('\nStream ended.'); - }); + runner.on('content', content => { + process.stdout.write(content); + }); - runner.on('error', error => { - console.error(error); - }); + runner.on('end', () => { + console.log('\nStream ended.'); + }); - spinner.stop('Pipe agent run successfully!'); - } - else { - const response = await langbase.pipes.run({ - name, - stream, - messages: [ - { - role: 'user', - content: prompt - } - ] - }); + runner.on('error', error => { + console.error(error); + }); - spinner.stop('Pipe agent run successfully!'); - console.log(response.completion); - } - - - }); + spinner.stop('Pipe agent run successfully!'); + } else { + const response = await langbase.pipes.run({ + name, + stream, + messages: [ + { + role: 'user', + content: prompt + } + ] + }); + + spinner.stop('Pipe agent run successfully!'); + console.log(response.completion); + } + }); } diff --git a/packages/cli/src/pipe/update.ts b/packages/cli/src/pipe/update.ts index d38f967..1885502 100644 --- a/packages/cli/src/pipe/update.ts +++ b/packages/cli/src/pipe/update.ts @@ -4,68 +4,74 @@ import { pipeNameSchema } from '../../types/pipe'; import { Langbase } from 'langbase'; import { getApiKey } from '@/utils/get-langbase-api-key'; - export async function updatePipe() { const apiKey = await getApiKey(); - + p.intro(heading({ text: 'PIPE AGENT', sub: 'Update a pipe agent' })); - await p.group( - { - name: () => - p.text({ - message: 'Name of the pipe agent', - placeholder: 'cli-pipe-agent', - validate: value => { - const result = pipeNameSchema.safeParse(value); - if (!result.success) { - return result.error.issues[0].message; + await p + .group( + { + name: () => + p.text({ + message: 'Name of the pipe agent', + placeholder: 'cli-pipe-agent', + validate: value => { + const result = pipeNameSchema.safeParse(value); + if (!result.success) { + return result.error.issues[0].message; + } + return; } - return; - } - }), - description: () => - p.text({ - message: 'Description of the pipe', - placeholder: 'This is a CLI pipe agent' - }), - systemPrompt: () => - p.text({ - message: 'System prompt', - placeholder: 'You are a helpful AI assistant.' - }), - temperature: () => - p.text({ - message: 'Temperature', - placeholder: '0.5' - }) - }, - { - onCancel: () => { - p.cancel('Operation cancelled.'); - process.exit(0); + }), + description: () => + p.text({ + message: 'Description of the pipe', + placeholder: 'This is a CLI pipe agent' + }), + systemPrompt: () => + p.text({ + message: 'System prompt', + placeholder: 'You are a helpful AI assistant.' + }), + temperature: () => + p.text({ + message: 'Temperature', + placeholder: '0.5' + }) + }, + { + onCancel: () => { + p.cancel('Operation cancelled.'); + process.exit(0); + } } - } - ).then(async ({ name, description, systemPrompt, temperature }) => { - const langbase = new Langbase({ - apiKey: apiKey - }); + ) + .then(async ({ name, description, systemPrompt, temperature }) => { + const langbase = new Langbase({ + apiKey: apiKey + }); - const spinner = p.spinner(); - spinner.start('Updating the pipe agent...'); + const spinner = p.spinner(); + spinner.start('Updating the pipe agent...'); - const pipe = await langbase.pipes.update({ - name, - description, - messages: [ - { - role: 'system', - content: systemPrompt - } - ], - temperature: Number(temperature) - }); + const pipe = await langbase.pipes.update({ + name, + description, + messages: [ + { + role: 'system', + content: systemPrompt + } + ], + temperature: Number(temperature) + }); - spinner.stop('Pipe updated successfully!'); - p.outro(heading({ text: 'PIPE', sub: `${pipe.name} updated successfully` })); - }); + spinner.stop('Pipe updated successfully!'); + p.outro( + heading({ + text: 'PIPE', + sub: `${pipe.name} updated successfully` + }) + ); + }); } diff --git a/packages/cli/src/utils/cli.ts b/packages/cli/src/utils/cli.ts index 032a1d8..747eff0 100644 --- a/packages/cli/src/utils/cli.ts +++ b/packages/cli/src/utils/cli.ts @@ -28,7 +28,7 @@ const flags = { type: `boolean`, default: false, desc: `List all pipe agents` - }, + }, listMemories: { type: `boolean`, default: false, @@ -78,7 +78,7 @@ const flags = { }; const commands = { - 'pipe': { + pipe: { desc: `Create a pipe agent`, flags: { create: { @@ -88,8 +88,8 @@ const commands = { } } }, - 'memory': { - desc: `Create a memory`, + memory: { + desc: `Create a memory` }, 'docs-mcp-server': { desc: `Start the Langbase docs MCP server` diff --git a/packages/cli/src/utils/get-langbase-api-key.ts b/packages/cli/src/utils/get-langbase-api-key.ts index 871a0b8..c6bb271 100644 --- a/packages/cli/src/utils/get-langbase-api-key.ts +++ b/packages/cli/src/utils/get-langbase-api-key.ts @@ -1,34 +1,34 @@ -import { auth } from "@/auth"; -import fs from "fs/promises"; -import path from "path"; -import { findEnvFile } from "./find-env"; +import { auth } from '@/auth'; +import fs from 'fs/promises'; +import path from 'path'; +import { findEnvFile } from './find-env'; export async function getApiKey(): Promise { - const envFileName = await findEnvFile(); - if (!envFileName) { - await auth(); - // After auth, try to find the env file again - const newEnvFileName = await findEnvFile(); - if (!newEnvFileName) { - console.error("Failed to find .env file after authentication"); - process.exit(1); - } - - return getApiKey(); - } + const envFileName = await findEnvFile(); + if (!envFileName) { + await auth(); + // After auth, try to find the env file again + const newEnvFileName = await findEnvFile(); + if (!newEnvFileName) { + console.error('Failed to find .env file after authentication'); + process.exit(1); + } - // Read the content of the env file - const envFilePath = path.join(process.cwd(), envFileName); - const envContent = await fs.readFile(envFilePath, 'utf-8'); + return getApiKey(); + } - // Extract the API key from the env content - const apiKeyMatch = envContent.match(/LANGBASE_API_KEY=([^\n]+)/); - if (!apiKeyMatch) { - console.error("LANGBASE_API_KEY not found in .env file"); - process.exit(1); - } + // Read the content of the env file + const envFilePath = path.join(process.cwd(), envFileName); + const envContent = await fs.readFile(envFilePath, 'utf-8'); - const apiKey = apiKeyMatch[1]; + // Extract the API key from the env content + const apiKeyMatch = envContent.match(/LANGBASE_API_KEY=([^\n]+)/); + if (!apiKeyMatch) { + console.error('LANGBASE_API_KEY not found in .env file'); + process.exit(1); + } - return apiKey; -} \ No newline at end of file + const apiKey = apiKeyMatch[1]; + + return apiKey; +} diff --git a/packages/cli/types/memory.ts b/packages/cli/types/memory.ts index edebe20..433c9cc 100644 --- a/packages/cli/types/memory.ts +++ b/packages/cli/types/memory.ts @@ -7,4 +7,4 @@ export const memoryNameSchema = z .regex( /^[a-zA-Z0-9.-]+$/, 'Memory name can only contain letters, numbers, dots, and hyphens' - ); \ No newline at end of file + ); diff --git a/packages/cli/types/pipe.ts b/packages/cli/types/pipe.ts index 33f8cdb..3241ac9 100644 --- a/packages/cli/types/pipe.ts +++ b/packages/cli/types/pipe.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from 'zod'; export type PipeStatus = 'public' | 'private'; @@ -9,4 +9,4 @@ export const pipeNameSchema = z .regex( /^[a-zA-Z0-9.-]+$/, 'Pipe name can only contain letters, numbers, dots, and hyphens' - ); \ No newline at end of file + );