main
py 156 lines 4.47 KB
Raw
1 # Copyright 2026 Google LLC
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import os
16 from typing import Optional
17
18 import click
19 import typer
20 from typer.core import TyperGroup
21 from typing_extensions import Annotated
22
23 from colab_cli import auto_update
24 from colab_cli.auth import AuthProvider
25 from colab_cli.common import state, setup_logging
26 from colab_cli.commands import session, execution, files, automation, run, ssh, utility
27
28
29 class AlphabeticalGroup(TyperGroup):
30 """A `TyperGroup` that lists subcommands alphabetically in `--help` output.
31
32 Subcommands are registered in functional groups (session, execution, files,
33 automation, utility), but users discovering the CLI via `colab --help` /
34 `colab help` benefit from a deterministic, alphabetical listing.
35 """
36
37 def list_commands(self, ctx: click.Context) -> list[str]:
38 return sorted(super().list_commands(ctx))
39
40
41 app = typer.Typer(
42 help="Colab CLI",
43 no_args_is_help=True,
44 context_settings={"help_option_names": ["-h", "--help"]},
45 cls=AlphabeticalGroup,
46 )
47
48
49 @app.callback()
50 def callback(
51 ctx: typer.Context,
52 client_oauth_config: Annotated[
53 str,
54 typer.Option(
55 "-c", "--client-oauth-config", help="Path to client OAuth config JSON file"
56 ),
57 ] = os.path.expanduser("~/.colab-cli-oauth-config.json"),
58 config: Annotated[
59 Optional[str],
60 typer.Option(
61 "--config",
62 help="Path to session state file (~/.config/colab-cli/sessions.json)",
63 ),
64 ] = None,
65 logtostderr: Annotated[
66 bool, typer.Option("--logtostderr", help="Log all output to stderr")
67 ] = False,
68 auth: Annotated[
69 AuthProvider,
70 typer.Option(
71 "--auth",
72 help=(
73 "Authentication strategy to use: 'oauth2' (public InstalledAppFlow),"
74 " or 'adc' (Application Default Credentials)."
75 ),
76 case_sensitive=False,
77 ),
78 ] = AuthProvider.OAUTH2,
79 ):
80 """
81 Colab CLI global configuration.
82 """
83 state.client_oauth_config = client_oauth_config
84 state.config_path = config
85 state.logtostderr = logtostderr
86 state.auth_provider = auth
87 setup_logging(logtostderr)
88
89 # Daily fetch + cached banner on every invocation.
90 #
91 # Suppress the banner for short-lived informational subcommands so their
92 # output stays clean and machine-parseable:
93 # - `update`: runs its own check + announce; would duplicate the banner.
94 # - `version`, `log`, `pay`, `help`, `url`: pure-display commands whose
95 # output users routinely pipe / scrape (e.g. `colab url -s s1 | xclip`);
96 # a stochastic upgrade banner injected once a day would corrupt those
97 # pipelines.
98 # - `whoami`: developer-only debugging tool; banner would obscure the
99 # auth/scope info the user invoked it to see.
100 _AUTO_UPDATE_SUPPRESSED = {
101 "update",
102 "version",
103 "log",
104 "pay",
105 "help",
106 "url",
107 "whoami",
108 "readme",
109 "README",
110 "skill",
111 "SKILL",
112 }
113 if ctx.invoked_subcommand not in _AUTO_UPDATE_SUPPRESSED:
114 auto_update.run_background_check()
115
116
117 @app.command(name="help")
118 def help_command(
119 ctx: typer.Context,
120 command: Annotated[
121 Optional[str], typer.Argument(help="Command to show help for")
122 ] = None,
123 ):
124 """
125 Show help for a command.
126 """
127 if not command:
128 typer.echo(ctx.parent.get_help())
129 return
130
131 group = ctx.parent.command
132 cmd = group.get_command(ctx, command)
133 if cmd is None:
134 typer.echo(f"No such command '{command}'.", err=True)
135 raise typer.Exit(code=2)
136
137 with click.Context(cmd, info_name=command, parent=ctx.parent) as cmd_ctx:
138 typer.echo(cmd.get_help(cmd_ctx))
139
140
141 # Register subcommands
142 session.register(app)
143 execution.register(app)
144 files.register(app)
145 automation.register(app)
146 run.register(app)
147 ssh.register(app)
148 utility.register(app)
149
150
151 def main():
152 app()
153
154
155 if __name__ == "__main__":
156 main()