Skip to content

feat(root): add agent template to novu init CLI scaffolding fixes NV-7371#10734

Merged
ChmaraX merged 10 commits intonextfrom
nv-7371-add-agent-template-to-novu-init-cli-scaffolding
Apr 15, 2026
Merged

feat(root): add agent template to novu init CLI scaffolding fixes NV-7371#10734
ChmaraX merged 10 commits intonextfrom
nv-7371-add-agent-template-to-novu-init-cli-scaffolding

Conversation

@ChmaraX
Copy link
Copy Markdown
Contributor

@ChmaraX ChmaraX commented Apr 15, 2026

Summary

Extends npx novu init with an agent template option so users can scaffold a conversational AI agent app alongside the existing notifications template.

  • Adds interactive template prompt ("Notifications" / "Agent") and --template CLI flag
  • New app-agent template scaffolds a Next.js app with a bridge endpoint serving an agent via @novu/framework
  • Demo agent showcases the full SDK surface: interactive Cards with Buttons, onAction handler, ctx.metadata.set(), ctx.resolve(), markdown replies, and conversation history — all working without an LLM API key
  • Agent template generates a leaner package.json (no Tailwind, React Email, or @novu/nextjs) and a simplified .env.local (just NOVU_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 --> Install
Loading

Files changed

File Change
packages/novu/src/index.ts Added --template option
packages/novu/src/commands/init/index.ts Template prompt + pass-through
packages/novu/src/commands/init/create-app.ts Accept template choice
packages/novu/src/commands/init/templates/types.ts APP_AGENT enum value
packages/novu/src/commands/init/templates/index.ts Agent branch for deps, env, skip GitHub Action
packages/novu/src/commands/init/templates/app-agent/ts/* 13 new template files

Test plan

  • Run npx novu init — verify template prompt appears, selecting "Notifications" produces the same output as before
  • Run npx novu init --template agent — verify agent template scaffolds correctly with the right package.json deps and .env.local
  • Run npx novu init --template notifications — verify it skips the prompt and uses the existing template
  • Verify npm run dev works in the scaffolded agent project
  • Verify the build script (pnpm build in packages/novu) copies app-agent into dist

Made with Cursor

What changed

The npx novu init CLI now supports scaffolding conversational AI agent applications alongside the existing notifications template. Users can interactively choose between "Notifications" or "Agent," or use the new --template flag to skip the prompt. A new app-agent template 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 --template CLI flag and interactive template selection in the init command. The createApp function now accepts templateChoice and routes to either the existing app-react-email template or the new app-agent template based on selection.

novu (templates) — Introduced APP_AGENT enum value to TemplateTypeEnum. Template-specific configuration now varies: the agent template outputs a minimal .env.local (only NOVU_SECRET_KEY), skips .github directory copying, and includes a leaner package.json without 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 with onMessage/onAction/onResolve handlers, TypeScript/ESLint/Next.js configuration, and documentation.

Key technical decisions

  • Agent template uses JSX syntax with per-file @jsxImportSource pragma and tsconfig.json configured with jsx: react-jsx.
  • Zod dependencies are now included for both APP_REACT_EMAIL and APP_AGENT templates.
  • ESLint version updated from ^8 to ^9 in generated package.json.
  • Agent template intentionally omits @novu/nextjs dependency 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, --template flag functionality, correct file scaffolding, successful npm run dev execution in the generated agent project, and build copying of the app-agent template to dist.

@linear
Copy link
Copy Markdown

linear bot commented Apr 15, 2026

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 15, 2026

Deploy Preview for dashboard-v2-novu-staging canceled.

Name Link
🔨 Latest commit 085ede6
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69dfc4037ea5bb0008a6d14a

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 15, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
CLI Template Selection
packages/novu/src/commands/init/index.ts, packages/novu/src/commands/init/create-app.ts, packages/novu/src/index.ts
Extended init command with optional template CLI option; added user prompt to select between 'notifications' and 'agent' templates when not provided; threaded templateChoice parameter through init and createApp functions with validation.
Template Type Registry & Installation Logic
packages/novu/src/commands/init/templates/types.ts, packages/novu/src/commands/init/templates/index.ts
Added APP_AGENT enum variant; refactored template installation to conditionally manage environment variables (agent template uses only NOVU_SECRET_KEY), skip .github directory for agents, and selectively install dependencies (e.g., @novu/nextjs excluded for agent template).
Agent Template Scaffolding
packages/novu/src/commands/init/templates/app-agent/ts/app/*, packages/novu/src/commands/init/templates/app-agent/ts/\{eslintrc.json,gitignore,next-env.d.ts,next.config.mjs,tsconfig.json\}
Next.js app boilerplate including layout, pages, global styles, agent API route handler (/api/novu), support agent implementation with conversation state management and multi-handler support (onMessage, onAction, onResolve), and full configuration files.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • scopsy
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits format with valid type (feat) and scope (root), includes a clear lowercase imperative description, and ends with a Linear ticket reference (fixes NV-7371) as required.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 return statement 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.Mixed provides 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 as string | null for 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 and return spacing 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 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/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 reactToMessage and removeReaction should/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 for ctx.action and value.

The non-null assertions (ctx.action! and value!) 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_EMAIL block and the isAgentTemplate block 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6e7bfa5 and b710d94.

📒 Files selected for processing (34)
  • apps/api/src/app/agents/agents.module.ts
  • apps/api/src/app/agents/dtos/agent-behavior.dto.ts
  • apps/api/src/app/agents/e2e/agent-reply.e2e.ts
  • apps/api/src/app/agents/e2e/agent-webhook.e2e.ts
  • apps/api/src/app/agents/e2e/agents.e2e.ts
  • apps/api/src/app/agents/services/agent-config-resolver.service.ts
  • apps/api/src/app/agents/services/agent-inbound-handler.service.ts
  • apps/api/src/app/agents/services/bridge-executor.service.ts
  • apps/api/src/app/agents/services/chat-sdk.service.ts
  • apps/api/src/app/agents/usecases/handle-agent-reply/handle-agent-reply.usecase.ts
  • apps/api/src/app/agents/usecases/update-agent/update-agent.usecase.ts
  • libs/dal/src/repositories/agent/agent.entity.ts
  • libs/dal/src/repositories/agent/agent.schema.ts
  • libs/dal/src/repositories/conversation/conversation.entity.ts
  • libs/dal/src/repositories/conversation/conversation.repository.ts
  • libs/dal/src/repositories/conversation/conversation.schema.ts
  • packages/novu/src/commands/init/create-app.ts
  • packages/novu/src/commands/init/index.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
  • packages/novu/src/commands/init/templates/app-agent/ts/app/api/novu/route.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/app/globals.css
  • packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/index.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/app/page.module.css
  • packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/eslintrc.json
  • packages/novu/src/commands/init/templates/app-agent/ts/gitignore
  • packages/novu/src/commands/init/templates/app-agent/ts/next-env.d.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/next.config.mjs
  • packages/novu/src/commands/init/templates/app-agent/ts/tsconfig.json
  • packages/novu/src/commands/init/templates/index.ts
  • packages/novu/src/commands/init/templates/types.ts
  • packages/novu/src/index.ts

Comment on lines +79 to +92
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`);
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 5

Repository: 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 2

Repository: 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 2

Repository: 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 -100

Repository: 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.

Suggested change
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.

Comment thread libs/dal/src/repositories/conversation/conversation.repository.ts
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 15, 2026

Open in StackBlitz

npm i https://pkg.pr.new/novu@10734

commit: 085ede6

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🔴 Critical

Update ESLint setup for Next.js 16 compatibility: remove next lint and use flat config format.

The scaffold targets Next.js 16.2.1 with ESLint 9, but generates broken lint configuration. next lint was removed in Next.js 16, and ESLint 9 requires flat config format (eslint.config.mjs or eslint.config.js), not .eslintrc.json. Fresh projects will fail at npm run lint.

To fix:

  1. Change lint script (line 199) from 'next lint' to 'eslint .'
  2. Generate eslint.config.mjs instead of .eslintrc.json in templates
  3. 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 before return.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b710d94 and b503e42.

📒 Files selected for processing (2)
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.ts
  • packages/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

Comment on lines +180 to +189
const baseDependencies: Record<string, string> = {
react: '^19',
'react-dom': '^19',
next: version,
'@novu/framework': 'latest',
};

if (!isAgentTemplate) {
baseDependencies['@novu/nextjs'] = '^2.5.0';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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.ts with your LLM call.
+1. Replace the demo handler in app/novu/agents/support-agent.ts with 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

📥 Commits

Reviewing files that changed from the base of the PR and between b503e42 and 0424b09.

📒 Files selected for processing (2)
  • packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
  • packages/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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.md uses 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0424b09 and 121f29a.

📒 Files selected for processing (4)
  • packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
  • packages/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

Comment thread packages/novu/src/commands/init/templates/app-agent/ts/README-template.md Outdated
Comment thread packages/novu/src/commands/init/templates/app-agent/ts/README-template.md Outdated
Comment thread packages/novu/src/commands/init/templates/app-agent/ts/README-template.md Outdated
ChmaraX added 9 commits April 15, 2026 19:28
…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
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
@ChmaraX ChmaraX force-pushed the nv-7371-add-agent-template-to-novu-init-cli-scaffolding branch from 121f29a to e0468f5 Compare April 15, 2026 16:29
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 121f29a and e0468f5.

📒 Files selected for processing (18)
  • packages/novu/src/commands/init/create-app.ts
  • packages/novu/src/commands/init/index.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/README-template.md
  • packages/novu/src/commands/init/templates/app-agent/ts/app/api/novu/route.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/app/globals.css
  • packages/novu/src/commands/init/templates/app-agent/ts/app/layout.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/index.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/app/novu/agents/support-agent.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/app/page.module.css
  • packages/novu/src/commands/init/templates/app-agent/ts/app/page.tsx
  • packages/novu/src/commands/init/templates/app-agent/ts/eslintrc.json
  • packages/novu/src/commands/init/templates/app-agent/ts/gitignore
  • packages/novu/src/commands/init/templates/app-agent/ts/next-env.d.ts
  • packages/novu/src/commands/init/templates/app-agent/ts/next.config.mjs
  • packages/novu/src/commands/init/templates/app-agent/ts/tsconfig.json
  • packages/novu/src/commands/init/templates/index.ts
  • packages/novu/src/commands/init/templates/types.ts
  • packages/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

Comment thread packages/novu/src/commands/init/index.ts
Comment on lines +151 to +158
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,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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
@ChmaraX ChmaraX merged commit 840763a into next Apr 15, 2026
37 checks passed
@ChmaraX ChmaraX deleted the nv-7371-add-agent-template-to-novu-init-cli-scaffolding branch April 15, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant