Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 99 additions & 6 deletions src/commands/app/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const { importConsoleConfig } = require('../../lib/import')
const { loadAndValidateConfigFile } = require('../../lib/import-helper')
const { Octokit } = require('@octokit/rest')
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:init', { provider: 'debug' })
const { getAIRecommendation } = require('../../lib/template-recommendation')

const DEFAULT_WORKSPACE = 'Stage'

Expand Down Expand Up @@ -107,9 +108,22 @@ class InitCommand extends TemplatesCommand {
await this.withQuickstart(flags.repo, flags['github-pat'])
} else {
// 2. prompt for templates to be installed

// TODO: Modify this to be a prompt for natural language here -mg
const templates = await this.getTemplatesForFlags(flags)

// Ask user: Do you want to use natural language?
const { useNL } = await inquirer.prompt([{
Comment thread
chsrimanaswi marked this conversation as resolved.
Outdated
type: 'confirm',
name: 'useNL',
message: 'Do you want to describe your needs in natural language? (AI will recommend templates)',
default: false
}])

let templates
if (useNL) {
Comment thread
chsrimanaswi marked this conversation as resolved.
Outdated
templates = await this.getTemplatesWithAI(flags) // NEW AI FUNCTION
} else {
templates = await this.getTemplatesForFlags(flags) // EXISTING TABLE FLOW
}

// If no templates selected, init a standalone app
if (templates.length <= 0) {
flags['standalone-app'] = true
Expand Down Expand Up @@ -154,9 +168,21 @@ class InitCommand extends TemplatesCommand {
let templates
if (!flags.repo) {
// 5. get list of templates to install

// TODO: Modify this to be a prompt for natural language here -mg
templates = await this.getTemplatesForFlags(flags, orgSupportedServices)

// Ask user: Do you want to use natural language?
const { useNL } = await inquirer.prompt([{
type: 'confirm',
name: 'useNL',
message: 'Do you want to describe your needs in natural language? (AI will recommend templates)',
default: false
}])

if (useNL) {
templates = await this.getTemplatesWithAI(flags, orgSupportedServices) // NEW AI FUNCTION
} else {
templates = await this.getTemplatesForFlags(flags, orgSupportedServices) // EXISTING TABLE FLOW
}

// If no templates selected, init a standalone app
if (templates.length <= 0) {
flags['standalone-app'] = true
Expand Down Expand Up @@ -187,6 +213,73 @@ class InitCommand extends TemplatesCommand {
this.log(chalk.blue(chalk.bold(`Project initialized for Workspace ${workspace.name}, you can run 'aio app use -w <workspace>' to switch workspace.`)))
}

async getTemplatesWithAI (flags, orgSupportedServices = null) {
// Step 1: Ask user to describe their needs in natural language
const { userPrompt } = await inquirer.prompt([
{
type: 'input',
name: 'userPrompt',
message: 'Describe what you want to build (e.g., "I need a CRUD API" or "I want an event-driven app"):',
validate: (input) => {
if (!input || input.trim() === '') {
return 'Please provide a description of what you want to build.'
}
return true
}
}
])

const spinner = ora()
spinner.start(`Analyzing your request: "${userPrompt}"`)

try {
// Step 2: Call backend API via lib
const template = await getAIRecommendation(userPrompt)


Comment thread
chsrimanaswi marked this conversation as resolved.
Outdated

// Step 3: No template was returned
if (!template || !template.name) {
spinner.stop()
this.log(chalk.cyan('\n💡 AI could not find a matching template. Please explore templates from the options below:\n'))
return this.getTemplatesForFlags(flags, orgSupportedServices)
}

// Step 4: Display AI recommendation
spinner.succeed('Found matching template!')
this.log(chalk.bold('\n🤖 AI Recommendation:'))
this.log(chalk.dim(` Based on "${userPrompt}"\n`))
this.log(` ${chalk.bold(template.name)}`)
if (template.description) {
this.log(` ${chalk.dim(template.description)}`)
}
this.log('') // Empty line

// Step 5: Confirm with user
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Do you want to install this recommended template?',
default: false
}
])

if (confirm) {
return [template.name]
} else {
this.log(chalk.cyan('\n💡 Please explore templates from the options below:\n'))
return this.getTemplatesForFlags(flags, orgSupportedServices)
}

} catch (error) {
spinner.stop()
aioLogger.error('AI API error:', error)
this.log(chalk.cyan('\n💡 AI recommendation unavailable. Please explore templates from the options below:\n'))
return this.getTemplatesForFlags(flags, orgSupportedServices)
}
}

async getTemplatesForFlags (flags, orgSupportedServices = null) {
if (flags.template) {
return flags.template
Expand Down
4 changes: 3 additions & 1 deletion src/lib/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,7 @@ module.exports = {
EXTENSIONS_CONFIG_KEY: 'extensions',
// Adding tracking file constants
LAST_BUILT_ACTIONS_FILENAME: 'last-built-actions.json',
LAST_DEPLOYED_ACTIONS_FILENAME: 'last-deployed-actions.json'
LAST_DEPLOYED_ACTIONS_FILENAME: 'last-deployed-actions.json',
// Template recommendation API
defaultTemplateRecommendationApiUrl: 'https://development-918-aiappinit-stage.adobeioruntime.net/api/v1/web/recommend-api/recommend-template'
Comment thread
chsrimanaswi marked this conversation as resolved.
Outdated
}
53 changes: 53 additions & 0 deletions src/lib/template-recommendation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
Copyright 2025 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:template-recommendation', { provider: 'debug' })
const { defaultTemplateRecommendationApiUrl } = require('./defaults')

/**
* Calls the template recommendation API to get AI-based template suggestions
*
* @param {string} prompt - User's natural language description of what they want to build
* @param {string} [apiUrl] - Optional API URL (defaults to env var TEMPLATE_RECOMMENDATION_API or default URL from defaults.js)
* @returns {Promise<object>} Template recommendation from the API
* @throws {Error} If API call fails
*/
async function getAIRecommendation (prompt, apiUrl) {
const url = apiUrl || process.env.TEMPLATE_RECOMMENDATION_API || defaultTemplateRecommendationApiUrl

aioLogger.debug(`Calling template recommendation API: ${url}`)
aioLogger.debug(`Prompt: ${prompt}`)

const response = await fetch(url, {
method: 'POST',
headers: {
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.

Do we have any auth enabled for recommendation API? If not it can lead to DOS attacks very easily.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We initially wanted everyone to access the AI prompt. We will look into how to achieve the auth part.

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.

@sandeep-paliwal This would be good to discuss - I was thinking maybe we could pass the CLI token and do IMS auth on the backend. That of course would make it so we only allow the recommend flow for the logged in use cases and not noLogin. But maybe that's okay since noLogin is usually non-interactive?

'Content-Type': 'application/json'
},
body: JSON.stringify({ prompt })
})

if (!response.ok) {
const errorText = await response.text()
aioLogger.error(`API returned status ${response.status}: ${errorText}`)
throw new Error(`API returned status ${response.status}`)
}

const data = await response.json()
aioLogger.debug(`API response: ${JSON.stringify(data)}`)

return data.body || data
}

module.exports = {
getAIRecommendation
}