Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
1,241 linesCodeBlameRaw
1//! The command line's shape: every command, flag and argument.
2
3use clap::{Args, Parser, Subcommand, ValueEnum};
4use std::path::PathBuf;
5
6pub const ABOUT: &str = "Work with g1t from the command line.";
7
8pub const AFTER_HELP: &str = "\
9Examples:
10 g1t auth login
11 g1t issue list --label bug
12 g1t pr create --fill
13 g1t pr checks --watch --fail-fast
14 g1t run logs --failed
15 g1t api list_issues --jq '.[].title'
16
17Environment:
18 G1T_TOKEN A token to use instead of the stored one
19 G1T_HOST The host to talk to when no repository names one
20 G1T_REPO The repository, as OWNER/NAME, instead of the git remote
21 NO_COLOR Plain output on a terminal too
22
23Exit codes: 0 done, 1 failed, 2 used wrongly, 4 sign-in needed, 8 checks pending.
24Docs: https://docs.g1t.sh/guides/cli/";
25
26#[derive(Debug, Parser)]
27#[command(
28 name = "g1t",
29 version,
30 about = ABOUT,
31 after_help = AFTER_HELP,
32 propagate_version = true,
33 max_term_width = 100,
34 arg_required_else_help = true
35)]
36pub struct Cli {
37 #[command(flatten)]
38 pub global: Global,
39 #[command(subcommand)]
40 pub command: Command,
41}
42
43#[derive(Debug, Args, Clone, Default)]
44pub struct Global {
45 /// The repository, as [HOST/]OWNER/NAME, instead of the current directory's.
46 #[arg(
47 short = 'R',
48 long,
49 global = true,
50 value_name = "[HOST/]OWNER/NAME",
51 env = "G1T_REPO"
52 )]
53 pub repo: Option<String>,
54 /// The g1t host, such as g1t.sh or git.example.com.
55 #[arg(long, global = true, value_name = "HOST")]
56 pub hostname: Option<String>,
57 /// Print JSON: every field, or only the comma-separated FIELDS.
58 #[arg(
59 long,
60 global = true,
61 value_name = "FIELDS",
62 num_args = 0..=1,
63 default_missing_value = "",
64 help_heading = "Output"
65 )]
66 pub json: Option<String>,
67 /// Run a jq expression over the JSON and print what it gives.
68 #[arg(
69 long,
70 global = true,
71 value_name = "EXPRESSION",
72 help_heading = "Output"
73 )]
74 pub jq: Option<String>,
75}
76
77#[derive(Debug, Subcommand)]
78pub enum Command {
79 /// Sign in, sign out, and set git up to use g1t's credentials.
80 #[command(subcommand)]
81 Auth(AuthCommand),
82 /// Repositories: view, clone, create, list.
83 #[command(subcommand)]
84 Repo(RepoCommand),
85 /// Issues: list, view, create, comment, close, reopen, edit.
86 #[command(subcommand)]
87 Issue(IssueCommand),
88 /// Pull requests: list, view, create, check out, diff, checks, review, merge.
89 #[command(subcommand)]
90 Pr(PrCommand),
91 /// Workflow runs: list, view, watch, logs, rerun, cancel.
92 #[command(subcommand)]
93 Run(RunCommand),
94 /// Secrets for workflows and deployments, a repository's or a workspace's.
95 #[command(subcommand)]
96 Secret(SecretCommand),
97 /// The repository's actions/cache entries: list, usage, delete.
98 #[command(subcommand)]
99 Cache(CacheCommand),
100 /// Deployments: list and view.
101 #[command(subcommand)]
102 Deploy(DeployCommand),
103 /// A project's logs and errors.
104 Logs(LogsArgs),
105 /// What needs you: your pull requests, reviews asked of you, your issues, failing checks, and your sessions and claims here.
106 Status(StatusArgs),
107 /// Sessions: one run of an agent's harness, recorded as it works.
108 #[command(subcommand)]
109 Session(SessionCommand),
110 /// Coordinate work on issues: context, claims, hand-offs, dependencies, events.
111 #[command(subcommand)]
112 Work(WorkCommand),
113 /// Why a file or line is as it is: its commit, pull request, issue, decisions and sessions.
114 Why(WhyArgs),
115 /// Call any API operation or path, with your token.
116 Api(ApiArgs),
117 /// Open the repository, an issue or a pull request in the browser.
118 Browse(BrowseArgs),
119 /// Shortcuts for commands you type often.
120 #[command(subcommand)]
121 Alias(AliasCommand),
122 /// Print a shell completion script.
123 Completion(CompletionArgs),
124 /// Push a local container image to g1t's registry, in chunks.
125 Push(PushArgs),
126}
127
128// --- auth -------------------------------------------------------------
129
130#[derive(Debug, Subcommand)]
131pub enum AuthCommand {
132 /// Sign in to a g1t host.
133 #[command(after_help = "\
134Without --with-token, g1t shows a code and a link; approve it in the browser, signed in.
135With --with-token, the token is read from stdin:
136 g1t auth login --with-token < token.txt")]
137 Login(LoginArgs),
138 /// Sign out of an account on a host.
139 Logout(AccountArgs),
140 /// Show which accounts are signed in, and whether their tokens work.
141 Status(AuthStatusArgs),
142 /// Print the token in use, for another program.
143 Token(AccountArgs),
144 /// Make another signed-in account on a host the active one.
145 Switch(AccountArgs),
146 /// Make git use g1t's credentials for pushing and fetching over HTTPS.
147 SetupGit(SetupGitArgs),
148 /// The git credential helper `setup-git` installs. Git runs it.
149 #[command(hide = true)]
150 GitCredential(GitCredentialArgs),
151}
152
153#[derive(Debug, Args)]
154pub struct LoginArgs {
155 /// Read a token from stdin instead of approving in the browser.
156 #[arg(long)]
157 pub with_token: bool,
158 /// Keep the token in the config file, not the system keychain.
159 #[arg(long)]
160 pub insecure_storage: bool,
161 /// Do not open the browser; just print the link.
162 #[arg(long)]
163 pub no_browser: bool,
164 /// Also set git up to use g1t's credentials (see `g1t auth setup-git`).
165 #[arg(long)]
166 pub git: bool,
167}
168
169#[derive(Debug, Args)]
170pub struct AccountArgs {
171 /// The account, when more than one is signed in on the host.
172 #[arg(short, long)]
173 pub user: Option<String>,
174}
175
176#[derive(Debug, Args)]
177pub struct AuthStatusArgs {
178 /// Print each token too.
179 #[arg(long)]
180 pub show_token: bool,
181}
182
183#[derive(Debug, Args)]
184pub struct SetupGitArgs {
185 /// Print the git config commands instead of running them.
186 #[arg(long)]
187 pub dry_run: bool,
188}
189
190#[derive(Debug, Args)]
191pub struct GitCredentialArgs {
192 /// get, store or erase, as git says.
193 pub operation: String,
194}
195
196// --- repo -------------------------------------------------------------
197
198#[derive(Debug, Subcommand)]
199pub enum RepoCommand {
200 /// Show a repository.
201 View(RepoViewArgs),
202 /// Clone a repository.
203 Clone(RepoCloneArgs),
204 /// Make a repository.
205 Create(RepoCreateArgs),
206 /// List repositories you can see.
207 List(RepoListArgs),
208}
209
210#[derive(Debug, Args)]
211pub struct RepoViewArgs {
212 /// OWNER/NAME; the current repository when left out.
213 pub repository: Option<String>,
214 /// Open it in the browser.
215 #[arg(short, long)]
216 pub web: bool,
217}
218
219#[derive(Debug, Args)]
220pub struct RepoCloneArgs {
221 /// OWNER/NAME, or NAME for one of your own.
222 pub repository: String,
223 /// Where to clone it.
224 pub directory: Option<String>,
225 /// Flags for git clone, after --.
226 #[arg(last = true)]
227 pub git_flags: Vec<String>,
228}
229
230#[derive(Debug, Args)]
231pub struct RepoCreateArgs {
232 /// NAME, or WORKSPACE/NAME.
233 pub name: Option<String>,
234 /// Anyone can see it. Repositories are private unless this is given.
235 #[arg(long, conflicts_with = "private")]
236 pub public: bool,
237 /// Only people with a role on it can see it (the default).
238 #[arg(long)]
239 pub private: bool,
240 #[arg(short, long)]
241 pub description: Option<String>,
242 /// Clone it once it is made.
243 #[arg(short, long)]
244 pub clone: bool,
245 /// Add it as a remote of this directory's repository and push it there.
246 #[arg(long, value_name = "DIR")]
247 pub source: Option<String>,
248 /// The remote's name, with --source.
249 #[arg(long, default_value = "origin")]
250 pub remote: String,
251}
252
253#[derive(Debug, Args)]
254pub struct RepoListArgs {
255 /// Only this workspace's.
256 pub workspace: Option<String>,
257 /// Only those whose name or description holds this.
258 #[arg(short, long)]
259 pub query: Option<String>,
260 #[arg(short = 'L', long, default_value_t = 20)]
261 pub limit: usize,
262}
263
264// --- issue ------------------------------------------------------------
265
266#[derive(Debug, Subcommand)]
267pub enum IssueCommand {
268 /// List issues.
269 List(IssueListArgs),
270 /// Show an issue, with its comments.
271 View(IssueViewArgs),
272 /// Open an issue.
273 Create(IssueCreateArgs),
274 /// Comment on an issue.
275 Comment(CommentArgs),
276 /// Close an issue.
277 Close(IssueCloseArgs),
278 /// Reopen an issue.
279 Reopen(ReopenArgs),
280 /// Change an issue's title, body, labels, assignees or milestone.
281 Edit(IssueEditArgs),
282}
283
284#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
285pub enum IssueState {
286 Open,
287 Closed,
288 All,
289}
290
291#[derive(Debug, Args)]
292pub struct IssueListArgs {
293 #[arg(short, long, value_enum, default_value_t = IssueState::Open)]
294 pub state: IssueState,
295 /// Only issues with this label.
296 #[arg(short, long)]
297 pub label: Option<String>,
298 /// Only issues in the milestone of this number.
299 #[arg(short, long)]
300 pub milestone: Option<u64>,
301 /// Only issues assigned to this person (@me for you).
302 #[arg(short, long)]
303 pub assignee: Option<String>,
304 /// Only issues opened by this person (@me for you).
305 #[arg(short = 'A', long)]
306 pub author: Option<String>,
307 #[arg(short = 'L', long, default_value_t = 20)]
308 pub limit: usize,
309}
310
311#[derive(Debug, Args)]
312pub struct IssueViewArgs {
313 /// The issue's number.
314 pub number: u64,
315 /// Show its comments too.
316 #[arg(short, long)]
317 pub comments: bool,
318 /// Open it in the browser.
319 #[arg(short, long)]
320 pub web: bool,
321}
322
323#[derive(Debug, Args)]
324pub struct BodyArgs {
325 /// The text. Markdown.
326 #[arg(short, long)]
327 pub body: Option<String>,
328 /// Read the text from a file, or - for stdin.
329 #[arg(short = 'F', long, value_name = "FILE")]
330 pub body_file: Option<String>,
331}
332
333#[derive(Debug, Args)]
334pub struct IssueCreateArgs {
335 #[arg(short, long)]
336 pub title: Option<String>,
337 #[command(flatten)]
338 pub body: BodyArgs,
339 /// A label; repeat for more.
340 #[arg(short, long)]
341 pub label: Vec<String>,
342 /// Someone to assign; repeat for more (@me for you).
343 #[arg(short, long)]
344 pub assignee: Vec<String>,
345 /// A milestone's number.
346 #[arg(short, long)]
347 pub milestone: Option<u64>,
348 /// Open the new-issue page in the browser instead.
349 #[arg(short, long)]
350 pub web: bool,
351}
352
353#[derive(Debug, Args)]
354pub struct CommentArgs {
355 /// The number; for a pull request, the current branch's when left out.
356 pub number: Option<u64>,
357 #[command(flatten)]
358 pub body: BodyArgs,
359}
360
361#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
362pub enum CloseReason {
363 Completed,
364 NotPlanned,
365}
366
367#[derive(Debug, Args)]
368pub struct IssueCloseArgs {
369 pub number: u64,
370 #[arg(short, long, value_enum)]
371 pub reason: Option<CloseReason>,
372 /// Leave a comment as it closes.
373 #[arg(short, long)]
374 pub comment: Option<String>,
375}
376
377#[derive(Debug, Args)]
378pub struct ReopenArgs {
379 pub number: u64,
380 /// Leave a comment as it reopens.
381 #[arg(short, long)]
382 pub comment: Option<String>,
383}
384
385#[derive(Debug, Args)]
386pub struct IssueEditArgs {
387 pub number: u64,
388 #[arg(short, long)]
389 pub title: Option<String>,
390 #[command(flatten)]
391 pub body: BodyArgs,
392 #[arg(long, value_name = "LABEL")]
393 pub add_label: Vec<String>,
394 #[arg(long, value_name = "LABEL")]
395 pub remove_label: Vec<String>,
396 #[arg(long, value_name = "LOGIN")]
397 pub add_assignee: Vec<String>,
398 #[arg(long, value_name = "LOGIN")]
399 pub remove_assignee: Vec<String>,
400 /// A milestone's number.
401 #[arg(short, long)]
402 pub milestone: Option<u64>,
403 /// Take it out of its milestone.
404 #[arg(long, conflicts_with = "milestone")]
405 pub remove_milestone: bool,
406}
407
408// --- pr ---------------------------------------------------------------
409
410#[derive(Debug, Subcommand)]
411pub enum PrCommand {
412 /// List pull requests.
413 List(PrListArgs),
414 /// Show a pull request.
415 View(PrViewArgs),
416 /// Open a pull request from a branch, or start one for an issue.
417 Create(PrCreateArgs),
418 /// Check a pull request's changes out locally.
419 Checkout(PrCheckoutArgs),
420 /// Show a pull request's changes as a diff.
421 Diff(PrDiffArgs),
422 /// Show a pull request's checks; --watch waits for them.
423 Checks(PrChecksArgs),
424 /// Approve a pull request, or ask for changes.
425 Review(PrReviewArgs),
426 /// Merge a pull request.
427 Merge(PrMergeArgs),
428 /// Mark a draft ready for review, or turn it back into a draft.
429 Ready(PrReadyArgs),
430 /// Comment on a pull request.
431 Comment(CommentArgs),
432 /// Close a pull request without merging it.
433 Close(PrSelect),
434 /// Reopen a closed pull request.
435 Reopen(PrSelect),
436}
437
438#[derive(Debug, Args)]
439pub struct PrSelect {
440 /// The number; the current branch's when left out.
441 pub number: Option<u64>,
442}
443
444#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
445pub enum PrState {
446 Open,
447 Closed,
448 Merged,
449 All,
450}
451
452#[derive(Debug, Args)]
453pub struct PrListArgs {
454 #[arg(short, long, value_enum, default_value_t = PrState::Open)]
455 pub state: PrState,
456 /// Only those merging into this branch.
457 #[arg(short = 'B', long)]
458 pub base: Option<String>,
459 #[arg(short, long)]
460 pub label: Option<String>,
461 /// Only those opened by this person (@me for you).
462 #[arg(short = 'A', long)]
463 pub author: Option<String>,
464 /// Only drafts.
465 #[arg(short, long)]
466 pub draft: bool,
467 #[arg(short = 'L', long, default_value_t = 20)]
468 pub limit: usize,
469}
470
471#[derive(Debug, Args)]
472pub struct PrViewArgs {
473 /// The number; the current branch's when left out.
474 pub number: Option<u64>,
475 #[arg(short, long)]
476 pub comments: bool,
477 #[arg(short, long)]
478 pub web: bool,
479}
480
481#[derive(Debug, Args)]
482pub struct PrCreateArgs {
483 #[arg(short, long)]
484 pub title: Option<String>,
485 #[command(flatten)]
486 pub body: BodyArgs,
487 /// The branch to merge into; the default branch when left out.
488 #[arg(short = 'B', long)]
489 pub base: Option<String>,
490 /// The branch with the changes; the current one when left out.
491 #[arg(short = 'H', long)]
492 pub head: Option<String>,
493 /// The issue it is for. Without a branch, starts a draft in a working copy of its own.
494 #[arg(short, long)]
495 pub issue: Option<u64>,
496 /// Take the title and body from the branch's commits.
497 #[arg(short, long)]
498 pub fill: bool,
499 /// Open it as a draft.
500 #[arg(short, long)]
501 pub draft: bool,
502 /// Do not push the branch first.
503 #[arg(long)]
504 pub no_push: bool,
505}
506
507#[derive(Debug, Args)]
508pub struct PrCheckoutArgs {
509 pub number: u64,
510 /// The local branch's name; pr-NUMBER when left out.
511 #[arg(short, long)]
512 pub branch: Option<String>,
513 /// Reset the local branch to the pull request's head even if it has other commits.
514 #[arg(short, long)]
515 pub force: bool,
516}
517
518#[derive(Debug, Args)]
519pub struct PrDiffArgs {
520 pub number: Option<u64>,
521 /// Only the changed files' names.
522 #[arg(long)]
523 pub name_only: bool,
524 /// Color: auto, always or never.
525 #[arg(long, default_value = "auto")]
526 pub color: String,
527}
528
529#[derive(Debug, Args)]
530pub struct PrChecksArgs {
531 pub number: Option<u64>,
532 /// Wait until every check has finished.
533 #[arg(short, long)]
534 pub watch: bool,
535 /// With --watch, stop at the first failing check.
536 #[arg(long, requires = "watch")]
537 pub fail_fast: bool,
538 /// Seconds between looks, with --watch.
539 #[arg(short, long, default_value_t = 10)]
540 pub interval: u64,
541}
542
543#[derive(Debug, Args)]
544#[command(group(clap::ArgGroup::new("verdict").required(true).args(["approve", "request_changes", "comment"])))]
545pub struct PrReviewArgs {
546 pub number: Option<u64>,
547 #[arg(short, long)]
548 pub approve: bool,
549 #[arg(short, long)]
550 pub request_changes: bool,
551 /// Comment without approving or asking for changes.
552 #[arg(short, long)]
553 pub comment: bool,
554 #[command(flatten)]
555 pub body: BodyArgs,
556}
557
558#[derive(Debug, Args)]
559pub struct PrMergeArgs {
560 pub number: Option<u64>,
561 /// Leave the issue it is for open.
562 #[arg(long)]
563 pub keep_issue_open: bool,
564 /// Merge past failing required checks and rules (needs the Admin role).
565 #[arg(long)]
566 pub admin: bool,
567 /// Delete the local and remote branch afterwards.
568 #[arg(short, long)]
569 pub delete_branch: bool,
570}
571
572#[derive(Debug, Args)]
573pub struct PrReadyArgs {
574 pub number: Option<u64>,
575 /// What changed and why; it becomes the description. The current one when left out.
576 #[arg(short, long)]
577 pub summary: Option<String>,
578 /// Turn it back into a draft.
579 #[arg(long)]
580 pub undo: bool,
581}
582
583// --- run --------------------------------------------------------------
584
585#[derive(Debug, Subcommand)]
586pub enum RunCommand {
587 /// List workflow runs, newest first.
588 List(RunListArgs),
589 /// Show a run's jobs and steps.
590 View(RunViewArgs),
591 /// Follow a run until it finishes; exits 1 if it fails.
592 Watch(RunWatchArgs),
593 /// Print a run's logs; --failed prints only the failing steps' tails.
594 Logs(RunLogsArgs),
595 /// Run a run again: all of it, its failed jobs, or one job.
596 Rerun(RunRerunArgs),
597 /// Cancel a run.
598 Cancel(RunSelect),
599}
600
601#[derive(Debug, Args)]
602pub struct RunSelect {
603 /// The run's id (run_…) or number; the current branch's latest when left out.
604 pub run: Option<String>,
605}
606
607#[derive(Debug, Args)]
608pub struct RunListArgs {
609 /// Only this workflow's: its file name or id.
610 #[arg(short, long)]
611 pub workflow: Option<String>,
612 #[arg(short, long)]
613 pub branch: Option<String>,
614 /// Only runs for this event: push, pull_request, schedule...
615 #[arg(short, long)]
616 pub event: Option<String>,
617 /// Only runs of this commit.
618 #[arg(short, long)]
619 pub commit: Option<String>,
620 /// Only runs for this pull request.
621 #[arg(long)]
622 pub pr: Option<u64>,
623 #[arg(short = 'L', long, default_value_t = 20)]
624 pub limit: usize,
625}
626
627#[derive(Debug, Args)]
628pub struct RunViewArgs {
629 pub run: Option<String>,
630 /// Show this job's steps only.
631 #[arg(short, long)]
632 pub job: Option<String>,
633 /// Print the log too.
634 #[arg(long)]
635 pub log: bool,
636 /// Print the failing steps' logs too.
637 #[arg(long)]
638 pub log_failed: bool,
639 #[arg(short, long)]
640 pub web: bool,
641 /// Exit 1 when the run failed.
642 #[arg(long)]
643 pub exit_status: bool,
644}
645
646#[derive(Debug, Args)]
647pub struct RunWatchArgs {
648 pub run: Option<String>,
649 /// Seconds between looks.
650 #[arg(short, long, default_value_t = 3)]
651 pub interval: u64,
652}
653
654#[derive(Debug, Args)]
655pub struct RunLogsArgs {
656 pub run: Option<String>,
657 /// Only this job's (its id, key or name).
658 #[arg(short, long)]
659 pub job: Option<String>,
660 /// Only the failing steps, and only their last lines.
661 #[arg(short, long)]
662 pub failed: bool,
663 /// How many lines of each failing step to print, with --failed.
664 #[arg(short = 'n', long, default_value_t = 60)]
665 pub tail: usize,
666}
667
668#[derive(Debug, Args)]
669pub struct RunRerunArgs {
670 pub run: Option<String>,
671 /// Only the jobs that failed.
672 #[arg(long)]
673 pub failed: bool,
674 /// Only this job (its id).
675 #[arg(short, long, conflicts_with = "failed")]
676 pub job: Option<String>,
677 /// With debug logging.
678 #[arg(short, long)]
679 pub debug: bool,
680}
681
682// --- secret -----------------------------------------------------------
683
684#[derive(Debug, Subcommand)]
685pub enum SecretCommand {
686 /// List secrets' names (never their values).
687 List(SecretScope),
688 /// Set a secret. The value is read from stdin when not given.
689 Set(SecretSetArgs),
690 /// Delete a secret.
691 Delete(SecretDeleteArgs),
692}
693
694#[derive(Debug, Args)]
695pub struct SecretScope {
696 /// A workspace's secrets instead of the repository's.
697 #[arg(short, long, value_name = "WORKSPACE")]
698 pub workspace: Option<String>,
699 /// Only the value for this environment (production, preview...).
700 #[arg(short, long, value_name = "ENVIRONMENT")]
701 pub env: Option<String>,
702}
703
704#[derive(Debug, Args)]
705pub struct SecretSetArgs {
706 /// The secret's name; uppercased.
707 pub name: String,
708 /// The value. Read from stdin when left out, so it stays out of your shell's history.
709 #[arg(short, long)]
710 pub body: Option<String>,
711 #[command(flatten)]
712 pub scope: SecretScope,
713 /// Who can read it: workflows, deployments, or both (repeat).
714 #[arg(long, value_name = "WHO")]
715 pub available_to: Vec<String>,
716 /// Broker it: jobs get a stand-in, and g1t fills the value in only on requests to this host. Repeat for more.
717 #[arg(long, value_name = "HOST")]
718 pub brokered_host: Vec<String>,
719 /// A note for whoever reads the list.
720 #[arg(long)]
721 pub note: Option<String>,
722}
723
724#[derive(Debug, Args)]
725pub struct SecretDeleteArgs {
726 pub name: String,
727 #[command(flatten)]
728 pub scope: SecretScope,
729}
730
731// --- cache ------------------------------------------------------------
732
733#[derive(Debug, Subcommand)]
734pub enum CacheCommand {
735 /// List the repository's cache entries, least recently used last.
736 List(CacheListArgs),
737 /// What the repository's cache entries hold, against its quota.
738 Usage,
739 /// Delete cache entries: one by its id, or every entry with a key.
740 Delete(CacheDeleteArgs),
741}
742
743#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
744pub enum CacheSort {
745 /// When it was last restored or looked up.
746 LastAccessedAt,
747 CreatedAt,
748 SizeInBytes,
749}
750
751#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
752pub enum SortOrder {
753 Asc,
754 Desc,
755}
756
757#[derive(Debug, Args)]
758pub struct CacheListArgs {
759 /// Only entries whose key starts with this.
760 #[arg(short, long)]
761 pub key: Option<String>,
762 /// Only entries saved by this ref's runs, e.g. refs/heads/main.
763 #[arg(short, long = "ref", value_name = "REF")]
764 pub git_ref: Option<String>,
765 #[arg(short = 'S', long, value_enum, default_value_t = CacheSort::LastAccessedAt)]
766 pub sort: CacheSort,
767 #[arg(short = 'O', long, value_enum, default_value_t = SortOrder::Desc)]
768 pub order: SortOrder,
769 #[arg(short = 'L', long, default_value_t = 30)]
770 pub limit: usize,
771}
772
773#[derive(Debug, Args)]
774pub struct CacheDeleteArgs {
775 /// An entry's id (a number, from `g1t cache list`), or a key: every entry with exactly that key.
776 pub entry: String,
777 /// With a key, only the entries this ref's runs saved.
778 #[arg(short, long = "ref", value_name = "REF")]
779 pub git_ref: Option<String>,
780}
781
782// --- deploy -----------------------------------------------------------
783
784#[derive(Debug, Subcommand)]
785pub enum DeployCommand {
786 /// List deployments, newest first.
787 List(DeployListArgs),
788 /// Show a deployment and its statuses.
789 View(DeployViewArgs),
790}
791
792#[derive(Debug, Args)]
793pub struct DeployListArgs {
794 #[arg(short, long)]
795 pub env: Option<String>,
796 /// queued, in_progress, success, failure, error or inactive.
797 #[arg(short, long)]
798 pub state: Option<String>,
799 /// api, actions or g1t_page.
800 #[arg(long)]
801 pub source: Option<String>,
802 #[arg(short = 'L', long, default_value_t = 20)]
803 pub limit: usize,
804}
805
806#[derive(Debug, Args)]
807pub struct DeployViewArgs {
808 pub id: String,
809 #[arg(short, long)]
810 pub web: bool,
811}
812
813// --- logs -------------------------------------------------------------
814
815#[derive(Debug, Args)]
816pub struct LogsArgs {
817 /// The project; the current repository's when left out.
818 #[arg(short, long)]
819 pub project: Option<String>,
820 /// Grouped errors instead of log lines.
821 #[arg(long)]
822 pub errors: bool,
823 /// Lines holding all of these words, or a "quoted phrase".
824 #[arg(short, long)]
825 pub query: Option<String>,
826 /// Levels: trace, debug, info, warn, error, fatal (comma-separated).
827 #[arg(short, long)]
828 pub level: Option<String>,
829 #[arg(short, long)]
830 pub env: Option<String>,
831 #[arg(long)]
832 pub service: Option<String>,
833 /// How far back: 15m, 2h, 1d, or a time like 2026-10-12T09:00:00Z.
834 #[arg(short, long)]
835 pub since: Option<String>,
836 /// With --errors: open, resolved or ignored.
837 #[arg(long)]
838 pub state: Option<String>,
839 /// Keep printing new lines as they come.
840 #[arg(short, long, conflicts_with = "errors")]
841 pub follow: bool,
842 #[arg(short = 'L', long, default_value_t = 50)]
843 pub limit: usize,
844}
845
846// --- session ----------------------------------------------------------
847
848#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
849pub enum Harness {
850 // Spelled as the API spells it, which is what the plugin sends.
851 #[value(name = "claude_code", alias = "claude-code")]
852 ClaudeCode,
853 Codex,
854 Cursor,
855 Copilot,
856 Other,
857}
858
859impl Harness {
860 pub fn name(self) -> &'static str {
861 match self {
862 Harness::ClaudeCode => "claude_code",
863 Harness::Codex => "codex",
864 Harness::Cursor => "cursor",
865 Harness::Copilot => "copilot",
866 Harness::Other => "other",
867 }
868 }
869}
870
871#[derive(Debug, Subcommand)]
872pub enum SessionCommand {
873 /// Start a session and remember it for this working copy. Prints its id.
874 #[command(after_help = "\
875The id is kept in .git/g1t/session (each worktree has its own); G1T_SESSION overrides it.
876Starting again with the same --harness-session-id returns the same session.")]
877 Start(SessionStartArgs),
878 /// Say the current session is still working; renews its claims.
879 Heartbeat,
880 /// Record a note, prompt or message in the current session.
881 Log(SessionLogArgs),
882 /// Attach the current session to an issue, a pull request or a branch.
883 Attach(SessionAttachArgs),
884 /// End the current session.
885 End(SessionEndArgs),
886 /// List the repository's sessions.
887 List(SessionListArgs),
888 /// Show a session and what it recorded; the current one when left out.
889 View(SessionViewArgs),
890}
891
892#[derive(Debug, Args)]
893pub struct SessionStartArgs {
894 /// The issue it works on.
895 #[arg(short, long)]
896 pub issue: Option<u64>,
897 /// The pull request it works on.
898 #[arg(short, long)]
899 pub pull: Option<u64>,
900 /// The harness running it.
901 #[arg(long, value_enum, default_value_t = Harness::Other)]
902 pub harness: Harness,
903 /// The harness's own id for the run, which makes starting again return the same session.
904 #[arg(long, value_name = "ID")]
905 pub harness_session_id: Option<String>,
906 #[arg(short, long)]
907 pub title: Option<String>,
908 /// The branch it works on; the current one when left out.
909 #[arg(short, long)]
910 pub branch: Option<String>,
911}
912
913#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
914pub enum EntryKind {
915 Note,
916 Prompt,
917 Message,
918 ToolCall,
919}
920
921impl EntryKind {
922 pub fn name(self) -> &'static str {
923 match self {
924 EntryKind::Note => "note",
925 EntryKind::Prompt => "prompt",
926 EntryKind::Message => "message",
927 EntryKind::ToolCall => "tool_call",
928 }
929 }
930}
931
932#[derive(Debug, Args)]
933pub struct SessionLogArgs {
934 /// What to record; - reads it from stdin.
935 pub text: String,
936 #[arg(short, long, value_enum, default_value_t = EntryKind::Note)]
937 pub kind: EntryKind,
938 /// The tool, for a tool_call.
939 #[arg(long)]
940 pub tool: Option<String>,
941 /// A key that makes recording it again do nothing.
942 #[arg(long)]
943 pub key: Option<String>,
944}
945
946#[derive(Debug, Args)]
947#[command(group(clap::ArgGroup::new("what").required(true).multiple(true).args(["issue", "pull", "branch", "head_commit"])))]
948pub struct SessionAttachArgs {
949 #[arg(short, long)]
950 pub issue: Option<u64>,
951 #[arg(short, long)]
952 pub pull: Option<u64>,
953 #[arg(short, long)]
954 pub branch: Option<String>,
955 /// The commit it pushed last.
956 #[arg(long, value_name = "SHA")]
957 pub head_commit: Option<String>,
958}
959
960#[derive(Debug, Args)]
961pub struct SessionEndArgs {
962 /// Why it ended: done, abandoned, handed_off...
963 #[arg(short, long)]
964 pub reason: Option<String>,
965}
966
967#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
968pub enum SessionState {
969 Active,
970 Idle,
971 Ended,
972}
973
974#[derive(Debug, Args)]
975pub struct SessionListArgs {
976 /// Only sessions in this state.
977 #[arg(short, long, value_enum)]
978 pub state: Option<SessionState>,
979 #[arg(short, long)]
980 pub issue: Option<u64>,
981 #[arg(short, long)]
982 pub pull: Option<u64>,
983 #[arg(short = 'L', long, default_value_t = 20)]
984 pub limit: usize,
985}
986
987#[derive(Debug, Args)]
988pub struct SessionViewArgs {
989 /// The session's id (ses_…); the current one when left out.
990 pub id: Option<String>,
991}
992
993// --- work -------------------------------------------------------------
994
995#[derive(Debug, Subcommand)]
996pub enum WorkCommand {
997 /// Everything about an issue's work: claims, sessions, pull requests, dependencies, decisions.
998 Context(WorkNumber),
999 /// Claim an issue: say you are working on it. Others are told, never stopped.
1000 Claim(WorkClaimArgs),
1001 /// Release your claim on an issue.
1002 Release(WorkReleaseArgs),
1003 /// Hand an issue over to someone else, or to anyone.
1004 Handoff(WorkHandoffArgs),
1005 /// Active claims and sessions in the repository.
1006 List,
1007 /// Say an issue waits on others: it is blocked by them.
1008 Block(WorkBlockArgs),
1009 /// Say an issue no longer waits on others.
1010 Unblock(WorkBlockArgs),
1011 /// The repository's events; --follow keeps printing them as they happen.
1012 #[command(after_help = "\
1013Examples:
1014 g1t work events --follow --issue 12
1015 g1t work events --follow --type integration.failed --type overlap.detected --json
1016With --json, each event is one JSON object on a line of its own.")]
1017 Events(WorkEventsArgs),
1018}
1019
1020#[derive(Debug, Args)]
1021pub struct WorkNumber {
1022 /// The issue's number.
1023 pub number: u64,
1024}
1025
1026#[derive(Debug, Args)]
1027pub struct WorkClaimArgs {
1028 /// The issue's number.
1029 pub number: u64,
1030 /// What you are doing about it.
1031 #[arg(short, long)]
1032 pub note: Option<String>,
1033 /// How long the claim lasts unless renewed: 30m, 2h (at most 4h).
1034 #[arg(long, value_name = "DURATION", value_parser = parse_ttl)]
1035 pub ttl: Option<u64>,
1036}
1037
1038#[derive(Debug, Args)]
1039pub struct WorkReleaseArgs {
1040 pub number: u64,
1041 /// Why: done, blocked, abandoned...
1042 #[arg(short, long)]
1043 pub reason: Option<String>,
1044}
1045
1046#[derive(Debug, Args)]
1047pub struct WorkHandoffArgs {
1048 pub number: u64,
1049 /// Who takes it over; anyone when left out.
1050 #[arg(long, value_name = "LOGIN")]
1051 pub to: Option<String>,
1052 /// Where it stands and what is left, for whoever takes it.
1053 #[arg(short, long)]
1054 pub note: String,
1055}
1056
1057#[derive(Debug, Args)]
1058pub struct WorkBlockArgs {
1059 /// The issue that waits.
1060 pub number: u64,
1061 /// The issue it waits on; repeat for more.
1062 #[arg(long = "by", value_name = "NUMBER", required = true)]
1063 pub by: Vec<u64>,
1064}
1065
1066#[derive(Debug, Args)]
1067pub struct WorkEventsArgs {
1068 /// Keep printing events as they happen, until stopped.
1069 #[arg(short, long)]
1070 pub follow: bool,
1071 /// Only events of this type; repeat for more.
1072 #[arg(short = 't', long = "type", value_name = "TYPE")]
1073 pub types: Vec<String>,
1074 /// Only events about this issue or pull request; repeat for more.
1075 #[arg(short, long = "issue", value_name = "NUMBER")]
1076 pub issues: Vec<u64>,
1077 /// Only events since then: 15m, 2h, 1d, or a time like 2026-10-12T09:00:00Z.
1078 #[arg(short, long)]
1079 pub since: Option<String>,
1080 /// Start after this event id, as a follow that stopped would resume.
1081 #[arg(long, value_name = "EVENT_ID")]
1082 pub after: Option<String>,
1083 /// How many past events to show first.
1084 #[arg(short = 'L', long, default_value_t = 20)]
1085 pub limit: usize,
1086}
1087
1088/// `30m`, `2h`, `90` (minutes) as minutes, from 1 minute to 4 hours.
1089pub fn parse_ttl(text: &str) -> Result<u64, String> {
1090 let text = text.trim();
1091 let (number, unit) = text.split_at(text.find(|c: char| !c.is_ascii_digit()).unwrap_or(text.len()));
1092 let wrong = || format!("`{text}` is not a duration: write it as 30m or 2h");
1093 let number: u64 = number.parse().map_err(|_| wrong())?;
1094 let minutes = match unit {
1095 "" | "m" | "min" => number,
1096 "h" => number * 60,
1097 _ => return Err(wrong()),
1098 };
1099 if minutes == 0 || minutes > 240 {
1100 return Err("a claim lasts from 1 minute to 4 hours".to_owned());
1101 }
1102 Ok(minutes)
1103}
1104
1105// --- why --------------------------------------------------------------
1106
1107#[derive(Debug, Args)]
1108pub struct WhyArgs {
1109 /// A file, or a line of it as PATH:LINE.
1110 pub target: String,
1111 /// The branch, tag or commit to read it at; the default branch when left out.
1112 #[arg(long = "ref", value_name = "REF")]
1113 pub reference: Option<String>,
1114}
1115
1116// --- status, api, browse ---------------------------------------------
1117
1118#[derive(Debug, Args)]
1119pub struct StatusArgs {}
1120
1121#[derive(Debug, Args)]
1122#[command(after_help = "\
1123ENDPOINT is an operation's name (list_issues) or a path (/repos/{owner}/{name}/issues).
1124{owner}, {name}, {repo} and {workspace} are filled in from the current repository,
1125and an operation's other path parameters from -f/-F fields of the same name.
1126
1127Examples:
1128 g1t api list_issues -f state=closed
1129 g1t api /user --jq .username
1130 g1t api create_issue -f title='Broken link' -f labels[]=docs
1131 g1t api list_workflow_runs --paginate --jq '.[].conclusion'
1132 g1t api --list issue")]
1133pub struct ApiArgs {
1134 /// An operation's name, or a path.
1135 pub endpoint: Option<String>,
1136 /// List the operations, or those matching this word.
1137 #[arg(long, value_name = "QUERY", num_args = 0..=1, default_missing_value = "")]
1138 pub list: Option<String>,
1139 /// The HTTP method: GET unless fields are given, then POST.
1140 #[arg(short = 'X', long)]
1141 pub method: Option<String>,
1142 /// A string field, key=value. key[]=value adds to a list.
1143 #[arg(short = 'f', long = "raw-field", value_name = "KEY=VALUE")]
1144 pub raw_field: Vec<String>,
1145 /// A typed field: true, false, null and numbers become JSON; @file reads a file.
1146 #[arg(short = 'F', long = "field", value_name = "KEY=VALUE")]
1147 pub field: Vec<String>,
1148 /// A request header, key:value.
1149 #[arg(short = 'H', long = "header", value_name = "KEY:VALUE")]
1150 pub header: Vec<String>,
1151 /// The request body, from a file or - for stdin.
1152 #[arg(long, value_name = "FILE")]
1153 pub input: Option<String>,
1154 /// Follow next pages until there are none.
1155 #[arg(long)]
1156 pub paginate: bool,
1157 /// With --paginate, put every page's items into one array.
1158 #[arg(long, requires = "paginate")]
1159 pub slurp: bool,
1160 /// Print the status line and headers too.
1161 #[arg(short, long)]
1162 pub include: bool,
1163 /// Print nothing on success.
1164 #[arg(long)]
1165 pub silent: bool,
1166}
1167
1168#[derive(Debug, Args)]
1169pub struct BrowseArgs {
1170 /// An issue or pull request number, a file path (path:line), or a commit.
1171 pub target: Option<String>,
1172 /// The branch to show files on.
1173 #[arg(short, long)]
1174 pub branch: Option<String>,
1175 #[arg(short, long)]
1176 pub settings: bool,
1177 #[arg(long)]
1178 pub issues: bool,
1179 #[arg(long)]
1180 pub pulls: bool,
1181 #[arg(long)]
1182 pub actions: bool,
1183 /// Print the address instead of opening it.
1184 #[arg(short, long)]
1185 pub no_browser: bool,
1186}
1187
1188// --- alias, completion, push -----------------------------------------
1189
1190#[derive(Debug, Subcommand)]
1191pub enum AliasCommand {
1192 /// Make an alias: g1t alias set co 'pr checkout'. Start it with ! to run it through the shell.
1193 Set(AliasSetArgs),
1194 /// List aliases.
1195 List,
1196 /// Delete an alias.
1197 Delete(AliasDeleteArgs),
1198}
1199
1200#[derive(Debug, Args)]
1201pub struct AliasSetArgs {
1202 pub name: String,
1203 /// What it expands to. $1, $2... take the arguments after it.
1204 pub expansion: String,
1205}
1206
1207#[derive(Debug, Args)]
1208pub struct AliasDeleteArgs {
1209 pub name: String,
1210}
1211
1212#[derive(Debug, Args)]
1213pub struct CompletionArgs {
1214 #[arg(value_enum)]
1215 pub shell: clap_complete::Shell,
1216}
1217
1218#[derive(Debug, Args)]
1219#[command(after_help = "\
1220The token is the first of: --token-stdin, G1T_TOKEN, the account `g1t auth login`
1221signed in for the registry's host, or what `docker login g1t.sh` stored.")]
1222pub struct PushArgs {
1223 /// The local image, [REGISTRY/]NAME[:TAG].
1224 pub image: Option<String>,
1225 /// Where to push, when the image's own name is not an address on g1t.sh. The tag defaults to latest.
1226 #[arg(long = "as", value_name = "ADDRESS")]
1227 pub target: Option<String>,
1228 /// How much of a layer each request carries: 5MB to 95MB.
1229 #[arg(long, value_name = "SIZE", default_value = "90MB", value_parser = parse_chunk)]
1230 pub chunk_size: u64,
1231 /// Read the g1t token from stdin.
1232 #[arg(long)]
1233 pub token_stdin: bool,
1234 /// Push a tarball `docker save` wrote, instead of asking docker for the image.
1235 #[arg(long, value_name = "FILE", conflicts_with = "image")]
1236 pub archive: Option<PathBuf>,
1237}
1238
1239fn parse_chunk(text: &str) -> Result<u64, String> {
1240 crate::chunk::check_chunk(crate::chunk::parse_size(text)?)
1241}