Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cli-no-browser-headless-init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---

Add `--no-browser` flag to `init` and `login` to skip auto-opening the browser during authentication. Also error loudly when `init` is run without `--yes` under non-TTY stdin (previously default-and-exited silently, leaving the project half-initialized). Both commands now show an `Examples` section in `--help`.
35 changes: 33 additions & 2 deletions packages/cli-v3/src/commands/init.ts
Comment thread
nicktrn marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const InitCommandOptions = CommonCommandOptions.extend({
gitRef: z.string().default("main"),
javascript: z.boolean().default(false),
yes: z.boolean().default(false),
browser: z.boolean().default(true),
});

type InitCommandOptions = z.infer<typeof InitCommandOptions>;
Expand All @@ -65,7 +66,23 @@ export function configureInitCommand(program: Command) {
return commonOptions(
program
.command("init")
.description("Initialize your existing project for development with Trigger.dev")
.summary("Initialize your existing project for development with Trigger.dev")
.description(
`Initialize your existing project for development with Trigger.dev.

Examples:
# Interactive setup
$ trigger.dev init

# Non-interactive (CI / scripts)
$ trigger.dev init --yes --project-ref proj_abc123

# Headless / agent (no browser)
$ trigger.dev init --yes --project-ref proj_abc123 --no-browser

# Use a named profile
$ trigger.dev init --profile staging`
)
.argument("[path]", "The path to the project", ".")
.option(
"-p, --project-ref <project ref>",
Expand All @@ -89,6 +106,7 @@ export function configureInitCommand(program: Command) {
"Additional arguments to pass to the package manager, accepts CSV for multiple args"
)
.option("-y, --yes", "Skip all prompts and use defaults (requires --project-ref)")
.option("--no-browser", "Don't automatically open the browser during login; print the URL only")
)
.addOption(
new CommandOption(
Expand Down Expand Up @@ -118,9 +136,21 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
throw new Error("--project-ref is required when using --yes flag");
}

// Refuse to run interactively when stdin isn't a TTY (CI, agent harness, etc).
// Previously this silently default-and-exited at the first prompt, leaving the
// project half-initialized.
if (!options.yes && !process.stdin.isTTY) {
throw new Error(
"Interactive prompts cannot be used in non-TTY environments. Pass --yes (and --project-ref) to run non-interactively."
);
}
Comment thread
nicktrn marked this conversation as resolved.

const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();

if (!hasSeenMCPInstallPrompt) {
// Skip the MCP-vs-CLI prompt when --yes is set: the user explicitly chose CLI
// by running `trigger.dev init` non-interactively, and the prompt would
// otherwise hang on a fresh machine where `hasSeenMCPInstallPrompt` is false.
if (!hasSeenMCPInstallPrompt && !options.yes) {
const installChoice = await select({
message: "Choose how you want to initialize your project:",
options: [
Expand Down Expand Up @@ -165,6 +195,7 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
embedded: true,
defaultApiUrl: options.apiUrl,
profile: options.profile,
browser: options.browser,
});

if (!authorization.ok) {
Expand Down
29 changes: 26 additions & 3 deletions packages/cli-v3/src/commands/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { links } from "@trigger.dev/core/v3";

export const LoginCommandOptions = CommonCommandOptions.extend({
apiUrl: z.string(),
browser: z.boolean().default(true),
});

export type LoginCommandOptions = z.infer<typeof LoginCommandOptions>;
Expand All @@ -49,7 +50,21 @@ export function configureLoginCommand(program: Command) {
return commonOptions(
program
.command("login")
.description("Login with Trigger.dev so you can perform authenticated actions")
.summary("Login with Trigger.dev so you can perform authenticated actions")
.description(
`Login with Trigger.dev so you can perform authenticated actions.

Examples:
# Interactive login (opens browser)
$ trigger.dev login

# Headless / agent (print URL only)
$ trigger.dev login --no-browser

Comment thread
nicktrn marked this conversation as resolved.
# Login to a named profile
$ trigger.dev login --profile staging`
)
.option("--no-browser", "Don't automatically open the browser; print the URL only")
)
.version(VERSION, "-v, --version", "Display the version number")
.action(async (options) => {
Expand All @@ -67,14 +82,20 @@ export async function loginCommand(options: unknown) {
}

async function _loginCommand(options: LoginCommandOptions) {
return login({ defaultApiUrl: options.apiUrl, embedded: false, profile: options.profile });
return login({
defaultApiUrl: options.apiUrl,
embedded: false,
profile: options.profile,
browser: options.browser,
});
}

export type LoginOptions = {
defaultApiUrl?: string;
embedded?: boolean;
profile?: string;
silent?: boolean;
browser?: boolean;
};

export async function login(options?: LoginOptions): Promise<LoginResult> {
Expand Down Expand Up @@ -259,7 +280,9 @@ export async function login(options?: LoginOptions): Promise<LoginResult> {
`Please visit the following URL to login:\n${chalkLink(authorizationCodeResult.url)}`
);

if (await isLinuxServer()) {
if (opts.browser === false) {
log.message("Browser auto-open disabled. Visit the URL above to login.");
} else if (await isLinuxServer()) {
log.message("Please install `xdg-utils` to automatically open the login URL.");
} else {
await open(authorizationCodeResult.url);
Expand Down
Loading