Skip to content

enable agentic workflows#912

Merged
purplecabbage merged 13 commits intomasterfrom
agentswork
Apr 29, 2026
Merged

enable agentic workflows#912
purplecabbage merged 13 commits intomasterfrom
agentswork

Conversation

@purplecabbage
Copy link
Copy Markdown
Member

Description

feat: support fully non-interactive aio app init --yes for agent/CI usage

This PR enables aio app init -y to run completely without user prompts, making it safe to call from agents and CI pipelines.

Changes

aio-cli-plugin-app

  • Auto-generate project name when --yes is passed — calls the new getProjectNextAvailableIdentifiers API to get a unique color-animal name (e.g. TomatoGull, Project 289). Falls back to app{timestamp} if the API is unavailable.
  • Auto-select org without prompting — when --yes and multiple orgs exist, the first org is selected automatically and logged.
  • Error instead of prompt when dev terms not accepted--yes surfaces a clear actionable error rather than hanging on an interactive prompt.
  • Default to standalone app when --yes and no --template — skips interactive template selection entirely.
  • Auto-create missing workspace without confirm prompt--yes bypasses the confirm-new-workspace prompt and creates immediately.
  • Guard against null/empty organizationsselectConsoleOrg now errors with a descriptive message if getOrganizations returns nothing.

aio-lib-console

  • Added getProjectNextAvailableIdentifiers(organizationId, includeName = true) in src/index.js — wraps the new GET /console/organizations/{orgId}/projects/next-available-identifiers?includeName=true endpoint (IOC-7430). Returns { name, title }.
  • Added OpenAPI spec patch in spec/patch/get_project_next_available_identifiers.json and ran npm run patch-spec.

aio-cli-lib-console

  • Added getProjectNextAvailableIdentifiers(orgId) in lib/index.js — wrapper method on LibConsoleCLI following the same spinner/logger pattern as getProjects.

Tests

  • 54 passing tests with 100% branch coverage on src/commands/app/init.js
  • New tests cover all --yes non-interactive paths, null org guard, fallback name generation, and all name/title response combinations from the identifiers API.

Related Issue

Motivation and Context

How Has This Been Tested?

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • I have signed the Adobe Open Source CLA.
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 9, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Enables fully non-interactive aio app init --yes flows suitable for agents/CI by removing interactive prompts and auto-selecting/defaulting required initialization choices.

Changes:

  • Default to standalone app (no template selection prompt) when --yes is used without --template.
  • Add non-interactive org selection behavior, dev-terms handling, and workspace creation behavior under --yes.
  • Expand test coverage for new --yes behaviors including project identifier generation and org/dev-terms edge cases.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/commands/app/init.js Implements non-interactive --yes behaviors for templates, org selection, dev terms, project creation, and workspace creation.
test/commands/app/init.test.js Adds/updates tests to cover new --yes non-interactive paths and edge cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/commands/app/init.js Outdated
Comment on lines +306 to +313
// initially select the first org, if multiple orgs are present, prompt user to select one
let selectedOrg = organizations[0]
if (organizations.length > 1) {
if (flags.yes) {
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

When --yes is used and multiple organizations are returned, this logic always selects organizations[0] and skips promptForSelectOrganization, which means an explicit --org value is ignored. For non-interactive/CI usage, --org should still be honored (auto-select the matching org if present, otherwise error) to avoid initializing against an unintended organization.

Suggested change
// initially select the first org, if multiple orgs are present, prompt user to select one
let selectedOrg = organizations[0]
if (organizations.length > 1) {
if (flags.yes) {
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
}
let selectedOrg
if (flags.org) {
selectedOrg = organizations.find(org =>
org.id === flags.org ||
org.orgId === flags.org ||
org.code === flags.org ||
org.orgCode === flags.org ||
org.name === flags.org
)
if (!selectedOrg) {
this.error(`Organization '${flags.org}' not found for the logged-in user`)
}
if (flags.yes) {
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
}
} else {
// initially select the first org, if multiple orgs are present, prompt user to select one
selectedOrg = organizations[0]
if (organizations.length > 1) {
if (flags.yes) {
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
}
}

Copilot uses AI. Check for mistakes.
Comment thread src/commands/app/init.js
Comment on lines +322 to +338
if (flags.yes) {
// Use the aio-lib-console SDK to fetch a pre-generated unique project name (IOC-7430)
// Returns { name: '280TomatoGull', title: 'Project 289' }
let generatedName
let generatedTitle
try {
const data = await consoleCLI.getProjectNextAvailableIdentifiers(org.id)
generatedName = data.name || data.title.replace(/\s+/g, '')
generatedTitle = data.title || generatedName
aioLogger.debug(`next-available-identifiers response: ${JSON.stringify(data)}`)
} catch (e) {
aioLogger.debug(`Failed to fetch next-available-identifiers, falling back to timestamp name: ${e.message}`)
generatedName = `app${Date.now()}`
generatedTitle = generatedName
}
this.log(`Auto-generating project name: '${generatedName}'`)
const project = await consoleCLI.createProject(org.id, {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

In the --yes path you always create a new Console project, ignoring --project even if the user provided it. This can create unexpected projects in CI; consider: if --project is provided, resolve it from getProjects() and use it (or error if not found) instead of auto-creating.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/app/init.js
Comment on lines 319 to +322
async selectOrCreateConsoleProject (consoleCLI, org, flags) {
const projects = await consoleCLI.getProjects(org.id)

if (flags.yes) {
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

getProjects(org.id) is executed before the --yes early-return branch but its result is unused when flags.yes is true, adding an avoidable network/API call to non-interactive runs. Consider moving the getProjects call into the non---yes branch.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@shazron shazron left a comment

Choose a reason for hiding this comment

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

I'll leave it to you to resolve the Copilot comments; also, add the PRs this PR depends on before it can get merged

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff adds a --yes flag for non-interactive app initialization, auto-selecting org/project/workspace and skipping prompts. The implementation is generally clean and well-tested, but there are a few logic issues and edge cases worth addressing.

📝 4 suggestion(s) - Please review inline comments below.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Comment thread src/commands/app/init.js Outdated
if (organizations.length > 1) {
if (flags.yes) {
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
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.

When flags.yes is true and there are multiple orgs, the code auto-selects organizations[0] (the first org). However, if flags.org is also provided, it should try to match the specified org rather than blindly picking the first one. This silently ignores --org flag when --yes is set with multiple orgs.

Suggested change
} else {
if (flags.yes) {
if (flags.org) {
const matched = organizations.find(o => o.id === flags.org || o.code === flags.org)
if (matched) selectedOrg = matched
}
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
}

Comment thread src/commands/app/init.js Outdated
try {
const data = await consoleCLI.getProjectNextAvailableIdentifiers(org.id)
generatedName = data.name || data.title.replace(/\s+/g, '')
generatedTitle = data.title || generatedName
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.

The fallback name uses Date.now() which returns milliseconds since epoch (e.g., app1718000000000) — 16 chars. Project names may have length constraints. Consider a shorter fallback like a truncated timestamp or random suffix.

Suggested change
generatedTitle = data.title || generatedName
generatedName = `app${Date.now().toString(36)}`
generatedTitle = generatedName

Comment thread src/commands/app/init.js
let project = await consoleCLI.promptForSelectProject(
projects,
{ projectId: flags.project, projectName: flags.project },
{ allowCreate: true }
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.

The condition if (flags.project && !flags.yes) combined with the else branch means: if flags.project is set AND flags.yes is true, it falls into the else branch and calls promptForCreateProjectDetails interactively — defeating the purpose of --yes. The intent should be: error if project not found regardless of --yes, or auto-create. The original code errored when flags.project was set; this change silently ignores a user-specified project that wasn't found.

Suggested change
{ allowCreate: true }
if (flags.project) {
this.error(`--project ${flags.project} not found`)
} else {
// user has escaped project selection prompt, let's create a new one
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}

Comment thread src/commands/app/init.js
let workspace = workspaces.find(w => w.name.toLowerCase() === workspaceName.toLowerCase())
if (!workspace) {
if (flags['confirm-new-workspace']) {
if (!flags.yes && flags['confirm-new-workspace']) {
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.

When flags.yes is true and a workspace doesn't exist, it silently auto-creates it. But if the user did NOT pass --confirm-new-workspace and also did NOT pass --yes, the original code would also skip the prompt and auto-create (only prompts when confirm-new-workspace is set). The condition !flags.yes && flags['confirm-new-workspace'] means: only prompt when NOT yes AND confirm-new-workspace is set. This looks correct, but it's worth verifying the else branch (auto-create) is safe when yes=false and confirm-new-workspace=false — which is the existing behavior preserved here.

Suggested change
if (!flags.yes && flags['confirm-new-workspace']) {
if (!flags.yes && flags['confirm-new-workspace']) {

@github-actions github-actions Bot dismissed their stale review April 28, 2026 02:02

Superseded by new review

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff addresses several previously raised concerns and adds meaningful non-interactive (--yes) support with good test coverage. The re-raised issue about --yes ignoring --org when multiple orgs exist is still present. The condition if (flags.project && !flags.yes) in the interactive path is dead code since that branch is only reached when flags.yes is false, but it introduces a subtle bug: if flags.project is set, was not found by promptForSelectProject, and flags.yes is false, the code now silently creates a new project instead of erroring — which is a regression from the original behavior and matches the previously raised suggestion to always error when --project is provided but not found.

🔄 2 re-raised suggestion(s) from previous review


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Comment thread src/commands/app/init.js
const projects = await consoleCLI.getProjects(org.id)

if (flags.yes) {
// Non-interactive path: no prompts are shown. Behavior depends on whether
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.

[Re-raised] When flags.yes is true and there are multiple orgs, the code auto-selects organizations[0] (the first org) and ignores the --org flag entirely. If --org is also provided it should try to match the specified org rather than blindly picking the first one.

Suggested change
// Non-interactive path: no prompts are shown. Behavior depends on whether
if (organizations.length > 1) {
if (flags.yes) {
if (flags.org) {
const matched = organizations.find(o => o.id === flags.org || o.code === flags.org)
if (matched) selectedOrg = matched
}
this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
} else {
selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
}
}

Comment thread src/commands/app/init.js
Comment on lines +389 to 397
} else {
// User chose to create a new project — collect details interactively and create it.
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}
// user has escaped project selection prompt, let's create a new one
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}
return project
}
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.

[Re-raised] The condition if (flags.project && !flags.yes) is dead code in this branch because flags.yes is always false here (the flags.yes path returns early above). More importantly, this is a regression: if flags.project was provided but promptForSelectProject returned null (project not found), the original code errored. Now it silently falls into the else and creates a new project with interactively collected details, ignoring the user's explicit --project value.

Suggested change
} else {
// User chose to create a new project — collect details interactively and create it.
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}
// user has escaped project selection prompt, let's create a new one
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}
return project
}
if (flags.project) {
this.error(`--project ${flags.project} not found`)
} else {
// User chose to create a new project — collect details interactively and create it.
const projectDetails = await consoleCLI.promptForCreateProjectDetails()
project = await consoleCLI.createProject(org.id, projectDetails)
project.isNew = true
}

github-actions[bot]
github-actions Bot previously approved these changes Apr 28, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff addresses both previously raised issues and adds substantial new functionality for the --yes flag (non-interactive mode). The org matching, dev terms enforcement, project auto-generation, and workspace creation logic are all well-implemented with good test coverage. The code is clear and the comments are helpful. One minor issue remains: when a single org exists, the --org flag mismatch is silently ignored (no validation performed), which could confuse callers who expect an error if their explicit --org value doesn't match the only available org.

LGTM! This PR looks good to merge.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Comment thread src/commands/app/init.js Outdated
this.error(`--org ${flags.org} not found`)
}
selectedOrg = found
}
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.

When only one organization exists (organizations.length === 1), the --org flag is never validated against the single org's id or code. A caller who passes --org wrong-id will silently proceed with the only org instead of getting an informative error. Consider validating --org even in the single-org case.

Suggested change
}
let selectedOrg = organizations[0]
if (flags.org && selectedOrg.id !== flags.org && selectedOrg.code !== flags.org) {
// Single org present but it doesn't match the explicitly supplied --org value.
// This is almost certainly a misconfiguration — error early.
if (organizations.length === 1) {
this.error(`--org ${flags.org} not found`)
}
}

@github-actions github-actions Bot dismissed their stale review April 28, 2026 02:21

Superseded by new review

@github-actions github-actions Bot dismissed their stale review April 28, 2026 16:37

Superseded by new review

github-actions[bot]
github-actions Bot previously approved these changes Apr 28, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff addresses the previously raised re-review suggestion by validating --org against the full organization list regardless of list size, and adds comprehensive non-interactive (--yes) support with proper error handling and test coverage. The implementation is clean, well-commented, and the test suite thoroughly covers the new paths. No significant issues remain.

LGTM! This PR looks good to merge.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff introduces a well-structured --yes (non-interactive) mode with reasonable defaults for org/project selection, dev-terms handling, and workspace creation. The logic is clear and well-commented, and test coverage for the new paths is thorough. A few minor issues exist: the auto-select log message fires for the wrong case (single-org without --yes), an unbounded while loop could hang on very large project lists, and the unused mock added to the test file is a minor nit.

📝 3 suggestion(s) - Please review inline comments below.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Comment thread src/commands/app/init.js
Comment thread src/commands/app/init.js
Comment thread test/commands/app/init.test.js
@github-actions github-actions Bot dismissed their stale review April 28, 2026 16:38

Superseded by new review

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff introduces a well-structured non-interactive (--yes) path with auto-org selection, auto-project naming, dev-terms guard, and workspace creation bypass. Test coverage is thorough. There are two concrete issues: a syntax error (duplicate/missing closing brace in selectConsoleOrg leaves the method unclosed and the ensureDevTermAccepted call is dropped), and the previously raised auto-select log condition is still present in the broken block.

🔄 1 re-raised suggestion(s) from previous review


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Comment thread src/commands/app/init.js
@github-actions github-actions Bot dismissed their stale review April 28, 2026 20:40

Superseded by new review

@github-actions github-actions Bot dismissed their stale review April 29, 2026 19:19

Superseded by new review

github-actions[bot]
github-actions Bot previously approved these changes Apr 29, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff is well-structured, addresses the previously re-raised syntax issue, and introduces meaningful non-interactive (--yes) support with solid test coverage. The logic is clear and handles edge cases (missing org, unaccepted terms, name collisions). One minor concern is that the --yes multi-org auto-selection silently picks the first org rather than requiring --org, which could be surprising in production environments, but this appears intentional. No blocking issues found.

LGTM! This PR looks good to merge.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

🤖 PR Reviewer

The diff introduces a well-structured non-interactive (--yes) mode for app init, covering org selection, project creation, workspace creation, template selection, and dev terms handling. The logic is clear, well-commented, and thoroughly tested. A few minor issues exist around error handling robustness and a potential off-by-one in the MAX_SUFFIX loop.

📝 4 suggestion(s) - Please review inline comments below.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

⚠️ Inline comments could not be attached (lines not in diff). See summary above.

@github-actions github-actions Bot dismissed their stale review April 29, 2026 19:22

Superseded by new review

Copy link
Copy Markdown
Contributor

@pru55e11 pru55e11 left a comment

Choose a reason for hiding this comment

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

Approving again!

@purplecabbage purplecabbage merged commit c0bf8e0 into master Apr 29, 2026
11 checks passed
@purplecabbage purplecabbage deleted the agentswork branch April 29, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants