Conversation
✅ Deploy Preview for dashboard-v2-novu-staging canceled.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR introduces an "agent" template option to the Novu CLI init command. Users can now select between "notifications" and "agent" templates during project initialization. The agent template scaffolds a Next.js-based conversational AI application with handlers for message processing, actions, and conversation resolution, along with conditional environment setup and dependencies. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant CLI as Init CLI
participant Prompt as Template Selector
participant CreateApp as createApp()
participant InstallTemplate as installTemplate()
User->>CLI: Run init command (optional: --template flag)
CLI->>CLI: Check if template provided
alt No template option
CLI->>Prompt: Show selection menu
Prompt->>User: Prompt: "Choose template (notifications/agent)"
User->>Prompt: Select option
Prompt->>CLI: Return templateChoice
else Template provided
CLI->>CLI: Use provided templateChoice
end
CLI->>CLI: Validate templateChoice
CLI->>CreateApp: Call with templateChoice
CreateApp->>CreateApp: Resolve template type based on choice
CreateApp->>InstallTemplate: Call with resolved template
InstallTemplate->>InstallTemplate: Apply conditional logic (env vars, dependencies, directories)
InstallTemplate->>User: Scaffold files and install dependencies
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (7)
packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx (1)
9-18: Add blank line before return statement.The coding guideline requires a blank line before every
returnstatement for consistency.📝 Proposed fix
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { + return ( <html lang="en"> <body>{children}</body> </html> ); }As per coding guidelines, all TypeScript/JavaScript files should include a blank line before return statements.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx` around lines 9 - 18, Add a blank line immediately before the return statement in the RootLayout function to satisfy the coding guideline; open the RootLayout function (export default function RootLayout) and insert one empty line right above the line that begins with return so the structure has a visible blank line before returning the JSX.libs/dal/src/repositories/agent/agent.schema.ts (1)
19-22: Consider defining a more specific schema for reactions.
Schema.Types.Mixedprovides no database-layer validation or type safety, which could allow inconsistent data if the database is written to outside the application layer. The DTO (AgentReactionSettingsDto) already validates reactions asstring | nullfor both fields with comprehensive validators, and the entity type interface is properly defined—however, adding explicit schema validation (e.g.,{ type: String, enum: ['emoji-names', ...] }or a custom schema object) would strengthen data integrity at the database layer and enable better indexing. Note that this change requires a schema migration.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@libs/dal/src/repositories/agent/agent.schema.ts` around lines 19 - 22, The reactions subdocument currently uses Schema.Types.Mixed (fields onMessageReceived and onResolved) which provides no DB-level validation; replace it with a concrete Mongoose schema that mirrors the AgentReactionSettingsDto (e.g., an object schema for reactions with onMessageReceived and onResolved defined as { type: String, enum: [...], default: null, required: false } or as { type: String, enum: [...], nullable: true } depending on allowed values), update the agent schema to use this reactions schema (refer to the reactions property and its fields onMessageReceived/onResolved in agent.schema.ts), and prepare a schema migration to convert existing Mixed data to the new format and add appropriate indexes if needed.packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx (1)
3-4: Align component export andreturnspacing with repo frontend rules.Please switch to a named component export and add a blank line before
return.♻️ Proposed patch
-export default function Home() { +export function Home() { + return ( <main className={styles.main}> <div className={styles.container}> @@ ); } + +export default Home;As per coding guidelines,
**/*.{ts,tsx,js,jsx}requires named exports for components and a blank line before everyreturnstatement.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx` around lines 3 - 4, The component is currently a default export and has no blank line before its return; change the default exported function Home to a named export (e.g., export function Home or export const Home) and insert a blank line immediately before the return statement inside Home to comply with the repo frontend rules requiring named component exports and a blank line before every return.apps/api/src/app/agents/e2e/agent-reply.e2e.ts (1)
38-39: Consider asserting reaction side effects, not just stubbing them.These stubs avoid failures, but they don’t verify the new behavior. Add expectations in relevant cases to assert when
reactToMessageandremoveReactionshould/shouldn’t be called.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/app/agents/e2e/agent-reply.e2e.ts` around lines 38 - 39, The tests currently stub chatSdkService.reactToMessage and chatSdkService.removeReaction but don't assert their invocation; update agent-reply.e2e.ts to capture the stubs (e.g., const reactStub = sinon.stub(...); const removeStub = sinon.stub(...)) and add expectations in each relevant test case: assert reactStub was called with the expected messageId/reaction payload when a reaction should occur (use sinon.assert.calledOnceWithExactly or expect(reactStub).to.have.been.calledWith(...)) and assert removeStub was not called, and conversely assert removeStub.called* and reactStub.notCalled in cases where a reaction is removed; also reset or restore the stubs between tests to avoid cross-test pollution.packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts (1)
45-53: Consider defensive handling forctx.actionandvalue.The non-null assertions (
ctx.action!andvalue!) work for this template since the buttons always define values, but for a template that users will modify, adding defensive checks could prevent runtime errors if the action structure changes.♻️ Suggested defensive pattern
onAction: async (ctx) => { - const { actionId, value } = ctx.action!; - if (actionId === 'topic') { - ctx.metadata.set('topic', value!); + const { actionId, value } = ctx.action ?? {}; + if (actionId === 'topic' && value) { + ctx.metadata.set('topic', value); await ctx.reply({ markdown: `Topic set to **${value}**. Describe your issue and I'll help.`, }); } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts` around lines 45 - 53, The onAction handler currently uses non-null assertions on ctx.action and value; update the onAction function to defensively check that ctx.action exists and that actionId and value are defined (and of expected types) before using them: extract const action = ctx.action, return early if missing, then const { actionId, value } = action and validate actionId === 'topic' and typeof value === 'string' before calling ctx.metadata.set('topic', value) and ctx.reply; also handle the else/invalid case (e.g., log or send a safe reply) so malformed actions don't throw.packages/novu/src/commands/init/templates/index.ts (1)
228-241: Consolidate duplicate zod dependency additions.Both the
APP_REACT_EMAILblock and theisAgentTemplateblock add the same zod dependencies. Consider combining these conditions to reduce duplication.♻️ Suggested refactor
- if (template === TemplateTypeEnum.APP_REACT_EMAIL) { + if (template === TemplateTypeEnum.APP_REACT_EMAIL || isAgentTemplate) { packageJson.dependencies = { ...packageJson.dependencies, zod: '^3.23.8', 'zod-to-json-schema': '^3.23.1', }; - } - - if (isAgentTemplate) { - packageJson.dependencies = { - ...packageJson.dependencies, - zod: '^3.23.8', - 'zod-to-json-schema': '^3.23.1', - }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/index.ts` around lines 228 - 241, Duplicate zod dependency additions are present in both the APP_REACT_EMAIL block and the isAgentTemplate block; consolidate by adding the zod entries to packageJson.dependencies only once — either combine the two condition checks (APP_REACT_EMAIL || isAgentTemplate) around the packageJson.dependencies assignment or move the zod/'zod-to-json-schema' insertion to a single post-processing step that ensures packageJson.dependencies = { ...packageJson.dependencies, zod: '^3.23.8', 'zod-to-json-schema': '^3.23.1' } is executed once; update the code paths that reference packageJson and the isAgentTemplate condition accordingly so duplication is removed.apps/api/src/app/agents/services/agent-config-resolver.service.ts (1)
28-40: Consider validating reaction emoji values against platform-supported emojis.The
resolveReaction()helper function correctly handles the three-way logic (null/undefined/string). However, the DTO validation only checks that emoji fields are strings—there's no validation that the emoji is actually supported by the target platform (Slack, Discord, etc.).The current implementation allows invalid emojis to reach
addReaction(), which fails silently with warnings logged in the inbound handler. This is acceptable given the fire-and-forget pattern, but emoji validation in the DTO or service could improve developer feedback and catch configuration errors earlier.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/api/src/app/agents/services/agent-config-resolver.service.ts` around lines 28 - 40, The resolveReaction helper currently returns null/undefined/defaults but doesn't validate that the returned emoji is supported by the target platform; add emoji validation (either inside resolveReaction or as a new validateEmoji(value, platform) helper invoked from resolveReaction or the agent config validation flow) that checks the candidate emoji against the platform's supported set (Slack/Discord mappings) and returns null or the default if invalid, and surface a clear validation error or warning when an unsupported emoji is configured; reference resolveReaction, DEFAULT_REACTION_ON_MESSAGE, DEFAULT_REACTION_ON_RESOLVED and the agent config resolution code path so the check runs during config resolution/DTO validation rather than letting addReaction receive invalid values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts`:
- Around line 79-92: Guard access to conversation.channels before using
conversation.channels[0]: change the isFirstMessage logic to first verify
Array.isArray(conversation.channels) && conversation.channels.length > 0 (or
treat missing channels as no-first-message) and only then read the first
channel; update the block that uses channel (the channel variable,
isFirstMessage calculation, and subsequent calls to
thread.createSentMessageFromMessage(...) and
conversationRepository.setFirstPlatformMessageId(...)) to operate under that
guard (or handle undefined channel safely with optional chaining) so you never
directly index conversation.channels when it may be undefined.
In
`@apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts`:
- Around line 234-274: Both removeAckReaction and reactOnResolve (and
fireOnResolveBridgeCall) call agentConfigResolver.resolve(conversation._agentId,
command.integrationIdentifier) separately causing redundant async lookups;
resolve the AgentConfig once per request and pass it into these methods (e.g.,
change removeAckReaction/reactOnResolve/fireOnResolveBridgeCall signatures to
accept the resolved config) or add a short-lived cache inside
AgentConfigResolver so duplicate resolve() calls with the same
conversation._agentId and command.integrationIdentifier return the cached config
for the duration of the request.
In `@libs/dal/src/repositories/conversation/conversation.repository.ts`:
- Around line 112-127: The update currently overwrites firstPlatformMessageId on
every call; modify setFirstPlatformMessageId so the update filter only matches
when the channel's firstPlatformMessageId is not already set. In the filter
passed to this.update (inside setFirstPlatformMessageId) add a condition that
the channels array contains an element matching the platformThreadId AND where
firstPlatformMessageId does not exist or is null (use an $elemMatch on
'channels' with { platformThreadId: platformThreadId, firstPlatformMessageId: {
$exists: false } } or equivalent to treat null as not set); keep the $set to
'channels.$.firstPlatformMessageId' so the field is written only once.
---
Nitpick comments:
In `@apps/api/src/app/agents/e2e/agent-reply.e2e.ts`:
- Around line 38-39: The tests currently stub chatSdkService.reactToMessage and
chatSdkService.removeReaction but don't assert their invocation; update
agent-reply.e2e.ts to capture the stubs (e.g., const reactStub =
sinon.stub(...); const removeStub = sinon.stub(...)) and add expectations in
each relevant test case: assert reactStub was called with the expected
messageId/reaction payload when a reaction should occur (use
sinon.assert.calledOnceWithExactly or
expect(reactStub).to.have.been.calledWith(...)) and assert removeStub was not
called, and conversely assert removeStub.called* and reactStub.notCalled in
cases where a reaction is removed; also reset or restore the stubs between tests
to avoid cross-test pollution.
In `@apps/api/src/app/agents/services/agent-config-resolver.service.ts`:
- Around line 28-40: The resolveReaction helper currently returns
null/undefined/defaults but doesn't validate that the returned emoji is
supported by the target platform; add emoji validation (either inside
resolveReaction or as a new validateEmoji(value, platform) helper invoked from
resolveReaction or the agent config validation flow) that checks the candidate
emoji against the platform's supported set (Slack/Discord mappings) and returns
null or the default if invalid, and surface a clear validation error or warning
when an unsupported emoji is configured; reference resolveReaction,
DEFAULT_REACTION_ON_MESSAGE, DEFAULT_REACTION_ON_RESOLVED and the agent config
resolution code path so the check runs during config resolution/DTO validation
rather than letting addReaction receive invalid values.
In `@libs/dal/src/repositories/agent/agent.schema.ts`:
- Around line 19-22: The reactions subdocument currently uses Schema.Types.Mixed
(fields onMessageReceived and onResolved) which provides no DB-level validation;
replace it with a concrete Mongoose schema that mirrors the
AgentReactionSettingsDto (e.g., an object schema for reactions with
onMessageReceived and onResolved defined as { type: String, enum: [...],
default: null, required: false } or as { type: String, enum: [...], nullable:
true } depending on allowed values), update the agent schema to use this
reactions schema (refer to the reactions property and its fields
onMessageReceived/onResolved in agent.schema.ts), and prepare a schema migration
to convert existing Mixed data to the new format and add appropriate indexes if
needed.
In `@packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx`:
- Around line 9-18: Add a blank line immediately before the return statement in
the RootLayout function to satisfy the coding guideline; open the RootLayout
function (export default function RootLayout) and insert one empty line right
above the line that begins with return so the structure has a visible blank line
before returning the JSX.
In
`@packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts`:
- Around line 45-53: The onAction handler currently uses non-null assertions on
ctx.action and value; update the onAction function to defensively check that
ctx.action exists and that actionId and value are defined (and of expected
types) before using them: extract const action = ctx.action, return early if
missing, then const { actionId, value } = action and validate actionId ===
'topic' and typeof value === 'string' before calling ctx.metadata.set('topic',
value) and ctx.reply; also handle the else/invalid case (e.g., log or send a
safe reply) so malformed actions don't throw.
In `@packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx`:
- Around line 3-4: The component is currently a default export and has no blank
line before its return; change the default exported function Home to a named
export (e.g., export function Home or export const Home) and insert a blank line
immediately before the return statement inside Home to comply with the repo
frontend rules requiring named component exports and a blank line before every
return.
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 228-241: Duplicate zod dependency additions are present in both
the APP_REACT_EMAIL block and the isAgentTemplate block; consolidate by adding
the zod entries to packageJson.dependencies only once — either combine the two
condition checks (APP_REACT_EMAIL || isAgentTemplate) around the
packageJson.dependencies assignment or move the zod/'zod-to-json-schema'
insertion to a single post-processing step that ensures packageJson.dependencies
= { ...packageJson.dependencies, zod: '^3.23.8', 'zod-to-json-schema': '^3.23.1'
} is executed once; update the code paths that reference packageJson and the
isAgentTemplate condition accordingly so duplication is removed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 17c5c8cc-9f6a-4465-b75b-8026d07eb1fd
📒 Files selected for processing (34)
apps/api/src/app/agents/agents.module.tsapps/api/src/app/agents/dtos/agent-behavior.dto.tsapps/api/src/app/agents/e2e/agent-reply.e2e.tsapps/api/src/app/agents/e2e/agent-webhook.e2e.tsapps/api/src/app/agents/e2e/agents.e2e.tsapps/api/src/app/agents/services/agent-config-resolver.service.tsapps/api/src/app/agents/services/agent-inbound-handler.service.tsapps/api/src/app/agents/services/bridge-executor.service.tsapps/api/src/app/agents/services/chat-sdk.service.tsapps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.tsapps/api/src/app/agents/usecases/update-agent/update-agent.usecase.tslibs/dal/src/repositories/agent/agent.entity.tslibs/dal/src/repositories/agent/agent.schema.tslibs/dal/src/repositories/conversation/conversation.entity.tslibs/dal/src/repositories/conversation/conversation.repository.tslibs/dal/src/repositories/conversation/conversation.schema.tspackages/novu/src/commands/init/create-app.tspackages/novu/src/commands/init/index.tspackages/novu/src/commands/init/templates/app-agent/ts/README-template.mdpackages/novu/src/commands/init/templates/app-agent/ts/app/api/novu/route.tspackages/novu/src/commands/init/templates/app-agent/ts/app/globals.csspackages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsxpackages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/index.tspackages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tspackages/novu/src/commands/init/templates/app-agent/ts/app/page.module.csspackages/novu/src/commands/init/templates/app-agent/ts/app/page.tsxpackages/novu/src/commands/init/templates/app-agent/ts/eslintrc.jsonpackages/novu/src/commands/init/templates/app-agent/ts/gitignorepackages/novu/src/commands/init/templates/app-agent/ts/next-env.d.tspackages/novu/src/commands/init/templates/app-agent/ts/next.config.mjspackages/novu/src/commands/init/templates/app-agent/ts/tsconfig.jsonpackages/novu/src/commands/init/templates/index.tspackages/novu/src/commands/init/templates/types.tspackages/novu/src/index.ts
| const channel = conversation.channels[0]; | ||
| const isFirstMessage = !channel?.firstPlatformMessageId; | ||
|
|
||
| if (isFirstMessage && config.reactionOnMessageReceived && message.id) { | ||
| thread.createSentMessageFromMessage(message).addReaction(config.reactionOnMessageReceived).catch((err) => { | ||
| this.logger.warn(err, `[agent:${agentId}] Failed to add ack reaction to first message`); | ||
| }); | ||
|
|
||
| this.conversationRepository | ||
| .setFirstPlatformMessageId(config.environmentId, config.organizationId, conversation._id, thread.id, message.id) | ||
| .catch((err) => { | ||
| this.logger.warn(err, `[agent:${agentId}] Failed to store firstPlatformMessageId`); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check how conversation.channels is initialized to confirm it's always defined
ast-grep --pattern $'createOrGetConversation($$$) {
$$$
}'
# Also search for the conversation entity/schema to understand the channels field
rg -n -A5 'channels.*:' --type ts -g '*conversation*'Repository: novuhq/novu
Length of output: 4293
🏁 Script executed:
# Search for where conversation comes from and any guards around channels
rg -n "conversation\?" apps/api/src/app/agents/services/agent-inbound-handler.service.ts -B 5 -A 5Repository: novuhq/novu
Length of output: 37
🏁 Script executed:
# Check if channels can ever be undefined by looking at ConversationDBModel/type definition
rg -n "channels.*ConversationChannel" libs/dal/src/repositories/conversation/ -A 2Repository: novuhq/novu
Length of output: 319
🏁 Script executed:
# Read the agent-inbound-handler.service.ts file around the problematic lines
cat -n apps/api/src/app/agents/services/agent-inbound-handler.service.ts | sed -n '60,100p'Repository: novuhq/novu
Length of output: 1921
🏁 Script executed:
# Check the full context - where does conversation come from in this handler
cat -n apps/api/src/app/agents/services/agent-inbound-handler.service.ts | sed -n '1,80p'Repository: novuhq/novu
Length of output: 3747
🏁 Script executed:
# Also check if there are any guards or assertions about channels existence
rg -n "channels" apps/api/src/app/agents/services/agent-conversation.service.ts -B 2 -A 2Repository: novuhq/novu
Length of output: 220
🏁 Script executed:
# Also check if this is a 'create' or 'getOrCreate' - does it always create with channels?
rg -n "async createOrGetConversation" apps/api/src/app/agents/services/ -A 60 | head -100Repository: novuhq/novu
Length of output: 6371
Add defensive check for conversation.channels access.
Direct access to conversation.channels[0] without verifying the array exists could throw a TypeError if channels is undefined. While newly created conversations always initialize with channels, existing conversations loaded from the database may not have this guarantee. The inconsistency between line 79 (direct access) and line 80 (optional chaining on channel) suggests the safest approach.
Proposed fix
- const channel = conversation.channels[0];
+ const channel = conversation.channels?.[0];
const isFirstMessage = !channel?.firstPlatformMessageId;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const channel = conversation.channels[0]; | |
| const isFirstMessage = !channel?.firstPlatformMessageId; | |
| if (isFirstMessage && config.reactionOnMessageReceived && message.id) { | |
| thread.createSentMessageFromMessage(message).addReaction(config.reactionOnMessageReceived).catch((err) => { | |
| this.logger.warn(err, `[agent:${agentId}] Failed to add ack reaction to first message`); | |
| }); | |
| this.conversationRepository | |
| .setFirstPlatformMessageId(config.environmentId, config.organizationId, conversation._id, thread.id, message.id) | |
| .catch((err) => { | |
| this.logger.warn(err, `[agent:${agentId}] Failed to store firstPlatformMessageId`); | |
| }); | |
| } | |
| const channel = conversation.channels?.[0]; | |
| const isFirstMessage = !channel?.firstPlatformMessageId; | |
| if (isFirstMessage && config.reactionOnMessageReceived && message.id) { | |
| thread.createSentMessageFromMessage(message).addReaction(config.reactionOnMessageReceived).catch((err) => { | |
| this.logger.warn(err, `[agent:${agentId}] Failed to add ack reaction to first message`); | |
| }); | |
| this.conversationRepository | |
| .setFirstPlatformMessageId(config.environmentId, config.organizationId, conversation._id, thread.id, message.id) | |
| .catch((err) => { | |
| this.logger.warn(err, `[agent:${agentId}] Failed to store firstPlatformMessageId`); | |
| }); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/api/src/app/agents/services/agent-inbound-handler.service.ts` around
lines 79 - 92, Guard access to conversation.channels before using
conversation.channels[0]: change the isFirstMessage logic to first verify
Array.isArray(conversation.channels) && conversation.channels.length > 0 (or
treat missing channels as no-first-message) and only then read the first
channel; update the block that uses channel (the channel variable,
isFirstMessage calculation, and subsequent calls to
thread.createSentMessageFromMessage(...) and
conversationRepository.setFirstPlatformMessageId(...)) to operate under that
guard (or handle undefined channel safely with optional chaining) so you never
directly index conversation.channels when it may be undefined.
commit: |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/novu/src/commands/init/templates/index.ts (1)
243-249:⚠️ Potential issue | 🔴 CriticalUpdate ESLint setup for Next.js 16 compatibility: remove
next lintand use flat config format.The scaffold targets Next.js 16.2.1 with ESLint 9, but generates broken lint configuration.
next lintwas removed in Next.js 16, and ESLint 9 requires flat config format (eslint.config.mjsoreslint.config.js), not.eslintrc.json. Fresh projects will fail atnpm run lint.To fix:
- Change lint script (line 199) from
'next lint'to'eslint .'- Generate
eslint.config.mjsinstead of.eslintrc.jsonin templates- Update lint setup to match Next.js 16 ESLint guide
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/index.ts` around lines 243 - 249, The scaffold currently targets Next.js 16 but keeps the old `next lint` and .eslintrc.json setup; update packageJson.scripts to use "eslint ." instead of "next lint" (modify packageJson.scripts where lint is set), replace generation of `.eslintrc.json` in the templates with a flat config file `eslint.config.mjs` (adjust the template creation logic that runs when eslint is truthy, and any code that writes the lint file), and ensure devDependencies and generated config follow the Next.js 16 ESLint flat-config guidance (see the code paths referencing the eslint variable and packageJson.devDependencies to add any required ESLint plugins/configs).
🧹 Nitpick comments (1)
packages/novu/src/commands/init/templates/index.ts (1)
160-162: Add the required blank line beforereturn.Tiny style nit, but this callback is on changed lines and the repo rule is explicit. As per coding guidelines, "Include a blank line before every return statement".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/index.ts` around lines 160 - 162, The reduce callback that builds `val` currently places the `return` immediately after the opening brace; add a blank line before the `return` statement inside the arrow function used in `Object.entries(envVars).reduce(...)` so the callback body follows the repo style rule (affects the reduce callback that references `acc`, `key`, `value`, and `os.EOL`).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 180-189: Replace the unpinned dependency declaration in the
baseDependencies object so `@novu/framework` is set to a concrete, tested version
or semver range instead of "latest"; update the entry in templates/index.ts (the
baseDependencies map where '@novu/framework' is currently 'latest') to the
verified version/range your CLI release is tested against (for example match the
same pinned range used for '@novu/nextjs' or the project's package.json), and
keep the conditional logic around isAgentTemplate unchanged.
---
Outside diff comments:
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 243-249: The scaffold currently targets Next.js 16 but keeps the
old `next lint` and .eslintrc.json setup; update packageJson.scripts to use
"eslint ." instead of "next lint" (modify packageJson.scripts where lint is
set), replace generation of `.eslintrc.json` in the templates with a flat config
file `eslint.config.mjs` (adjust the template creation logic that runs when
eslint is truthy, and any code that writes the lint file), and ensure
devDependencies and generated config follow the Next.js 16 ESLint flat-config
guidance (see the code paths referencing the eslint variable and
packageJson.devDependencies to add any required ESLint plugins/configs).
---
Nitpick comments:
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 160-162: The reduce callback that builds `val` currently places
the `return` immediately after the opening brace; add a blank line before the
`return` statement inside the arrow function used in
`Object.entries(envVars).reduce(...)` so the callback body follows the repo
style rule (affects the reduce callback that references `acc`, `key`, `value`,
and `os.EOL`).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 542c6bf0-cb10-47e4-b56b-346b7252e578
📒 Files selected for processing (2)
packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tspackages/novu/src/commands/init/templates/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts
| const baseDependencies: Record<string, string> = { | ||
| react: '^19', | ||
| 'react-dom': '^19', | ||
| next: version, | ||
| '@novu/framework': 'latest', | ||
| }; | ||
|
|
||
| if (!isAgentTemplate) { | ||
| baseDependencies['@novu/nextjs'] = '^2.5.0'; | ||
| } |
There was a problem hiding this comment.
Pin @novu/framework to a tested version range.
Scaffolding this dependency as "latest" makes novu init non-reproducible and can break newly generated apps as soon as @novu/framework publishes a semver-major. Please align it with a concrete version/range that this CLI release is verified against.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/novu/src/commands/init/templates/index.ts` around lines 180 - 189,
Replace the unpinned dependency declaration in the baseDependencies object so
`@novu/framework` is set to a concrete, tested version or semver range instead of
"latest"; update the entry in templates/index.ts (the baseDependencies map where
'@novu/framework' is currently 'latest') to the verified version/range your CLI
release is tested against (for example match the same pinned range used for
'@novu/nextjs' or the project's package.json), and keep the conditional logic
around isAgentTemplate unchanged.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
packages/novu/src/commands/init/templates/app-agent/ts/README-template.md (4)
41-41: Capitalize "Markdown" for style consistency.Markdown is a proper name and should be capitalized.
✏️ Proposed fix
-| `ctx.reply(content)` | Send a reply (text, markdown, or Card) | +| `ctx.reply(content)` | Send a reply (text, Markdown, or Card) |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md` at line 41, Update the README entry for `ctx.reply(content)` to capitalize "Markdown" for style consistency: find the table row containing "`ctx.reply(content)` | Send a reply (text, markdown, or Card)" and change "markdown" to "Markdown" so the cell reads "Send a reply (text, Markdown, or Card)".
7-15: Consider fixing ordered list numbering for markdown parsers.Some markdown parsers reset list numbering after a code block. The current numbering (1, 2, 3) triggers warnings because line 13 is treated as starting a new list.
📝 Proposed fix for consistent list numbering
Option 1: Indent the code block to keep it within the list item:
1. Start the development server: -```bash -npm run dev -``` + ```bash + npm run dev + ``` 2. Connect a chat platform in the [Novu Dashboard](https://dashboard.novu.co).Option 2: Use
1.for all items (auto-numbering):1. Start the development server: ```bash npm run dev-2. Connect a chat platform in the Novu Dashboard.
+1. Connect a chat platform in the Novu Dashboard.-3. Replace the demo handler in
app/novu/agents/support-agent.tswith your LLM call.
+1. Replace the demo handler inapp/novu/agents/support-agent.tswith your LLM call.</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In
@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
around lines 7 - 15, The numbered list in README-template.md breaks because the
fenced code block isn't indented under item 1; update the list so the code block
is part of the first item by indenting the triple-backtick fence and its
contents (or alternatively convert all list items to "1." auto-numbering) so the
items (1) the dev command code block and (2) "Connect a chat platform" and (3)
"Replace the demo handler in app/novu/agents/support-agent.ts" render as a
single ordered list without parser warnings.</details> --- `50-63`: **Consider adding the OpenAI import and installation note.** The example demonstrates calling OpenAI but omits the import statement and setup instructions, which could confuse developers copying the code. <details> <summary>📦 Proposed enhancement</summary> ```diff Replace the demo handler in `app/novu/agents/support-agent.ts` with your LLM call: +First, install the OpenAI SDK: + +```bash +npm install openai +``` + +Then update your handler: + ```typescript +import OpenAI from 'openai'; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); + onMessage: async (ctx) => { const response = await openai.chat.completions.create({🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md` around lines 50 - 63, Add an installation and import note and initialize the OpenAI client so the onMessage handler works: mention running "npm install openai", add an import for OpenAI and create the client (e.g., const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })), then leave the existing onMessage handler using openai.chat.completions.create and response.choices[0].message.content unchanged.
21-28: Add language identifier to code fence.The directory tree code block lacks a language identifier, which some markdown processors flag as a style violation.
📝 Proposed fix
-``` +```text app/ api/novu/route.ts → Bridge endpoint serving your agent🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md` around lines 21 - 28, The directory-tree code fence that starts with the "app/" listing in README-template.md is missing a language identifier; update the opening fence from ``` to ```text so the block becomes a fenced "text" code block (and apply the same change to any other similar directory-tree fences in the file to satisfy markdown style rules).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md`:
- Line 41: Update the README entry for `ctx.reply(content)` to capitalize
"Markdown" for style consistency: find the table row containing
"`ctx.reply(content)` | Send a reply (text, markdown, or Card)" and change
"markdown" to "Markdown" so the cell reads "Send a reply (text, Markdown, or
Card)".
- Around line 7-15: The numbered list in README-template.md breaks because the
fenced code block isn't indented under item 1; update the list so the code block
is part of the first item by indenting the triple-backtick fence and its
contents (or alternatively convert all list items to "1." auto-numbering) so the
items (1) the dev command code block and (2) "Connect a chat platform" and (3)
"Replace the demo handler in app/novu/agents/support-agent.ts" render as a
single ordered list without parser warnings.
- Around line 50-63: Add an installation and import note and initialize the
OpenAI client so the onMessage handler works: mention running "npm install
openai", add an import for OpenAI and create the client (e.g., const openai =
new OpenAI({ apiKey: process.env.OPENAI_API_KEY })), then leave the existing
onMessage handler using openai.chat.completions.create and
response.choices[0].message.content unchanged.
- Around line 21-28: The directory-tree code fence that starts with the "app/"
listing in README-template.md is missing a language identifier; update the
opening fence from ``` to ```text so the block becomes a fenced "text" code
block (and apply the same change to any other similar directory-tree fences in
the file to satisfy markdown style rules).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e912b1b-8bf2-4c9b-aded-fe0e5c62ab40
📒 Files selected for processing (2)
packages/novu/src/commands/init/templates/app-agent/ts/README-template.mdpackages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
packages/novu/src/commands/init/templates/app-agent/ts/README-template.md (1)
1-1: Filename breaks repository lowercase-dash naming convention.
README-template.mduses uppercase characters. Consider renaming to lowercase (for example,readme-template.md) if template resolution logic allows it.As per coding guidelines:
**/*: File/directory names should use lowercase with dashes (e.g.,components/auth-wizard).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md` at line 1, The file name README-template.md in the template bundle violates the repository naming convention (uppercase letters); rename the file to a lowercase-dash form such as readme-template.md and update any template resolution paths or references that expect README-template.md so the template loader (the app-agent template set) still finds it; ensure CI/template tooling still resolves the new name and update any references in package/command code or docs that point to README-template.md.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md`:
- Line 41: The docs line describing ctx.reply(content) uses lowercase
"markdown"; update the README-template.md entry for the `ctx.reply(content)` row
so the description reads "Send a reply (text, Markdown, or Card)"—i.e.,
capitalize "Markdown" while keeping the rest of the phrasing and the
`ctx.reply(content)` symbol unchanged.
- Line 48: The README instruction references the wrong handler filename; update
the sentence "Replace the demo handler in `app/novu/agents/support-agent.ts`
with your LLM call:" to point to the actual scaffolded file
`app/novu/agents/support-agent.tsx` so readers are directed to the correct
handler (ensure any other README mentions of support-agent.ts are changed to
support-agent.tsx).
- Line 21: The README-template.md contains a code fence for the project
structure that lacks a language specifier; update the fenced block in the
"project-structure" section by changing the opening triple backticks to include
the language token `text` (e.g., ```text) so the block around the "app/
api/novu/route.ts ..." listing renders correctly.
- Around line 7-15: The ordered list item "Start the development server:" in
README-template.md breaks linting because the fenced code block isn't indented
beneath item 1; update the block under that item (the line containing the npm
run dev example) to be indented so the opening ```bash, the command, and the
closing ``` are all nested under the "Start the development server:" list entry;
locate the block near the reference to app/novu/agents/support-agent.tsx and
adjust only the indentation of the fenced code block to fix the ordered-list
lint error.
---
Nitpick comments:
In `@packages/novu/src/commands/init/templates/app-agent/ts/README-template.md`:
- Line 1: The file name README-template.md in the template bundle violates the
repository naming convention (uppercase letters); rename the file to a
lowercase-dash form such as readme-template.md and update any template
resolution paths or references that expect README-template.md so the template
loader (the app-agent template set) still finds it; ensure CI/template tooling
still resolves the new name and update any references in package/command code or
docs that point to README-template.md.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 35fd823a-bb28-4178-a63a-83621d09fb22
📒 Files selected for processing (4)
packages/novu/src/commands/init/templates/app-agent/ts/README-template.mdpackages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsxpackages/novu/src/commands/init/templates/app-agent/ts/app/page.tsxpackages/novu/src/commands/init/templates/app-agent/ts/tsconfig.json
✅ Files skipped from review due to trivial changes (2)
- packages/novu/src/commands/init/templates/app-agent/ts/tsconfig.json
- packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
…V-7371 Extend `npx novu init` with an interactive template prompt and `--template` flag so users can scaffold either the existing notifications app or a new conversational agent app. The agent template ships a rich demo support-triage bot that showcases Cards, Buttons, onAction, ctx.metadata, ctx.resolve(), markdown replies, and conversation history — all working without an LLM API key. Made-with: Cursor
Made-with: Cursor
…eer dep" This reverts commit 7c950be.
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Made-with: Cursor
Switch support-agent from function-call API to JSX with per-file @jsxImportSource pragma. Set jsx to react-jsx in tsconfig. Made-with: Cursor
- Fix .ts → .tsx file extension in README LLM example section - Capitalize 'Markdown' in ctx.reply API table - Add 'text' language specifier to project structure code fence - Indent code block under ordered list item 1 for markdown parsers - Add blank line before return in layout.tsx and page.tsx templates - Consolidate duplicate zod dependency blocks into single condition Made-with: Cursor
121f29a to
e0468f5
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/novu/src/commands/init/index.ts`:
- Around line 152-173: The code currently accepts any non-empty program.template
and treats unknown values as 'notifications', so add explicit validation of
program.template (and templateChoice) after reading it and before calling
createApp: ensure the value is one of the supported choices ('agent' or
'notifications'), and if not, print a clear error (e.g., "Unsupported template:
<value>") and process.exit(1); reference the variables templateChoice and
program.template and perform the check in the init flow right after the
prompt/assignment and before the resolver/createApp call so unsupported values
fail fast.
In `@packages/novu/src/commands/init/templates/index.ts`:
- Around line 151-158: The envVars object (constructed near
TemplateTypeEnum.APP_AGENT) can include literal "undefined" for
NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER and NEXT_PUBLIC_NOVU_SUBSCRIBER_ID when
applicationId or userId are not set; change the logic that builds envVars so it
only includes NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER and
NEXT_PUBLIC_NOVU_SUBSCRIBER_ID keys when applicationId and userId are truthy
(e.g., conditional/filtered object construction or spread only when defined),
leaving only NOVU_SECRET_KEY when those values are absent to avoid writing
undefined placeholders from envVars into the generated .env.local.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 77cfaafc-505f-427f-a122-5865133b8aa8
📒 Files selected for processing (18)
packages/novu/src/commands/init/create-app.tspackages/novu/src/commands/init/index.tspackages/novu/src/commands/init/templates/app-agent/ts/README-template.mdpackages/novu/src/commands/init/templates/app-agent/ts/app/api/novu/route.tspackages/novu/src/commands/init/templates/app-agent/ts/app/globals.csspackages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsxpackages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/index.tspackages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsxpackages/novu/src/commands/init/templates/app-agent/ts/app/page.module.csspackages/novu/src/commands/init/templates/app-agent/ts/app/page.tsxpackages/novu/src/commands/init/templates/app-agent/ts/eslintrc.jsonpackages/novu/src/commands/init/templates/app-agent/ts/gitignorepackages/novu/src/commands/init/templates/app-agent/ts/next-env.d.tspackages/novu/src/commands/init/templates/app-agent/ts/next.config.mjspackages/novu/src/commands/init/templates/app-agent/ts/tsconfig.jsonpackages/novu/src/commands/init/templates/index.tspackages/novu/src/commands/init/templates/types.tspackages/novu/src/index.ts
✅ Files skipped from review due to trivial changes (12)
- packages/novu/src/commands/init/templates/app-agent/ts/eslintrc.json
- packages/novu/src/commands/init/templates/app-agent/ts/next-env.d.ts
- packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/index.ts
- packages/novu/src/commands/init/templates/app-agent/ts/next.config.mjs
- packages/novu/src/commands/init/templates/app-agent/ts/app/api/novu/route.ts
- packages/novu/src/commands/init/templates/app-agent/ts/gitignore
- packages/novu/src/commands/init/templates/app-agent/ts/app/globals.css
- packages/novu/src/commands/init/templates/app-agent/ts/app/page.module.css
- packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
- packages/novu/src/commands/init/templates/app-agent/ts/tsconfig.json
- packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
- packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/novu/src/commands/init/templates/types.ts
- packages/novu/src/index.ts
- packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx
| const envVars = | ||
| template === TemplateTypeEnum.APP_AGENT | ||
| ? { NOVU_SECRET_KEY: secretKey } | ||
| : { | ||
| NOVU_SECRET_KEY: secretKey, | ||
| NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER: applicationId, | ||
| NEXT_PUBLIC_NOVU_SUBSCRIBER_ID: userId, | ||
| }; |
There was a problem hiding this comment.
Avoid writing undefined placeholders into .env.local.
When novu init runs without a secret key, applicationId and userId are unset in the notifications flow, so this branch emits literal undefined values into the generated env file. That leaves the default scaffold with broken config out of the box.
🛠️ Proposed fix
const envVars =
template === TemplateTypeEnum.APP_AGENT
? { NOVU_SECRET_KEY: secretKey }
: {
NOVU_SECRET_KEY: secretKey,
- NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER: applicationId,
- NEXT_PUBLIC_NOVU_SUBSCRIBER_ID: userId,
+ NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER: applicationId ?? '',
+ NEXT_PUBLIC_NOVU_SUBSCRIBER_ID: userId ?? '',
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/novu/src/commands/init/templates/index.ts` around lines 151 - 158,
The envVars object (constructed near TemplateTypeEnum.APP_AGENT) can include
literal "undefined" for NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER and
NEXT_PUBLIC_NOVU_SUBSCRIBER_ID when applicationId or userId are not set; change
the logic that builds envVars so it only includes
NEXT_PUBLIC_NOVU_APPLICATION_IDENTIFIER and NEXT_PUBLIC_NOVU_SUBSCRIBER_ID keys
when applicationId and userId are truthy (e.g., conditional/filtered object
construction or spread only when defined), leaving only NOVU_SECRET_KEY when
those values are absent to avoid writing undefined placeholders from envVars
into the generated .env.local.
- Guard conversation.channels access with optional chaining - Resolve agent config once per request instead of redundant lookups - Add write-once semantics to setFirstPlatformMessageId - Validate --template flag in novu init CLI - Prevent undefined values in generated .env.local Made-with: Cursor
Summary
Extends
npx novu initwith an agent template option so users can scaffold a conversational AI agent app alongside the existing notifications template.--templateCLI flagapp-agenttemplate scaffolds a Next.js app with a bridge endpoint serving an agent via@novu/frameworkonActionhandler,ctx.metadata.set(),ctx.resolve(), markdown replies, and conversation history — all working without an LLM API keypackage.json(no Tailwind, React Email, or@novu/nextjs) and a simplified.env.local(justNOVU_SECRET_KEY)CLI flow
flowchart TD Start["npx novu init"] --> Name["Prompt: project name"] Name --> Flag{"--template flag?"} Flag -->|yes| Skip[Use flag value] Flag -->|no| Prompt["Prompt: Notifications / Agent"] Skip --> Create[createApp] Prompt --> Create Create -->|notifications| Existing[app-react-email] Create -->|agent| New[app-agent] Existing --> Install["npm install + git init"] New --> InstallFiles changed
packages/novu/src/index.ts--templateoptionpackages/novu/src/commands/init/index.tspackages/novu/src/commands/init/create-app.tspackages/novu/src/commands/init/templates/types.tsAPP_AGENTenum valuepackages/novu/src/commands/init/templates/index.tspackages/novu/src/commands/init/templates/app-agent/ts/*Test plan
npx novu init— verify template prompt appears, selecting "Notifications" produces the same output as beforenpx novu init --template agent— verify agent template scaffolds correctly with the rightpackage.jsondeps and.env.localnpx novu init --template notifications— verify it skips the prompt and uses the existing templatenpm run devworks in the scaffolded agent projectpnpm buildinpackages/novu) copiesapp-agentintodistMade with Cursor
What changed
The
npx novu initCLI now supports scaffolding conversational AI agent applications alongside the existing notifications template. Users can interactively choose between "Notifications" or "Agent," or use the new--templateflag to skip the prompt. A newapp-agenttemplate provides a pre-configured Next.js app with a Novu Bridge endpoint serving a demo agent that showcases SDK features: interactive Cards with action handlers, conversation metadata management, message history, and conversation resolution—all functional without an LLM API key.Affected areas
novu — Added
--templateCLI flag and interactive template selection in theinitcommand. ThecreateAppfunction now acceptstemplateChoiceand routes to either the existingapp-react-emailtemplate or the newapp-agenttemplate based on selection.novu (templates) — Introduced
APP_AGENTenum value toTemplateTypeEnum. Template-specific configuration now varies: the agent template outputs a minimal.env.local(onlyNOVU_SECRET_KEY), skips.githubdirectory copying, and includes a leanerpackage.jsonwithout Tailwind, React Email, or@novu/nextjs.novu (new template files) — Added 13 new files under
packages/novu/src/commands/init/templates/app-agent/ts/including Next.js layout/page components, API route handler, agent definition withonMessage/onAction/onResolvehandlers, TypeScript/ESLint/Next.js configuration, and documentation.Key technical decisions
@jsxImportSourcepragma andtsconfig.jsonconfigured withjsx: react-jsx.APP_REACT_EMAILandAPP_AGENTtemplates.^8to^9in generatedpackage.json.@novu/nextjsdependency to keep the scaffold lightweight.Testing
No new unit or integration tests were added. The PR includes a manual test plan to verify interactive prompt behavior,
--templateflag functionality, correct file scaffolding, successfulnpm run devexecution in the generated agent project, and build copying of theapp-agenttemplate todist.