feat(build-cli): add flub ai command and AI-enabled (Insiders) devcontainer#27020
feat(build-cli): add flub ai command and AI-enabled (Insiders) devcontainer#27020tylerbutler wants to merge 16 commits intomicrosoft:mainfrom
Conversation
Uses the GitHub Copilot SDK to run an interactive AI assistant that asks the user what they want to accomplish, then recommends and launches the right agent alias (dev, claude, copilot, oce) with appropriate MCP configuration. The system prompt is built dynamically from the actual agent-aliases.sh and GETTING_STARTED.md files at runtime. Also adds install-flub.sh to the AI-enabled codespace postCreateCommand so flub is globally available via npm link. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
The preflight auth check was comparing non-existent fields (authenticated, status) instead of the SDK's actual isAuthenticated field, making the check a no-op. Also: switched to async file I/O, parallelized independent operations, eliminated TOCTOU races, deduplicated repo root resolution, replaced raw process.exit() with oclif's this.exit(), and aligned the AliasProposal type with its JSON schema. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
…flub output Run install-flub before ai-setup so a failed build stops early. Add --reporter=default for visible progress during pnpm install. Replace cd with pnpm -C / npm --prefix for cleaner path handling. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Docker's overlayfs is very slow for pnpm's hardlink-based linking. Mount a named volume at /pnpm-store so the store lives on ext4, and point pnpm at it during install. Also move install-flub into onCreateCommand so it's captured by Codespace prebuilds, and suppress corepack's download prompt. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Named Docker volumes are created as root. The container runs as the node user, so chown the mount point before pnpm tries to write to it. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Switch to node-linker=hoisted for the devcontainer install to avoid pnpm's slow hardlinking on overlayfs. Set CI=true to suppress the interactive modules purge prompt. Remove the named volume mount since it didn't meaningfully improve performance. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
…install The nested pnpm install in postinstall hooks conflicts in non-TTY environments. Skip lifecycle scripts during install since the build step handles compilation anyway. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Extract the system prompt into .devcontainer/ai-agent/launcher-prompt.md
with gray-matter frontmatter for the default model. The CLI loads the
template at runtime and interpolates {{aliasFileContent}} and
{{gettingStartedContent}} placeholders. This allows changing prompt
content and the default model without rebuilding the CLI.
🤖 Generated with [Nori](https://noriagentic.com)
Co-Authored-By: Nori <contact@tilework.tech>
npm link --prefix installs the shim under the prefix directory, not the global npm prefix, so flub doesn't end up on PATH. Use a subshell cd to keep the working directory change contained. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
The code CLI isn't reliably available during postAttachCommand in local devcontainers. The codespaces.openFiles setting already handles opening GETTING_STARTED.md in Codespaces. 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Move flub ai / install-flub.sh into a new AI-enabled (Insiders) profile so the base AI-enabled config stays unchanged. The insiders config differs by only 4 lines in devcontainer.json (name, onCreateCommand, openFiles) plus small additions to GETTING_STARTED.md and first-run-notice.txt. Shared files (launcher-prompt.md) remain in ai-agent/ — the flub ai command searches ai-agent-insiders/ first then falls back to ai-agent/. Also: - Fix shell injection in formatAliasCommand (proper single-quote escaping) - Consolidate duplicate file-resolution and error-message patterns - Eliminate double I/O on alias file (TOCTOU → single tryReadFile) - Add FLUB_PERF env var to enable oclif performance logging - Add verbose logging throughout the ai command 🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
flub ai command for guided agent selection🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
🤖 Generated with [Nori](https://noriagentic.com) Co-Authored-By: Nori <contact@tilework.tech>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| @@ -0,0 +1,72 @@ | |||
| // AI-enabled (Insiders) — everything in AI-enabled, plus the flub AI launcher command. | |||
There was a problem hiding this comment.
Unfortunately devcontainers don't support inheritance, so we have to duplicate 99% of this file.
| _________ _____ __ __ _____ ______ ________ ________ | ||
| (_ _____) (_ _) ) ) ( ( (_ _) (_ __ \ /_______/\ /_______/\ | ||
| ) (___ | | ( ( ) ) | | ) ) \ \ \::: _ \ \ \__.::._\/ | ||
| ( ___) | | ) ) ( ( | | ( ( ) ) \::(_) \ \ \::\ \ | ||
| ) ( | | __ ( ( ) ) | | ) ) ) ) \:: __ \ \ _\::\ \__ | ||
| ( ) __| |___) ) ) \__/ ( _| |__ / /__/ / \:.\ \ \ \/__\::\__/\ | ||
| \_/ \________/ \______/ /_____( (______/ \__\/\__\/\________\/ | ||
|
|
There was a problem hiding this comment.
😆 I did not request this, but... it's a little too cheesy. Prob should remove it. But it's hilarious to me that this UX trend influenced Claude.
There was a problem hiding this comment.
Being the one that checked it in... I vote to keep it :D. I love it!
There was a problem hiding this comment.
Oh I didn't realize this wasn't new - my bad; in that case all statements retracted! :)
There was a problem hiding this comment.
Closing thread so claude doesn't get confused when reviewing feedback.
|
🔗 No broken links found! ✅ Your attention to detail is admirable. linkcheck output |
| } | ||
| } | ||
|
|
||
| this.verbose("launcher-prompt.md not found; using hardcoded fallback prompt."); |
There was a problem hiding this comment.
Make this a warning.
| export async function runAiSession(options: { | ||
| model: string; | ||
| prompt: string; | ||
| githubToken?: string; | ||
| ui: AiSessionUi; | ||
| }): Promise<AliasProposal | undefined> { |
There was a problem hiding this comment.
Worth a formal type?
There was a problem hiding this comment.
Pull request overview
Adds an AI-assisted launcher experience to the flub CLI and introduces an “AI-enabled (Insiders)” devcontainer profile that prebuilds and links flub from source so flub ai is available in Codespaces.
Changes:
- Add new
flub aioclif command powered by the GitHub Copilot SDK, plus unit tests and command docs. - Add “AI-enabled (Insiders)” devcontainer profile and an
install-flub.shhelper to build/linkflubduringonCreateCommand. - Add
FLUB_PERF=1opt-in to enable oclif performance diagnostics inbin/run.jsandbin/dev.js.
Reviewed changes
Copilot reviewed 13 out of 16 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/codespace-setup/install-flub.sh | New script to build build-tools from source and npm link the flub CLI in devcontainers. |
| build-tools/pnpm-lock.yaml | Lockfile updates to include @github/copilot-sdk and its transitive deps. |
| build-tools/packages/build-cli/src/test/commands/ai.test.ts | Unit tests for alias validation and prompt-answer normalization helpers. |
| build-tools/packages/build-cli/src/library/ai/copilotSession.ts | Copilot SDK session wrapper (tool definition, preflight, streaming output, cleanup). |
| build-tools/packages/build-cli/src/commands/ai.ts | New flub ai command: resolves prompt/aliases, runs session, confirms, launches alias via bash. |
| build-tools/packages/build-cli/package.json | Add Copilot SDK dependency and register an ai topic. |
| build-tools/packages/build-cli/docs/ai.md | Generated/added docs page for flub ai. |
| build-tools/packages/build-cli/bin/run.js | Enable oclif perf diagnostics when FLUB_PERF=1. |
| build-tools/packages/build-cli/bin/dev.js | Same perf diagnostics toggle for dev runner. |
| build-tools/packages/build-cli/README.md | Adds flub ai to command topic list. |
| .devcontainer/ai-agent/launcher-prompt.md | New shared launcher prompt template (with model frontmatter). |
| .devcontainer/ai-agent/devcontainer.json | Adjust AI-enabled profile env/hook configuration (and removes prior postAttach open behavior). |
| .devcontainer/ai-agent-insiders/first-run-notice.txt | Insiders welcome notice mentioning flub ai. |
| .devcontainer/ai-agent-insiders/devcontainer.json | New insiders profile that chains install-flub.sh in onCreateCommand. |
| .devcontainer/ai-agent-insiders/GETTING_STARTED.md | Insiders getting-started guide with flub ai callout and alias explanations. |
| .devcontainer/README.md | Documents the new insiders profile and the shared/override file fallback strategy. |
Files not reviewed (1)
- build-tools/pnpm-lock.yaml: Language not supported
| # CI=true suppresses pnpm's interactive "purge modules" prompt in non-TTY environments. | ||
| # --ignore-scripts skips postinstall hooks (which include nested pnpm installs that | ||
| # conflict in a non-TTY environment). | ||
| CI=true pnpm -C "$REPO_ROOT/build-tools" install --frozen-lockfile --reporter=default \ | ||
| --ignore-scripts |
There was a problem hiding this comment.
The header comment says this script follows the same install pattern as CI, but CI’s include-install-build-tools step runs pnpm i --frozen-lockfile without --ignore-scripts. Using --ignore-scripts here could skip required workspace lifecycle scripts and make pnpm build:compile/npm link fail or behave differently than CI. Consider aligning this install step with the pipeline template (or update the comment and document why scripts must be disabled here, plus what guarantees the build still works).
| # CI=true suppresses pnpm's interactive "purge modules" prompt in non-TTY environments. | |
| # --ignore-scripts skips postinstall hooks (which include nested pnpm installs that | |
| # conflict in a non-TTY environment). | |
| CI=true pnpm -C "$REPO_ROOT/build-tools" install --frozen-lockfile --reporter=default \ | |
| --ignore-scripts | |
| # CI=true suppresses pnpm's interactive "purge modules" prompt in non-TTY environments | |
| # while keeping the install behavior aligned with CI. | |
| CI=true pnpm -C "$REPO_ROOT/build-tools" install --frozen-lockfile --reporter=default |
| "postAttachCommand": { | ||
| "open-getting-started": "code .devcontainer/ai-agent/GETTING_STARTED.md" | ||
| }, | ||
| "postAttachCommand": {}, |
There was a problem hiding this comment.
This change removes the previous post-attach behavior that opened the Getting Started guide in VS Code (postAttachCommand), and now leaves postAttachCommand empty. codespaces.openFiles only applies in Codespaces, so users attaching via VS Code/SSH may no longer get the guide automatically. If that’s unintended, restore an appropriate post-attach open (or add a VS Code customization that applies outside Codespaces).
| "postAttachCommand": {}, | |
| "postAttachCommand": "if [ -z \"${CODESPACES}\" ] && command -v code >/dev/null 2>&1; then code -r .devcontainer/ai-agent/GETTING_STARTED.md; fi", |
| "NODE_VERSION_OVERRIDE": "24", | ||
| "COREPACK_ENABLE_DOWNLOAD_PROMPT": "0" | ||
| }, | ||
| "onCreateCommand": "bash scripts/codespace-setup/on-create.sh", | ||
| "postCreateCommand": "bash scripts/codespace-setup/ai-setup.sh", | ||
| "postStartCommand": { | ||
| "bwrap-setup": "sudo mount --make-rshared /", | ||
| "sandbox-tmpdir": "mkdir -p /tmp/claude" | ||
| }, | ||
| "postAttachCommand": { | ||
| "open-getting-started": "code .devcontainer/ai-agent/GETTING_STARTED.md" | ||
| }, | ||
| "postAttachCommand": {}, |
There was a problem hiding this comment.
PR description notes the base AI-enabled profile is unchanged, but this file is modified (env vars, lifecycle hook shape, and the postAttach behavior). If the intent is truly “insiders-only changes”, consider reverting the base profile edits or updating the PR summary so reviewers/users understand the base profile behavior has changed.
| "exitCode" in error && | ||
| typeof (error as { exitCode?: unknown }).exitCode === "number" | ||
| ? (error as { exitCode: number }).exitCode | ||
| : 1; |
There was a problem hiding this comment.
When launching the selected alias, the execa("bash", ...) error handler exits with a derived exit code but does not log the failure reason. If bash is missing (e.g. Windows) or the shell command fails before producing output, the user will see "Launching ..." and then an unexplained exit. Consider surfacing the error (at least for spawn failures) via this.error(...)/this.errorLog(...) before exiting, while still preserving the underlying exit code.
| : 1; | |
| : 1; | |
| const errorMessage = | |
| error !== null && | |
| typeof error === "object" && | |
| "shortMessage" in error && | |
| typeof (error as { shortMessage?: unknown }).shortMessage === "string" | |
| ? (error as { shortMessage: string }).shortMessage | |
| : error instanceof Error | |
| ? error.message | |
| : undefined; | |
| this.errorLog( | |
| errorMessage === undefined || errorMessage.length === 0 | |
| ? `Failed to launch ${proposal.alias}.` | |
| : `Failed to launch ${proposal.alias}: ${errorMessage}`, | |
| ); |
Summary
flub aicommand that launches an interactive Copilot SDK session to help users pick the right agent alias and MCP server configurationflub aiavailable in the codespacedevcontainer.json(name,onCreateCommandchainsinstall-flub.sh,openFilespath)launcher-prompt.mdlive inai-agent/only;flub aisearchesai-agent-insiders/first then falls back toai-agent/FLUB_PERF=1env var support for oclif performance diagnosticsTest plan
flub aiis available and launches the interactive sessionflub aiflub ai -vand confirm verbose logging shows file resolution and model sourceDEBUG=oclif:perf FLUB_PERF=1 flub aiand confirm performance output