mirror of
https://github.com/langchain-ai/create-agent-chat-app.git
synced 2026-07-01 21:24:02 -04:00
feat: Allow for passing flags instead of interactive prompts
This commit is contained in:
@@ -8,7 +8,48 @@ This will clone a frontend chat application (Next.js or Vite), along with up to
|
||||
|
||||
## Usage
|
||||
|
||||
Clone code:
|
||||
### Quickstart
|
||||
|
||||
The quickest way to get started is to pass flags to the CLI, instead of going through the prompts:
|
||||
|
||||
```bash
|
||||
# Pass `-y` to accept all default values
|
||||
npx create-agent-chat-app@latest -y
|
||||
```
|
||||
|
||||
You can also pass individual flags. Here are all of the options the CLI accepts, and their default values:
|
||||
|
||||
```bash
|
||||
npx create-agent-chat-app@latest --help
|
||||
```
|
||||
|
||||
```
|
||||
Usage: create-agent-chat-app [options]
|
||||
|
||||
Create an agent chat app with one command
|
||||
|
||||
Options:
|
||||
-V, --version output the version number
|
||||
-y, --yes Skip all prompts and use default values
|
||||
--project-name <name> Name of the project (default: "agent-chat-app")
|
||||
--package-manager <manager> Package manager to use (npm, pnpm, yarn) (default: "yarn")
|
||||
--install-deps <boolean> Automatically install dependencies (default: "true")
|
||||
--framework <framework> Framework to use (nextjs, vite) (default: "nextjs")
|
||||
--include-agent <agent...> Pre-built agents to include (react, memory, research, retrieval)
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
If you want to pass some flags, and use the defaults for the rest, simply add `-y`/`--yes`, in addition to the flags you want to pass:
|
||||
|
||||
```bash
|
||||
npx create-agent-chat-app@latest -y --package-manager pnpm
|
||||
```
|
||||
|
||||
This will accept all default values, except for the package manager, which will be set to `pnpm`.
|
||||
|
||||
### Interactive
|
||||
|
||||
If you prefer to go through the prompts, you can run the following:
|
||||
|
||||
```bash
|
||||
# Using npx
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-agent-chat-app",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3-rc.2",
|
||||
"description": "Create a LangGraph chat app with one command",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -35,6 +35,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.10.0",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^13.1.0",
|
||||
"fs-extra": "^11.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+243
-83
@@ -5,6 +5,7 @@ import fs from "fs-extra";
|
||||
import chalk, { ChalkInstance } from "chalk";
|
||||
import { fileURLToPath } from "url";
|
||||
import { execSync } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import {
|
||||
BASE_GITIGNORE,
|
||||
NEXTJS_GITIGNORE,
|
||||
@@ -24,6 +25,8 @@ import {
|
||||
const __filename: string = fileURLToPath(import.meta.url);
|
||||
const __dirname: string = path.dirname(__filename);
|
||||
|
||||
const VERSION = "0.1.3";
|
||||
|
||||
type PackageManager = "npm" | "pnpm" | "yarn";
|
||||
type Framework = "nextjs" | "vite";
|
||||
|
||||
@@ -129,6 +132,9 @@ async function setPackageJsonFields(
|
||||
pkgJson[overridesPkgManagerMap[packageManager]] = {
|
||||
"@langchain/core": "^0.3.42",
|
||||
};
|
||||
if (packageManager === "npm") {
|
||||
delete pkgJson["resolutions"];
|
||||
}
|
||||
await fs.promises.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
||||
} catch (_) {
|
||||
console.log(
|
||||
@@ -281,9 +287,13 @@ const AGENT_DEPENDENCIES_MAP = {
|
||||
*/
|
||||
async function setAgentPackageJsonFields(
|
||||
baseDir: string,
|
||||
args: IncludeAgents,
|
||||
chalk: ChalkInstance,
|
||||
inputs: {
|
||||
agentArgs: IncludeAgents;
|
||||
packageManager: PackageManager;
|
||||
chalk: ChalkInstance;
|
||||
},
|
||||
): Promise<void> {
|
||||
const { agentArgs, packageManager, chalk } = inputs;
|
||||
try {
|
||||
const agentsPkgJsonPath = path.join(
|
||||
baseDir,
|
||||
@@ -295,16 +305,16 @@ async function setAgentPackageJsonFields(
|
||||
await fs.promises.readFile(agentsPkgJsonPath, "utf8"),
|
||||
);
|
||||
const requiredPackages: Record<string, string> = {};
|
||||
if (args.includeReactAgent) {
|
||||
if (agentArgs.includeReactAgent) {
|
||||
Object.assign(requiredPackages, AGENT_DEPENDENCIES_MAP["react-agent"]);
|
||||
}
|
||||
if (args.includeMemoryAgent) {
|
||||
if (agentArgs.includeMemoryAgent) {
|
||||
Object.assign(requiredPackages, AGENT_DEPENDENCIES_MAP["memory-agent"]);
|
||||
}
|
||||
if (args.includeResearchAgent) {
|
||||
if (agentArgs.includeResearchAgent) {
|
||||
Object.assign(requiredPackages, AGENT_DEPENDENCIES_MAP["research-agent"]);
|
||||
}
|
||||
if (args.includeRetrievalAgent) {
|
||||
if (agentArgs.includeRetrievalAgent) {
|
||||
Object.assign(
|
||||
requiredPackages,
|
||||
AGENT_DEPENDENCIES_MAP["retrieval-agent"],
|
||||
@@ -314,6 +324,11 @@ async function setAgentPackageJsonFields(
|
||||
...pkgJson.dependencies,
|
||||
...requiredPackages,
|
||||
};
|
||||
|
||||
// Update the scripts to call the correct package manager
|
||||
pkgJson.scripts["build:internal"] = pkgJson.scripts[
|
||||
"build:internal"
|
||||
].replace("{PACKAGE_MANAGER}", packageManager);
|
||||
await fs.promises.writeFile(
|
||||
agentsPkgJsonPath,
|
||||
JSON.stringify(pkgJson, null, 2),
|
||||
@@ -484,86 +499,211 @@ async function addPnpmDirectDependencyWorkaround(
|
||||
}
|
||||
}
|
||||
|
||||
async function promptUser(): Promise<ProjectAnswers> {
|
||||
/**
|
||||
* Parse command-line arguments and return project configuration.
|
||||
* If all required arguments are provided, this will bypass the interactive prompts.
|
||||
* If only some arguments are provided, the user will be prompted for the remaining ones.
|
||||
*/
|
||||
async function parseCommandLineArgs(): Promise<Partial<ProjectAnswers>> {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("create-agent-chat-app")
|
||||
.description("Create an agent chat app with one command")
|
||||
.version(VERSION)
|
||||
.option("-y, --yes", "Skip all prompts and use default values")
|
||||
.option("--project-name <name>", "Name of the project", "agent-chat-app")
|
||||
.option(
|
||||
"--package-manager <manager>",
|
||||
"Package manager to use (npm, pnpm, yarn)",
|
||||
"yarn",
|
||||
)
|
||||
.option(
|
||||
"--install-deps <boolean>",
|
||||
"Automatically install dependencies",
|
||||
"true",
|
||||
)
|
||||
.option(
|
||||
"--framework <framework>",
|
||||
"Framework to use (nextjs, vite)",
|
||||
"nextjs",
|
||||
)
|
||||
.option(
|
||||
"--include-agent <agent...>",
|
||||
"Pre-built agents to include (react, memory, research, retrieval)",
|
||||
)
|
||||
.allowUnknownOption();
|
||||
|
||||
program.parse();
|
||||
const options = program.opts();
|
||||
|
||||
const result: Partial<ProjectAnswers> = {};
|
||||
|
||||
// If -y or --yes flag is provided, use all defaults
|
||||
if (options.yes) {
|
||||
return {
|
||||
projectName: options.projectName ?? "agent-chat-app",
|
||||
packageManager: options.packageManager ?? "yarn",
|
||||
autoInstallDeps: options.autoInstallDeps ?? true,
|
||||
framework: options.framework ?? "nextjs",
|
||||
includeReactAgent: options.includeAgent?.includes("react") ?? true,
|
||||
includeMemoryAgent: options.includeAgent?.includes("memory") ?? true,
|
||||
includeResearchAgent: options.includeAgent?.includes("research") ?? true,
|
||||
includeRetrievalAgent:
|
||||
options.includeAgent?.includes("retrieval") ?? true,
|
||||
} as ProjectAnswers;
|
||||
}
|
||||
|
||||
if (options.projectName) {
|
||||
result.projectName = options.projectName;
|
||||
}
|
||||
|
||||
if (
|
||||
options.packageManager &&
|
||||
["npm", "pnpm", "yarn"].includes(options.packageManager)
|
||||
) {
|
||||
result.packageManager = options.packageManager as PackageManager;
|
||||
}
|
||||
|
||||
if (options.installDeps !== undefined) {
|
||||
result.autoInstallDeps = options.installDeps.toLowerCase() === "true";
|
||||
}
|
||||
|
||||
if (options.framework && ["nextjs", "vite"].includes(options.framework)) {
|
||||
result.framework = options.framework as Framework;
|
||||
}
|
||||
|
||||
if (options.includeAgent) {
|
||||
const selectedAgents = Array.isArray(options.includeAgent)
|
||||
? options.includeAgent
|
||||
: [options.includeAgent];
|
||||
|
||||
result.includeReactAgent = selectedAgents.includes("react");
|
||||
result.includeMemoryAgent = selectedAgents.includes("memory");
|
||||
result.includeResearchAgent = selectedAgents.includes("research");
|
||||
result.includeRetrievalAgent = selectedAgents.includes("retrieval");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt the user for any missing configuration options.
|
||||
* If a value is already provided in partialAnswers, the user won't be prompted for it.
|
||||
*/
|
||||
async function promptUser(
|
||||
partialAnswers: Partial<ProjectAnswers> = {},
|
||||
): Promise<ProjectAnswers> {
|
||||
intro(chalk.green(" create-agent-chat-app "));
|
||||
|
||||
const projectNameResponse = await text({
|
||||
message: "What is the name of your project?",
|
||||
placeholder: "agent-chat-app",
|
||||
defaultValue: "agent-chat-app",
|
||||
});
|
||||
// Project name prompt
|
||||
let projectName = partialAnswers.projectName;
|
||||
if (!projectName) {
|
||||
const projectNameResponse = await text({
|
||||
message: "What is the name of your project?",
|
||||
placeholder: "agent-chat-app",
|
||||
defaultValue: "agent-chat-app",
|
||||
});
|
||||
|
||||
if (isCancel(projectNameResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
const projectName = projectNameResponse as string;
|
||||
|
||||
const packageManagerResponse = await select({
|
||||
message: "Which package manager would you like to use?",
|
||||
options: [
|
||||
{ value: "npm", label: "npm" },
|
||||
{ value: "pnpm", label: "pnpm" },
|
||||
{ value: "yarn", label: "yarn" },
|
||||
],
|
||||
});
|
||||
|
||||
if (isCancel(packageManagerResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
const packageManager = packageManagerResponse as PackageManager;
|
||||
|
||||
const autoInstallDepsResponse = await confirm({
|
||||
message: "Would you like to automatically install dependencies?",
|
||||
initialValue: true,
|
||||
});
|
||||
|
||||
if (isCancel(autoInstallDepsResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
const autoInstallDeps = autoInstallDepsResponse as boolean;
|
||||
|
||||
const frameworkResponse = await select({
|
||||
message: "Which framework would you like to use?",
|
||||
options: [
|
||||
{ value: "nextjs", label: "Next.js" },
|
||||
{ value: "vite", label: "Vite" },
|
||||
],
|
||||
});
|
||||
|
||||
if (isCancel(frameworkResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
const framework = frameworkResponse as Framework;
|
||||
|
||||
const selectedAgentsResponse = await multiselect({
|
||||
message:
|
||||
'Which pre-built agents would you like to include? (Press "space" to select/unselect)',
|
||||
options: [
|
||||
{ value: "react", label: "ReAct Agent" },
|
||||
{ value: "memory", label: "Memory Agent" },
|
||||
{ value: "research", label: "Research Agent" },
|
||||
{ value: "retrieval", label: "Retrieval Agent" },
|
||||
],
|
||||
initialValues: ["react", "memory", "research", "retrieval"],
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (isCancel(selectedAgentsResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
if (isCancel(projectNameResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
projectName = projectNameResponse as string;
|
||||
}
|
||||
|
||||
const selectedAgents = selectedAgentsResponse as string[];
|
||||
// Package manager prompt
|
||||
let packageManager = partialAnswers.packageManager;
|
||||
if (!packageManager) {
|
||||
const packageManagerResponse = await select({
|
||||
message: "Which package manager would you like to use?",
|
||||
options: [
|
||||
{ value: "npm", label: "npm" },
|
||||
{ value: "pnpm", label: "pnpm" },
|
||||
{ value: "yarn", label: "yarn" },
|
||||
],
|
||||
});
|
||||
|
||||
// Determine which agents are included
|
||||
const includeReactAgent = selectedAgents.includes("react");
|
||||
const includeMemoryAgent = selectedAgents.includes("memory");
|
||||
const includeResearchAgent = selectedAgents.includes("research");
|
||||
const includeRetrievalAgent = selectedAgents.includes("retrieval");
|
||||
if (isCancel(packageManagerResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
packageManager = packageManagerResponse as PackageManager;
|
||||
}
|
||||
|
||||
// Auto install dependencies prompt
|
||||
let autoInstallDeps = partialAnswers.autoInstallDeps;
|
||||
if (autoInstallDeps === undefined) {
|
||||
const autoInstallDepsResponse = await confirm({
|
||||
message: "Would you like to automatically install dependencies?",
|
||||
initialValue: true,
|
||||
});
|
||||
|
||||
if (isCancel(autoInstallDepsResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
autoInstallDeps = autoInstallDepsResponse as boolean;
|
||||
}
|
||||
|
||||
// Framework prompt
|
||||
let framework = partialAnswers.framework;
|
||||
if (!framework) {
|
||||
const frameworkResponse = await select({
|
||||
message: "Which framework would you like to use?",
|
||||
options: [
|
||||
{ value: "nextjs", label: "Next.js" },
|
||||
{ value: "vite", label: "Vite" },
|
||||
],
|
||||
});
|
||||
|
||||
if (isCancel(frameworkResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
framework = frameworkResponse as Framework;
|
||||
}
|
||||
|
||||
// Check if all agent selections are already provided
|
||||
const allAgentSelectionsProvided =
|
||||
partialAnswers.includeReactAgent !== undefined &&
|
||||
partialAnswers.includeMemoryAgent !== undefined &&
|
||||
partialAnswers.includeResearchAgent !== undefined &&
|
||||
partialAnswers.includeRetrievalAgent !== undefined;
|
||||
|
||||
// Agent selection prompt if not all provided
|
||||
let includeReactAgent = partialAnswers.includeReactAgent ?? false;
|
||||
let includeMemoryAgent = partialAnswers.includeMemoryAgent ?? false;
|
||||
let includeResearchAgent = partialAnswers.includeResearchAgent ?? false;
|
||||
let includeRetrievalAgent = partialAnswers.includeRetrievalAgent ?? false;
|
||||
|
||||
if (!allAgentSelectionsProvided) {
|
||||
const selectedAgentsResponse = await multiselect({
|
||||
message:
|
||||
'Which pre-built agents would you like to include? (Press "space" to select/unselect)',
|
||||
options: [
|
||||
{ value: "react", label: "ReAct Agent" },
|
||||
{ value: "memory", label: "Memory Agent" },
|
||||
{ value: "research", label: "Research Agent" },
|
||||
{ value: "retrieval", label: "Retrieval Agent" },
|
||||
],
|
||||
initialValues: ["react", "memory", "research", "retrieval"],
|
||||
required: false,
|
||||
});
|
||||
|
||||
if (isCancel(selectedAgentsResponse)) {
|
||||
cancel("Operation cancelled");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const selectedAgents = selectedAgentsResponse as string[];
|
||||
|
||||
// Determine which agents are included
|
||||
includeReactAgent = selectedAgents.includes("react");
|
||||
includeMemoryAgent = selectedAgents.includes("memory");
|
||||
includeResearchAgent = selectedAgents.includes("research");
|
||||
includeRetrievalAgent = selectedAgents.includes("retrieval");
|
||||
}
|
||||
|
||||
// Combine all answers
|
||||
return {
|
||||
@@ -579,8 +719,24 @@ async function promptUser(): Promise<ProjectAnswers> {
|
||||
}
|
||||
|
||||
async function init(): Promise<void> {
|
||||
// Get user input using our new promptUser function
|
||||
const answers = await promptUser();
|
||||
// Parse command-line arguments first
|
||||
const cliOptions = await parseCommandLineArgs();
|
||||
|
||||
const allRequiredOptionsProvided =
|
||||
cliOptions.autoInstallDeps !== undefined &&
|
||||
cliOptions.projectName !== undefined &&
|
||||
cliOptions.packageManager !== undefined &&
|
||||
cliOptions.framework !== undefined &&
|
||||
cliOptions.includeReactAgent !== undefined &&
|
||||
cliOptions.includeMemoryAgent !== undefined &&
|
||||
cliOptions.includeResearchAgent !== undefined &&
|
||||
cliOptions.includeRetrievalAgent !== undefined;
|
||||
|
||||
// If all options are provided via CLI, use them directly
|
||||
// Otherwise, prompt for the missing options
|
||||
const answers = allRequiredOptionsProvided
|
||||
? (cliOptions as ProjectAnswers)
|
||||
: await promptUser(cliOptions);
|
||||
|
||||
const { projectName, packageManager, autoInstallDeps, framework } = answers;
|
||||
|
||||
@@ -640,7 +796,11 @@ async function init(): Promise<void> {
|
||||
|
||||
await Promise.all([
|
||||
updateLangGraphConfig(targetDir, chalk, includesAgentSelectionsMap),
|
||||
setAgentPackageJsonFields(targetDir, includesAgentSelectionsMap, chalk),
|
||||
setAgentPackageJsonFields(targetDir, {
|
||||
agentArgs: includesAgentSelectionsMap,
|
||||
packageManager,
|
||||
chalk,
|
||||
}),
|
||||
setEnvExampleFile(targetDir, includesAgentSelectionsMap, chalk),
|
||||
]);
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "npx @langchain/langgraph-cli dev --port 2024 --config ../../langgraph.json",
|
||||
"build": "yarn turbo build:internal --filter=agents",
|
||||
"build:internal": "yarn clean && tsc",
|
||||
"build": "turbo build:internal --filter=agents",
|
||||
"build:internal": "{PACKAGE_MANAGER} run clean && tsc",
|
||||
"clean": "rm -rf ./dist .turbo || true",
|
||||
"format": "prettier --config .prettierrc --write \"src\"",
|
||||
"lint": "eslint src",
|
||||
|
||||
@@ -5,7 +5,9 @@ import { initChatModel } from "langchain/chat_models/universal";
|
||||
* @param fullySpecifiedName - String in the format 'provider/model' or 'provider/account/provider/model'.
|
||||
* @returns A Promise that resolves to a BaseChatModel instance.
|
||||
*/
|
||||
export async function loadChatModel(fullySpecifiedName: string) {
|
||||
export async function loadChatModel(
|
||||
fullySpecifiedName: string,
|
||||
): Promise<ReturnType<typeof initChatModel>> {
|
||||
const index = fullySpecifiedName.indexOf("/");
|
||||
if (index === -1) {
|
||||
// If there's no "/", assume it's just the model
|
||||
|
||||
@@ -1392,6 +1392,11 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
commander@^13.1.0:
|
||||
version "13.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46"
|
||||
integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
|
||||
Reference in New Issue
Block a user