teams-sdk / cli
AGENTS.md735 starsChanged 16 days agoMITView on GitHub
Do not commit or push unless explicitly asked. Use picocolors for terminal styling. Use Intl.DateTimeFormat via formatDate() from src/utils/date.ts. Use logger from src/utils/logger.ts for all output. Never use console.log/console.error directly (except inside logger.ts and json-output.ts). For error conditions, throw CliError from src/utils/errors.ts: Wrap command .action() handlers with wrapAction() — it catches CliError and unknown errors, routes to JSON or human output, and handles ExitPromptError gracefully. In interactive menus (not wrapped by wrapAction), catch errors inline with logger.error(). HTTP/API helper modules…
- Reads credentials
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository3 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- teams.md/static/llms_docs/llms.txtllms.txt
- teams-devSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
21 rules. About 0 are common, 4 are distinctive, and 17 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
17
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 5
Do not commit or push unless explicitly asked.
- Distinctive: line 9
Use picocolors for terminal styling.
- Very specific: line 19
Use Intl.DateTimeFormat via formatDate() from src/utils/date.ts.
- Very specific: line 23
Use logger from src/utils/logger.ts for all output.
- Very specific: line 44
Use createSilentSpinner() from src/utils/spinner.ts (not createSpinner from nanospinner directly).
- Very specific: line 50
When an options object has a field that callers should consciously decide, prefer a required key with an explicit | undefined value over an …
- Very specific: line 60
Do NOT mark as [OPTIONAL] if the value will be prompted interactively when not provided.
- Very specific: line 68
Before implementing logic, check if a shared function already exists for it.
- Very specific: line 72
Always run npm run build after changes — the CLI runs from dist/, not source.
- Very specific: line 76
Every command MUST support --json (boolean flag, marked [OPTIONAL]).
- Very specific: line 80
Every interactive menu that uses select() to dispatch to subcommands must be wrapped in a while (true) loop.
- Very specific: line 103
When adding a new interactive menu, also add a corresponding test in tests/menu-loop.test.ts verifying the loop behavior.
- Very specific: line 107
Use Commander's built-in features for global flags and hooks.
- Very specific: line 111
When adding or renaming commands, menu items, or features, update the corresponding docs.
- Distinctive: line 118
All new tests must follow a red/green cycle: write the test first, verify it fails against the broken code (red), then apply the fix and …
- Distinctive: line 122
Before creating a PR:
- Very specific: line 123
Run npm run build — ensures TypeScript compiles cleanly.
- Very specific: line 124
Run npm run test — runs unit tests (menu loop behavior, JSON output, etc.).
- Very specific: line 125
Run the agentic tests defined in agentic-tests.md — basic smoke tests (setup/act/assert) that verify the CLI works end-to-end.
- Very specific: line 131
Create AAD apps via TDP's /aadapp/v2 endpoint (createAadAppViaTdp in src/apps/tdp.ts), NOT via Graph API directly.
- Very specific: line 147
Always output CLIENT_ID, CLIENT_SECRET, and TENANT_ID together.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Conventions
Git
Do not commit or push unless explicitly asked.
Colors
Use picocolors for terminal styling.
- Success/username:
pc.bold(pc.green(...)) - Warning:
pc.yellow(...) - Command hints:
pc.cyan(...) - Labels:
pc.dim(...) - Errors:
pc.red(...)
Dates
Use Intl.DateTimeFormat via formatDate() from src/utils/date.ts.
Logging & Errors
Use logger from src/utils/logger.ts for all output. Never use console.log/console.error directly (except inside logger.ts and json-output.ts).
logger.info(...)— general output (stdout)logger.warn(...)— warnings (stderr)logger.error(...)— errors (stderr)logger.debug(...)— verbose-only, gated by--verbose
For error conditions, throw CliError from src/utils/errors.ts:
throw new CliError("AUTH_REQUIRED", "Not logged in.", "Run `teams login` first.");Wrap command .action() handlers with wrapAction() — it catches CliError and unknown errors, routes to JSON or human output, and handles ExitPromptError gracefully. In interactive menus (not wrapped by wrapAction), catch errors inline with logger.error().
HTTP/API helper modules (for example, src/apps/api.ts and similar transport/helper code) must never log directly. Functions like updateAppDetails, uploadIcon, and other API layer code should return metadata (e.g., result.versionBumped) instead of calling logger. Only command-layer code should log, because it knows whether the caller is in --json mode, has active spinners, etc. Logging from API helpers corrupts --json output and breaks spinner rendering. This restriction does not apply to interactive/UI modules that also live under src/apps/.
Shared app/service modules under src/apps/ must not import from src/commands/. Keep layering one-way: command modules parse CLI options, handle prompts/spinners/output, then call reusable app/service helpers. Put shared business logic (manifest mutation, validation, app update helpers, etc.) in src/apps/ or src/utils/ so interactive menus, scripted commands, and tests can reuse it without circular dependencies.
Spinners
Use createSilentSpinner() from src/utils/spinner.ts (not createSpinner from nanospinner directly). Pass silent = true when --json is active to suppress visual output. Always include descriptive text.
TypeScript
Strong, precise types are extremely important. Every interface, output shape, and function signature must be explicitly typed — never fall back to Record<string, unknown>, any, or untyped parameters when a concrete type is possible. Define dedicated interfaces for command options, JSON output shapes, and API responses. Avoid as unknown as X double-casts and any types unless absolutely necessary. Prefer fixing the underlying type (adding a field to an interface, using a proper generic, etc.) over casting.
When an options object has a field that callers should consciously decide, prefer a required key with an explicit | undefined value over an optional key. Use optional keys only when the field is genuinely not part of the contract for all callers.
CLI Options
Prefix truly optional flags with [OPTIONAL] in their description:
.option("--env <path>", "[OPTIONAL] Path to .env file")Do NOT mark as [OPTIONAL] if the value will be prompted interactively when not provided. Those are required inputs, just with an alternative input method.
Auto-Confirm (--yes / -y)
The --yes global flag skips interactive confirmation prompts (for CI/agent use). State is managed via setAutoConfirm/isAutoConfirm in src/utils/interactive.ts. Before any confirm() call, check isAutoConfirm() and accept the default action if true.
Code Reuse
Before implementing logic, check if a shared function already exists for it. Extract reusable logic into shared modules (e.g., src/utils/, action files like manifest/actions.ts, secret/generate.ts). Never duplicate business logic across interactive menus and CLI subcommands — both should call the same shared function.
Build
Always run npm run build after changes — the CLI runs from dist/, not source. tsc --noEmit only type-checks. From the monorepo root, use npx turbo build --filter=@microsoft/teams.cli.
JSON Output
Every command MUST support --json (boolean flag, marked [OPTIONAL]). Each command defines a typed output interface in its own file (e.g., AppCreateOutput). Use outputJson() from src/utils/json-output.ts. Guard all human output (logger.info, outputCredentials) with if (!options.json). Skip interactive prompts in JSON mode — use defaults or require flags.
Interactive Menus
Every interactive menu that uses select() to dispatch to subcommands must be wrapped in a while (true) loop. Without this, the menu exits after one action and the user gets thrown back to the parent menu instead of staying in context.
// ✅ Correct — loops back to menu after subcommand completes
while (true) {
try {
const action = await select({ message: "...", choices: [...] });
if (action === "back") return;
// dispatch to subcommands...
} catch (error) {
if (error instanceof Error && error.name === "ExitPromptError") return;
throw error;
}
}
// ❌ Wrong — menu shows once, then exits to parent
try {
const action = await select({ message: "...", choices: [...] });
if (action === "back") return;
// dispatch to subcommands...
} catch (error) { ... }When adding a new interactive menu, also add a corresponding test in tests/menu-loop.test.ts verifying the loop behavior.
Commander Patterns
Use Commander's built-in features for global flags and hooks. Don't manually parse process.argv — use .option() on the program and access via optsWithGlobals() in preAction hooks.
Documentation
When adding or renaming commands, menu items, or features, update the corresponding docs. CLI docs live at ../../teams.md/docs/cli/. Key files:
../../teams.md/docs/cli/commands/— one.mdper command/subcommand (autogenerated markers reflect--helpoutput)../../teams.md/docs/cli/commands/index.md— command tree overview../../teams.md/docs/cli/index.md— landing page feature descriptions
Testing
All new tests must follow a red/green cycle: write the test first, verify it fails against the broken code (red), then apply the fix and verify it passes (green). This ensures tests actually catch the bug they're designed for.
Pre-PR Validation
Before creating a PR:
- Run
npm run build— ensures TypeScript compiles cleanly. - Run
npm run test— runs unit tests (menu loop behavior, JSON output, etc.). These must all pass. - Run the agentic tests defined in
agentic-tests.md— basic smoke tests (setup/act/assert) that verify the CLI works end-to-end. Execute each test scenario vianode dist/index.js <command>(not the globalteamscommand) and confirm expected output.
Architecture Decisions
AAD App Creation — Use TDP, Not Graph API
Create AAD apps via TDP's /aadapp/v2 endpoint (createAadAppViaTdp in src/apps/tdp.ts), NOT via Graph API directly. TDP's backend creates the service principal server-side, which is required for single-tenant bot registration.
isSingleTenant: Alwaystrueon bot registration (SFI requirement)- TDP returns a different
idthan Graph's object ID — usegetAadAppByClientIdto look up the Graph object ID before callingaddPassword - Graph replication lag may require retries after TDP creates the app
Tenant Cross-Validation
Any command that uses both MSAL (Teams login) and Azure CLI must call ensureTenantMatch(account.tenantId) from src/utils/az-prompts.ts before creating Azure resources. This throws CliError("TENANT_MISMATCH", ...) if account.tenantId (MSAL) differs from the Azure CLI's active tenant (az account show). The status command displays the match/mismatch but does not throw.
Auth Client ID
Uses shared public client 7ea7c24c-b1f6-4a20-9d11-9ae12e9e7ac0. TDP's own web UI uses a different first-party client ID (e1979c22) which we cannot use for CLI auth.
Credential Output
Always output CLIENT_ID, CLIENT_SECRET, and TENANT_ID together. The TENANT_ID comes from account.tenantId (MSAL AccountInfo).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

