1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
use clap::{Args, Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Debug, Parser)]
#[command(
name = "shoka",
about = "shoka — your repository bookshelf",
long_about = "Repository workspace manager: jj-aware successor to ghq / rhq with a TUI dashboard.",
version,
propagate_version = true
)]
pub struct Cli {
/// Active profile (overrides $SHOKA_PROFILE and config default_profile).
#[arg(long, global = true, env = "SHOKA_PROFILE")]
pub profile: Option<String>,
/// Path to config.toml (defaults to XDG config dir).
#[arg(long, global = true, env = "SHOKA_CONFIG", value_name = "PATH")]
pub config: Option<PathBuf>,
/// Subcommand. Omitted ⇒ launches the TUI dashboard.
#[command(subcommand)]
pub cmd: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
/// Clone a repository (no arg ⇒ fuzzy-select from your gh repos).
Clone(CloneArgs),
/// List repos on the shelf.
List(ListArgs),
/// cd into a repo (no arg ⇒ fuzzy-select).
///
/// Emits the chosen path to stdout. A shell wrapper installed via
/// `shoka init-shell <shell>` consumes that to perform the cd.
Cd(CdArgs),
/// Run a command across the shelf in parallel.
Exec(ExecArgs),
/// Propose stale / merged repo candidates for cleanup.
Prune(PruneArgs),
/// Adopt an existing ghq tree (or any directory hierarchy in
/// `<root>/<host>/<owner>/<name>` shape).
Import(ImportArgs),
/// Export the shelf state.toml (portable ledger).
Export(ExportArgs),
/// Tag management.
#[command(subcommand)]
Tag(TagCommand),
/// Set per-repo metadata (vcs override etc.).
Set(SetArgs),
/// Set or read a per-repo note.
Note(NoteArgs),
/// Diagnose environment (gh, git, jj presence; config validity).
/// The first command run on a fresh machine also auto-creates a
/// starter `config.toml` at the resolved config path.
Doctor,
/// Launch the TUI dashboard explicitly.
Tui(TuiArgs),
/// Print shell completion script.
Completion(CompletionArgs),
/// Print the shell integration wrapper for `cd` (PowerShell / bash / zsh / fish).
InitShell(InitShellArgs),
/// Per-repo volatile cache management (refresh / show / clear).
#[command(subcommand)]
Cache(CacheCommand),
/// Update the shoka binary itself to the latest GitHub release.
SelfUpdate(SelfUpdateArgs),
}
#[derive(Debug, Args)]
pub struct SelfUpdateArgs {
/// Skip the confirmation prompt and install immediately.
#[arg(long, short = 'y')]
pub yes: bool,
/// Print availability and exit without installing — useful in
/// scripts that want to surface "an upgrade is available" without
/// performing it.
#[arg(long)]
pub check: bool,
}
#[derive(Debug, Subcommand)]
pub enum CacheCommand {
/// Refresh cache entries for the shelf. Skips repos whose
/// `last_refreshed` is within `[global.cache].refresh_threshold_secs`
/// of now, unless `--force` is passed.
Refresh {
/// Bypass the staleness threshold; refresh every entry.
#[arg(long)]
force: bool,
/// Restrict to repos carrying every listed tag (AND).
#[arg(long = "tag", value_name = "TAG")]
tags: Vec<String>,
/// Internal flag — set by the dispatcher when spawning the
/// detached background refresh. Suppresses the pretty
/// summary output and downgrades errors to log messages so
/// nothing leaks back to the parent process.
#[arg(long, hide = true)]
background: bool,
},
/// Dump the raw cache.toml to stdout.
Show,
/// Drop every cache entry. Equivalent to `rm cache.toml`, but
/// goes through the atomic-save path so we don't leave half-
/// written state behind.
Clear,
}
#[derive(Debug, Args)]
pub struct CloneArgs {
/// Repository URL or `owner/name`. Omitted ⇒ fuzzy-select.
pub url: Option<String>,
}
#[derive(Debug, Args)]
pub struct ListArgs {
/// Filter by tag (repeatable; AND semantics).
#[arg(long = "tag", value_name = "TAG")]
pub tags: Vec<String>,
/// Show only repos that carry an AGENTS.md.
#[arg(long)]
pub has_agents: bool,
}
#[derive(Debug, Args)]
pub struct CdArgs {
/// Repository hint (URL or owner/name fragment). Omitted ⇒ fuzzy-select.
pub repo: Option<String>,
/// Restrict fuzzy candidates by tag.
#[arg(long = "tag", value_name = "TAG")]
pub tags: Vec<String>,
}
#[derive(Debug, Args)]
pub struct ExecArgs {
/// Restrict by tag.
#[arg(long = "tag", value_name = "TAG")]
pub tags: Vec<String>,
/// Status filter (e.g. `dirty`, `behind`, `ahead`).
#[arg(long)]
pub filter: Option<String>,
/// Command + args (everything after `--`).
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub cmd: Vec<String>,
}
#[derive(Debug, Args)]
pub struct PruneArgs {
/// Don't actually remove anything; just print candidates.
#[arg(long)]
pub dry_run: bool,
/// Skip the interactive confirmation. Implies you have already
/// reviewed the candidate list (`--dry-run` first is a good
/// habit). No-op when combined with `--dry-run`.
#[arg(long, short)]
pub yes: bool,
}
#[derive(Debug, Args)]
pub struct ImportArgs {
/// Source directory (e.g. `~/ghq`). Omitted ⇒ pick from common candidates.
pub path: Option<PathBuf>,
}
#[derive(Debug, Args)]
pub struct ExportArgs {
/// Output path. `-` writes to stdout (default).
#[arg(long, value_name = "PATH")]
pub path: Option<PathBuf>,
}
#[derive(Debug, Subcommand)]
pub enum TagCommand {
/// Add tags to a repo (no arg ⇒ fuzzy-select repo + interactive tag prompt).
Add {
/// Repository identifier (owner/name). Omitted ⇒ fuzzy-select.
repo: Option<String>,
/// Tags to add (one or more). Omitted ⇒ interactive prompt.
tags: Vec<String>,
},
/// Remove tags from a repo (no arg ⇒ fuzzy-select repo + interactive tag prompt).
Rm {
/// Repository identifier (owner/name). Omitted ⇒ fuzzy-select.
repo: Option<String>,
/// Tags to remove (one or more). Omitted ⇒ interactive prompt.
tags: Vec<String>,
},
/// List tags carried by a repo (no arg ⇒ fuzzy-select repo).
Ls { repo: Option<String> },
/// List repos that carry a tag (no arg ⇒ fuzzy-select tag).
Who { tag: Option<String> },
}
#[derive(Debug, Args)]
pub struct SetArgs {
/// Repository identifier (no arg ⇒ fuzzy-select).
pub repo: Option<String>,
/// Force VCS for this repo (overrides auto-detection).
#[arg(long, value_name = "VCS")]
pub vcs: Option<VcsKind>,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum VcsKind {
Auto,
Git,
Jj,
}
#[derive(Debug, Args)]
pub struct NoteArgs {
/// Repository identifier (no arg ⇒ fuzzy-select).
pub repo: Option<String>,
/// Note text. Omitted with `--clear` clears the note; omitted otherwise prints current.
pub text: Option<String>,
/// Clear the note instead of setting it.
#[arg(long, conflicts_with = "text")]
pub clear: bool,
}
#[derive(Debug, Args)]
pub struct TuiArgs {
/// Pre-apply tag filter on launch.
#[arg(long = "tag", value_name = "TAG")]
pub tags: Vec<String>,
}
#[derive(Debug, Args)]
pub struct CompletionArgs {
/// Target shell.
#[arg(value_enum)]
pub shell: clap_complete::Shell,
}
#[derive(Debug, Args)]
pub struct InitShellArgs {
/// Shell to emit a `cd` wrapper for.
#[arg(value_enum)]
pub shell: SupportedShell,
/// Wrapper function name (default: `shoka`). The function
/// intercepts `cd` / `tui` to chdir the parent shell and
/// transparently passes every other subcommand through to the
/// `shoka` binary, so the same name covers both worlds. Override
/// to `s` (or anything shorter) if you want a separate alias.
#[arg(long, default_value = "shoka")]
pub name: String,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum SupportedShell {
Powershell,
Bash,
Zsh,
Fish,
}