Skip to content

Commit e2b9e0f

Browse files
authored
feat(cli-v3): add --no-browser flag and examples to init/login --help (#3483)
Closes the most common friction point hit while setting up a fresh project from an agent harness: the CLI auto-opens the user's default browser during auth and there is no supported way to skip it (the existing `isLinuxServer()` path only triggers when `xdg-open` is missing entirely). `--no-browser` on `login` and `init` prints the URL and waits to be visited from any browser. The flag threads through the embedded `login()` call inside `init`. While here: - `init` now errors loudly when stdin is non-TTY without `--yes` instead of default-and-exiting silently at the first prompt (which left the project half-initialized: deps installed, no config or example file). - Both commands gain an `Examples` block in `--help` rendered between the description and the arguments/options list, so `--help | head` surfaces the common invocations. Other commands also call `login()` embedded and would benefit from `--no-browser` too, but kept this PR scoped to the cases the friction log called out.
1 parent ac7177d commit e2b9e0f

3 files changed

Lines changed: 64 additions & 5 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
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`.

packages/cli-v3/src/commands/init.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const InitCommandOptions = CommonCommandOptions.extend({
5757
gitRef: z.string().default("main"),
5858
javascript: z.boolean().default(false),
5959
yes: z.boolean().default(false),
60+
browser: z.boolean().default(true),
6061
});
6162

6263
type InitCommandOptions = z.infer<typeof InitCommandOptions>;
@@ -65,7 +66,23 @@ export function configureInitCommand(program: Command) {
6566
return commonOptions(
6667
program
6768
.command("init")
68-
.description("Initialize your existing project for development with Trigger.dev")
69+
.summary("Initialize your existing project for development with Trigger.dev")
70+
.description(
71+
`Initialize your existing project for development with Trigger.dev.
72+
73+
Examples:
74+
# Interactive setup
75+
$ trigger.dev init
76+
77+
# Non-interactive (CI / scripts)
78+
$ trigger.dev init --yes --project-ref proj_abc123
79+
80+
# Headless / agent (no browser)
81+
$ trigger.dev init --yes --project-ref proj_abc123 --no-browser
82+
83+
# Use a named profile
84+
$ trigger.dev init --profile staging`
85+
)
6986
.argument("[path]", "The path to the project", ".")
7087
.option(
7188
"-p, --project-ref <project ref>",
@@ -89,6 +106,7 @@ export function configureInitCommand(program: Command) {
89106
"Additional arguments to pass to the package manager, accepts CSV for multiple args"
90107
)
91108
.option("-y, --yes", "Skip all prompts and use defaults (requires --project-ref)")
109+
.option("--no-browser", "Don't automatically open the browser during login; print the URL only")
92110
)
93111
.addOption(
94112
new CommandOption(
@@ -118,9 +136,21 @@ async function _initCommand(dir: string, options: InitCommandOptions) {
118136
throw new Error("--project-ref is required when using --yes flag");
119137
}
120138

139+
// Refuse to run interactively when stdin isn't a TTY (CI, agent harness, etc).
140+
// Previously this silently default-and-exited at the first prompt, leaving the
141+
// project half-initialized.
142+
if (!options.yes && !process.stdin.isTTY) {
143+
throw new Error(
144+
"Interactive prompts cannot be used in non-TTY environments. Pass --yes (and --project-ref) to run non-interactively."
145+
);
146+
}
147+
121148
const hasSeenMCPInstallPrompt = readConfigHasSeenMCPInstallPrompt();
122149

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

170201
if (!authorization.ok) {

packages/cli-v3/src/commands/login.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { links } from "@trigger.dev/core/v3";
4141

4242
export const LoginCommandOptions = CommonCommandOptions.extend({
4343
apiUrl: z.string(),
44+
browser: z.boolean().default(true),
4445
});
4546

4647
export type LoginCommandOptions = z.infer<typeof LoginCommandOptions>;
@@ -49,7 +50,21 @@ export function configureLoginCommand(program: Command) {
4950
return commonOptions(
5051
program
5152
.command("login")
52-
.description("Login with Trigger.dev so you can perform authenticated actions")
53+
.summary("Login with Trigger.dev so you can perform authenticated actions")
54+
.description(
55+
`Login with Trigger.dev so you can perform authenticated actions.
56+
57+
Examples:
58+
# Interactive login (opens browser)
59+
$ trigger.dev login
60+
61+
# Headless / agent (print URL only)
62+
$ trigger.dev login --no-browser
63+
64+
# Login to a named profile
65+
$ trigger.dev login --profile staging`
66+
)
67+
.option("--no-browser", "Don't automatically open the browser; print the URL only")
5368
)
5469
.version(VERSION, "-v, --version", "Display the version number")
5570
.action(async (options) => {
@@ -67,14 +82,20 @@ export async function loginCommand(options: unknown) {
6782
}
6883

6984
async function _loginCommand(options: LoginCommandOptions) {
70-
return login({ defaultApiUrl: options.apiUrl, embedded: false, profile: options.profile });
85+
return login({
86+
defaultApiUrl: options.apiUrl,
87+
embedded: false,
88+
profile: options.profile,
89+
browser: options.browser,
90+
});
7191
}
7292

7393
export type LoginOptions = {
7494
defaultApiUrl?: string;
7595
embedded?: boolean;
7696
profile?: string;
7797
silent?: boolean;
98+
browser?: boolean;
7899
};
79100

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

262-
if (await isLinuxServer()) {
283+
if (opts.browser === false) {
284+
log.message("Browser auto-open disabled. Visit the URL above to login.");
285+
} else if (await isLinuxServer()) {
263286
log.message("Please install `xdg-utils` to automatically open the login URL.");
264287
} else {
265288
await open(authorizationCodeResult.url);

0 commit comments

Comments
 (0)