Add interactive agent mode to Netlify CLI for managing AI agents#8346
Add interactive agent mode to Netlify CLI for managing AI agents#8346netlify-coding[bot] wants to merge 9 commits into
Conversation
…fb51b202629a56a101e6e)
✅ Deploy Preview for steady-pastelito-09e39e ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds an interactive Estimated code review effort: 3 (Moderate) | ~25 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
📊 Benchmark resultsComparing with f214e69
|
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
src/commands/agents/agents-interactive.ts (3)
88-106: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
result_diffis defined on the session but never displayed.
AgentRunnerSession(types.ts) carries an optionalresult_diffalongsideresult, butprintSessionResultonly ever printssession.result. If agent runs commonly produce a diff, consider surfacing it here too.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/agents/agents-interactive.ts` around lines 88 - 106, Update printSessionResult to display the optional session.result_diff alongside session.result for completed and error sessions, preserving the existing formatting and cancellation behavior. Use the AgentRunnerSession result_diff field directly and avoid changing unrelated session output.
181-186: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider guarding interactive prompts behind a TTY check.
agentsInteractiveunconditionally callsconsole.clear()andinquirer.promptfor the initial and every follow-up prompt. If invoked in a non-interactive environment (CI, piped stdin), this can hang or produce a confusing failure rather than a clear error message.Also applies to: 217-233
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/agents/agents-interactive.ts` around lines 181 - 186, Guard the interactive flow in agentsInteractive, including the initial askPrompt and follow-up prompt handling around the referenced sections, with a TTY check before calling console.clear() or prompting. When stdin/stdout is non-interactive, exit through a clear error message instead of invoking inquirer.prompt; preserve the existing behavior for interactive terminals.
15-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate terminal-state literal instead of reusing
constants.ts.
TERMINAL_SESSION_STATESre-declares terminal states already derivable fromSESSION_STATES/SessionStateinsrc/commands/agents/constants.ts. If that canonical list changes, this local copy can silently drift out of sync.♻️ Proposed fix
-const TERMINAL_SESSION_STATES = new Set(['done', 'error', 'cancelled']) +import { SESSION_STATES } from './constants.js' + +const TERMINAL_SESSION_STATES = new Set<SessionState>(SESSION_STATES.filter((s) => s !== 'new' && s !== 'running'))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/commands/agents/agents-interactive.ts` around lines 15 - 16, Update TERMINAL_SESSION_STATES in the agents interactive command to derive or reuse the canonical terminal-state values from SESSION_STATES/SessionState in constants.ts, removing the duplicated string literals and keeping terminal-state checks synchronized with the shared definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/commands/agents/agents-interactive.ts`:
- Line 1: Run the project’s Prettier formatter on agents-interactive.ts and
apply its formatting to the long template literal and the multi-line log(...)
call, preserving the existing behavior and matching the file’s surrounding
style.
- Around line 57-67: Validate the result returned by request in createRun before
casting or returning it as AgentRunner. Treat an undefined or malformed response
as an explicit error, so callers never receive an undefined runner and later
runner.id accesses remain safe.
- Around line 41-58: The request helper lacks cancellation, and the session
polling flows can run indefinitely. Update request() to use an AbortSignal with
a bounded timeout, and add a deadline to waitForSession() and followSession() so
retries stop when the deadline is reached while preserving their existing
success and terminal-state behavior.
- Around line 81-86: The getLatestSession request must explicitly request
sessions in descending order so sessions?.[0] is the newest session. Add the
order_by=desc query parameter to the existing sessions endpoint while preserving
the current pagination and return behavior.
---
Nitpick comments:
In `@src/commands/agents/agents-interactive.ts`:
- Around line 88-106: Update printSessionResult to display the optional
session.result_diff alongside session.result for completed and error sessions,
preserving the existing formatting and cancellation behavior. Use the
AgentRunnerSession result_diff field directly and avoid changing unrelated
session output.
- Around line 181-186: Guard the interactive flow in agentsInteractive,
including the initial askPrompt and follow-up prompt handling around the
referenced sections, with a TTY check before calling console.clear() or
prompting. When stdin/stdout is non-interactive, exit through a clear error
message instead of invoking inquirer.prompt; preserve the existing behavior for
interactive terminals.
- Around line 15-16: Update TERMINAL_SESSION_STATES in the agents interactive
command to derive or reuse the canonical terminal-state values from
SESSION_STATES/SessionState in constants.ts, removing the duplicated string
literals and keeping terminal-state checks synchronized with the shared
definition.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 784041f0-ca9d-4f93-8db9-9849ac8a8ed2
📒 Files selected for processing (32)
docs/commands/agents.mddocs/commands/api.mddocs/commands/blobs.mddocs/commands/build.mddocs/commands/claim.mddocs/commands/clone.mddocs/commands/completion.mddocs/commands/create.mddocs/commands/database.mddocs/commands/deploy.mddocs/commands/dev.mddocs/commands/env.mddocs/commands/functions.mddocs/commands/init.mddocs/commands/link.mddocs/commands/login.mddocs/commands/logout.mddocs/commands/logs.mddocs/commands/open.mddocs/commands/recipes.mddocs/commands/serve.mddocs/commands/sites.mddocs/commands/status.mddocs/commands/switch.mddocs/commands/teams.mddocs/commands/unlink.mddocs/commands/watch.mddocs/index.htmldocs/index.mdnetlify.tomlsrc/commands/agents/agents-interactive.tssrc/commands/agents/agents.ts
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
netlify/blueprints(manual)
💤 Files with no reviewable changes (28)
- docs/commands/recipes.md
- docs/commands/build.md
- docs/commands/switch.md
- docs/commands/serve.md
- docs/commands/database.md
- docs/commands/status.md
- docs/commands/deploy.md
- docs/commands/sites.md
- docs/commands/login.md
- docs/commands/dev.md
- docs/commands/logs.md
- docs/commands/open.md
- docs/commands/completion.md
- docs/commands/clone.md
- docs/commands/env.md
- docs/commands/create.md
- docs/commands/claim.md
- docs/commands/teams.md
- docs/commands/logout.md
- docs/index.md
- docs/commands/agents.md
- docs/commands/init.md
- docs/commands/watch.md
- docs/commands/functions.md
- docs/commands/api.md
- docs/commands/link.md
- docs/commands/blobs.md
- docs/commands/unlink.md
| @@ -0,0 +1,246 @@ | |||
| import type { OptionValues } from 'commander' | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Prettier check is failing on this file.
CI reports Prettier --check failed for this file. Line 183's long template literal and the odd multi-line indentation of the log(...) call at Lines 241-243 (compared to the single-line style used elsewhere in the file) are likely culprits. Run npm run format/prettier --write before merge.
Also applies to: 241-243
🧰 Tools
🪛 GitHub Actions: Format / 0_Format.txt
[error] 1-1: Prettier --check failed. Code style issues found in the file. Forgot to run Prettier?
🪛 GitHub Actions: Format / Format
[error] 1-1: Prettier --check failed. Code style issues found in the file. Forgot to run Prettier?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/commands/agents/agents-interactive.ts` at line 1, Run the project’s
Prettier formatter on agents-interactive.ts and apply its formatting to the long
template literal and the multi-line log(...) call, preserving the existing
behavior and matching the file’s surrounding style.
Source: Pipeline failures
| const request = async (ctx: RequestContext, path: string, init: RequestOptions = {}): Promise<unknown> => { | ||
| const response = await fetch(`${ctx.baseUrl}${path}`, { | ||
| method: init.method, | ||
| body: init.body, | ||
| headers: { | ||
| Authorization: `Bearer ${ctx.accessToken}`, | ||
| 'User-Agent': ctx.userAgent, | ||
| ...init.headers, | ||
| }, | ||
| }) | ||
|
|
||
| if (!response.ok) { | ||
| const errorData = (await response.json().catch(() => ({}))) as { error?: string } | ||
| throw new Error(errorData.error ?? `HTTP ${response.status.toString()}: ${response.statusText}`) | ||
| } | ||
|
|
||
| return response.status === 204 ? undefined : await response.json().catch(() => undefined) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file and inspect the relevant range.
ast-grep outline src/commands/agents/agents-interactive.ts --view expanded || true
echo '---'
cat -n src/commands/agents/agents-interactive.ts | sed -n '1,260p'Repository: netlify/cli
Length of output: 11101
Add a timeout and poll deadline here
request() has no AbortSignal/timeout, and waitForSession()/followSession() retry forever. A hung API call or a session that never reaches a terminal state can leave the CLI spinning indefinitely.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/commands/agents/agents-interactive.ts` around lines 41 - 58, The request
helper lacks cancellation, and the session polling flows can run indefinitely.
Update request() to use an AbortSignal with a bounded timeout, and add a
deadline to waitForSession() and followSession() so retries stop when the
deadline is reached while preserving their existing success and terminal-state
behavior.
| return response.status === 204 ? undefined : await response.json().catch(() => undefined) | ||
| } | ||
|
|
||
| const createRun = async (ctx: RequestContext, siteId: string, body: Record<string, unknown>): Promise<AgentRunner> => { | ||
| const params = new URLSearchParams({ site_id: siteId }) | ||
| return (await request(ctx, `/api/v1/agent_runners?${params.toString()}`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify(body), | ||
| })) as AgentRunner | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Unvalidated cast can turn a malformed response into a silent undefined.
request() returns undefined if the body isn't valid JSON (Line 57's .catch(() => undefined)). createRun then casts the result directly as AgentRunner (Lines 62-66) without checking it's actually defined. If the API ever returns a non-JSON or empty 2xx body, runner becomes undefined while typed as AgentRunner, and the later runner.id accesses (Lines 207, 240, 242) throw an uncaught TypeError.
🐛 Proposed fix
const createRun = async (ctx: RequestContext, siteId: string, body: Record<string, unknown>): Promise<AgentRunner> => {
const params = new URLSearchParams({ site_id: siteId })
- return (await request(ctx, `/api/v1/agent_runners?${params.toString()}`, {
+ const result = await request(ctx, `/api/v1/agent_runners?${params.toString()}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
- })) as AgentRunner
+ })
+ if (!result) {
+ throw new Error('Received an empty response while creating the agent run.')
+ }
+ return result as AgentRunner
}📝 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.
| return response.status === 204 ? undefined : await response.json().catch(() => undefined) | |
| } | |
| const createRun = async (ctx: RequestContext, siteId: string, body: Record<string, unknown>): Promise<AgentRunner> => { | |
| const params = new URLSearchParams({ site_id: siteId }) | |
| return (await request(ctx, `/api/v1/agent_runners?${params.toString()}`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(body), | |
| })) as AgentRunner | |
| } | |
| return response.status === 204 ? undefined : await response.json().catch(() => undefined) | |
| } | |
| const createRun = async (ctx: RequestContext, siteId: string, body: Record<string, unknown>): Promise<AgentRunner> => { | |
| const params = new URLSearchParams({ site_id: siteId }) | |
| const result = await request(ctx, `/api/v1/agent_runners?${params.toString()}`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(body), | |
| }) | |
| if (!result) { | |
| throw new Error('Received an empty response while creating the agent run.') | |
| } | |
| return result as AgentRunner | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/commands/agents/agents-interactive.ts` around lines 57 - 67, Validate the
result returned by request in createRun before casting or returning it as
AgentRunner. Treat an undefined or malformed response as an explicit error, so
callers never receive an undefined runner and later runner.id accesses remain
safe.
| const getLatestSession = async (ctx: RequestContext, runnerId: string): Promise<AgentRunnerSession | undefined> => { | ||
| const sessions = (await request(ctx, `/api/v1/agent_runners/${runnerId}/sessions?page=1&per_page=1`)) as | ||
| | AgentRunnerSession[] | ||
| | undefined | ||
| return sessions?.[0] | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Locate the agent_runners sessions endpoint implementation/docs to confirm default sort order.
rg -nP -C5 'agent_runners/:?\$?\{?runnerId\}?/sessions' --type=ts
rg -nP -C5 'per_page' src/commands/agentsRepository: netlify/cli
Length of output: 149
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== file context ==\n'
sed -n '1,280p' src/commands/agents/agents-interactive.ts
printf '\n== session endpoint usages ==\n'
rg -n "agent_runners/.*/sessions|/sessions\\?page=1&per_page=1|waitForSession|previousSessionId" src test docs -S || true
printf '\n== request helper ==\n'
rg -n "function request|const request|export .*request" src -S || trueRepository: netlify/cli
Length of output: 11207
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== files mentioning agent_runners or sessions ==\n'
rg -n "agent_runners|sessions\\?" src test docs . -S || true
printf '\n== likely API route definitions ==\n'
rg -n "router\\.(get|post|delete|put)\\(.*agent.*session|/api/v1/agent_runners|agent_runners" src test -S || trueRepository: netlify/cli
Length of output: 11151
🌐 Web query:
Netlify agent runners sessions API default sort order per_page newest first
💡 Result:
The Netlify API for retrieving agent runner sessions, located at /agent_runners/{agent_runner_id}/sessions, follows standard Netlify API conventions for pagination and sorting [1]. 1. Default per_page: By default, Netlify API endpoints that return multiple items are paginated with a page size of 100 items [2][3][4]. You can use the per_page query parameter to specify a custom number of items per page, up to a maximum of 100 [2][3][4]. 2. Default Sort Order: The endpoint definition includes an optional order_by query parameter that accepts 'asc' (ascending) or 'desc' (descending) values [1]. In Netlify's API design, when a specific sort field or direction is not provided, the default behavior typically follows a reverse-chronological order (newest first) for resource collections [2][3]. 3. Pagination: Pagination is 1-indexed using the page query parameter [2][3][4]. Omission of the page parameter defaults to the first page [2][3].
Citations:
- 1: https://raw.githubusercontent.com/netlify/open-api/master/swagger.yml
- 2: https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api/
- 3: https://netlify-staging.netlify.app/docs/api
- 4: https://www.stitchflow.com/user-management/netlify/api
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== agents show/list session handling ==\n'
sed -n '1,220p' src/commands/agents/agents-show.ts
printf '\n---\n'
sed -n '1,220p' src/commands/agents/agents-list.ts
printf '\n== openapi or swagger references ==\n'
rg -n "order_by|per_page|agent_runners/.*/sessions|sessions\\]" . -g '!node_modules' -g '!dist' -g '!build' -S || true
printf '\n== agent tests session payloads ==\n'
sed -n '1,260p' tests/integration/commands/agents/agents-show.test.ts
printf '\n---\n'
sed -n '1,260p' tests/integration/commands/agents/agents-list.test.tsRepository: netlify/cli
Length of output: 27453
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [
Path('src/commands/agents/agents-show.ts'),
Path('src/commands/agents/agents-list.ts'),
Path('tests/integration/commands/agents/agents-show.test.ts'),
Path('tests/integration/commands/agents/agents-list.test.ts'),
]:
print(f"{p}: {sum(1 for _ in p.open())} lines")
PYRepository: netlify/cli
Length of output: 371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
set +e
sed -n '1,220p' src/commands/agents/agents-show.ts
printf '\n---\n'
sed -n '1,220p' src/commands/agents/agents-list.ts
printf '\n---\n'
sed -n '1,260p' tests/integration/commands/agents/agents-show.test.ts
printf '\n---\n'
sed -n '1,260p' tests/integration/commands/agents/agents-list.test.tsRepository: netlify/cli
Length of output: 25431
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== file sizes ==\n'
for f in src/commands/agents/agents-show.ts src/commands/agents/agents-list.ts tests/integration/commands/agents/agents-show.test.ts tests/integration/commands/agents/agents-list.test.ts; do
wc -l "$f"
done
printf '\n== openapi/swagger files ==\n'
fd -a -H -I 'swagger.yml|openapi.yml|openapi.yaml|swagger.yaml' .
printf '\n== order/sort references in repo ==\n'
rg -n "order_by|sort_by|created_at|updated_at|newest|oldest|descending|ascending" src tests docs . -S || trueRepository: netlify/cli
Length of output: 6671
🏁 Script executed (no clone):
#!/bin/bash
set -euo pipefail
url='https://raw.githubusercontent.com/netlify/open-api/master/swagger.yml'
tmp=$(mktemp)
curl -fsSL "$url" -o "$tmp"
printf '\n== sessions endpoint in swagger ==\n'
python3 - <<'PY' "$tmp"
import sys
from pathlib import Path
text = Path(sys.argv[1]).read_text()
for needle in [
'/api/v1/agent_runners/{agent_runner_id}/sessions',
'agent_runners/{agent_runner_id}/sessions',
'order_by',
'per_page',
]:
print(f'\n-- {needle} --')
i = text.find(needle)
if i == -1:
print('not found')
continue
start = text.rfind('\n', 0, i-800)
start = 0 if start < 0 else start+1
end = text.find('\n\n', i)
if end == -1:
end = min(len(text), i+2500)
print(text[start:end])
PY
Length of output: 10117
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== agents-show test session fixtures ==\n'
rg -n -C4 "agent_runners/.*/sessions|sessions:" tests/integration/commands/agents/agents-show.test.ts -S
printf '\n== agents-list test session fixtures ==\n'
rg -n -C4 "agent_runners/.*/sessions|sessions:" tests/integration/commands/agents/agents-list.test.ts -SRepository: netlify/cli
Length of output: 2063
🌐 Web query:
site:raw.githubusercontent.com/netlify/open-api/master/swagger.yml "/api/v1/agent_runners/{agent_runner_id}/sessions" order_by desc per_page
💡 Result:
The Netlify API endpoint /api/v1/agent_runners/{agent_runner_id}/sessions supports retrieving a list of sessions associated with a specific agent runner using the GET method [1]. According to the API definition, you can control the output using the following query parameters: order_by: This parameter accepts a string value of either 'asc' (ascending) or 'desc' (descending) to determine the sort order [1]. per_page: This is a supported parameter for pagination, referenced as #/parameters/perPage in the API specification [1]. Additionally, the endpoint supports standard pagination via a 'page' parameter [1]. You can also filter results by state ('live' or 'error') and specify time ranges using 'from' and 'to' integer parameters [1]. Example usage: GET /api/v1/agent_runners/{agent_runner_id}/sessions?order_by=desc&per_page=20
Citations:
Request sessions in descending order. waitForSession(...candidate.id !== previousSessionId) assumes the first page contains the newest session, but this request doesn’t specify any sort. Add order_by=desc here so follow-up detection can’t stall or pick the wrong session.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/commands/agents/agents-interactive.ts` around lines 81 - 86, The
getLatestSession request must explicitly request sessions in descending order so
sessions?.[0] is the newest session. Add the order_by=desc query parameter to
the existing sessions endpoint while preserving the current pagination and
return behavior.
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
…ssion 1 (6a5fb51b202629a56a101e6e)
🔗 View agent run: https://app.netlify.com/projects/steady-pastelito-09e39e/agent-runs/6a5fb51b202629a56a101e6e
🤖 Agent: Claude
💬 Prompt: The application you're working in is a CLI that is used to manage Netlify. The CLI has many commands that a user runs in a terminal environment. There are commands dedicated to interacting with "Agent Runners," a product that runs AI agents and publi...
✅ Result: # Summary
Added an interactive mode to the Netlify CLI for the Agent Runners product, giving people a conversational way to build with an agent instead of firing off one-shot commands.
Interactive agent mode
A new command,
netlify agents:interactive, starts a session that takes over the terminal. It clears the pane, welcomes the person to the current site, and asks "What do you want to build today?" with a text input. Submitting that prompt spawns a brand-new agent run against the site — the same kind of runnetlify agents:createproduces — using Claude by default (overridable with--agentand--model), and the site's default branch for git-based projects.Once a run starts, the mode continuously polls the run's active session and surfaces progress as it happens, updating a live status indicator with the agent's current step. When the session finishes, the human-readable result is printed inline.
At that point the person can type a follow-up. Rather than starting a fresh run, a follow-up creates a new session attached to the same overarching run, so context and history stay together. Each follow-up is polled and displayed the same way, and the loop continues until the person presses Enter on an empty line or types
exit. On exit, the session prints how to revisit the run later, both from the CLI and in the browser.This was built on the CLI's existing conventions — the shared spinner utilities and
inquirerprompts already used across the agents commands — so it fits naturally alongsideagents:create,agents:list,agents:show, andagents:stop.Documentation and build configuration
The
docs/directory was replaced with a singleindex.htmlpage that reads "The app is the CLI," reflecting that the deployed artifact is documentation-only. A rootnetlify.tomlwas added that publishes thedocs/directory with a no-op build command.