Skip to content

feat(build-cli): add flub ai command and AI-enabled (Insiders) devcontainer#27020

Open
tylerbutler wants to merge 16 commits intomicrosoft:mainfrom
tylerbutler:flub-ai-v0
Open

feat(build-cli): add flub ai command and AI-enabled (Insiders) devcontainer#27020
tylerbutler wants to merge 16 commits intomicrosoft:mainfrom
tylerbutler:flub-ai-v0

Conversation

@tylerbutler
Copy link
Copy Markdown
Member

@tylerbutler tylerbutler commented Apr 13, 2026

Summary

  • Adds a flub ai command that launches an interactive Copilot SDK session to help users pick the right agent alias and MCP server configuration
  • Creates a new AI-enabled (Insiders) devcontainer profile that builds the flub CLI from source during prebuild, making flub ai available in the codespace
  • The base AI-enabled profile is unchanged — insiders differs by only 4 lines in devcontainer.json (name, onCreateCommand chains install-flub.sh, openFiles path)
  • Shared files like launcher-prompt.md live in ai-agent/ only; flub ai searches ai-agent-insiders/ first then falls back to ai-agent/
  • Adds FLUB_PERF=1 env var support for oclif performance diagnostics

Test plan

  • Create a Codespace using the AI-enabled (Insiders) profile
  • Verify flub ai is available and launches the interactive session
  • Verify the base AI-enabled profile still works without flub ai
  • Run flub ai -v and confirm verbose logging shows file resolution and model source
  • Run DEBUG=oclif:perf FLUB_PERF=1 flub ai and confirm performance output

tylerbutler and others added 10 commits April 13, 2026 14:10
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>
@tylerbutler tylerbutler self-assigned this Apr 13, 2026
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>
@tylerbutler tylerbutler changed the title Add flub ai command for guided agent selection feat(build-cli): add flub ai command and AI-enabled (Insiders) devcontainer Apr 13, 2026
tylerbutler and others added 5 commits April 13, 2026 15:47
🤖 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.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unfortunately devcontainers don't support inheritance, so we have to duplicate 99% of this file.

Comment on lines +1 to +8
_________ _____ __ __ _____ ______ ________ ________
(_ _____) (_ _) ) ) ( ( (_ _) (_ __ \ /_______/\ /_______/\
) (___ | | ( ( ) ) | | ) ) \ \ \::: _ \ \ \__.::._\/
( ___) | | ) ) ( ( | | ( ( ) ) \::(_) \ \ \::\ \
) ( | | __ ( ( ) ) | | ) ) ) ) \:: __ \ \ _\::\ \__
( ) __| |___) ) ) \__/ ( _| |__ / /__/ / \:.\ \ \ \/__\::\__/\
\_/ \________/ \______/ /_____( (______/ \__\/\__\/\________\/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

😆 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.

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.

Being the one that checked it in... I vote to keep it :D. I love it!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh I didn't realize this wasn't new - my bad; in that case all statements retracted! :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Closing thread so claude doesn't get confused when reviewing feedback.

@github-actions
Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  279037 links
    1880 destination URLs
    2126 URLs ignored
       0 warnings
       0 errors


}
}

this.verbose("launcher-prompt.md not found; using hardcoded fallback prompt.");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Make this a warning.

Comment on lines +32 to +37
export async function runAiSession(options: {
model: string;
prompt: string;
githubToken?: string;
ui: AiSessionUi;
}): Promise<AliasProposal | undefined> {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Worth a formal type?

@tylerbutler tylerbutler marked this pull request as ready for review April 13, 2026 23:52
Copilot AI review requested due to automatic review settings April 13, 2026 23:52
Copy link
Copy Markdown
Contributor

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

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 ai oclif command powered by the GitHub Copilot SDK, plus unit tests and command docs.
  • Add “AI-enabled (Insiders)” devcontainer profile and an install-flub.sh helper to build/link flub during onCreateCommand.
  • Add FLUB_PERF=1 opt-in to enable oclif performance diagnostics in bin/run.js and bin/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

Comment on lines +14 to +18
# 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
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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).

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

Copilot uses AI. Check for mistakes.
"postAttachCommand": {
"open-getting-started": "code .devcontainer/ai-agent/GETTING_STARTED.md"
},
"postAttachCommand": {},
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
"postAttachCommand": {},
"postAttachCommand": "if [ -z \"${CODESPACES}\" ] && command -v code >/dev/null 2>&1; then code -r .devcontainer/ai-agent/GETTING_STARTED.md; fi",

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +50
"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": {},
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
"exitCode" in error &&
typeof (error as { exitCode?: unknown }).exitCode === "number"
? (error as { exitCode: number }).exitCode
: 1;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
: 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}`,
);

Copilot uses AI. Check for mistakes.
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.

3 participants