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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// SPDX-License-Identifier: MIT OR Apache-2.0
// Global flags shared across all subcommands.
// Included from cli.rs.
#[derive(clap::Args, Debug)]
/// Global flags shared across all subcommands.
pub struct GlobalArgs {
/// Verbosity level (repeat for more: -v, -vv, -vvv).
#[arg(short, long, action = clap::ArgAction::Count, global = true, hide_short_help = true, help = "Increase verbosity (-v info, -vv debug, -vvv trace)")]
pub verbose: u8,
/// Quiet level (repeat for less: -q, -qq).
#[arg(short, long, action = clap::ArgAction::Count, global = true, hide_short_help = true, help = "Decrease verbosity (-q error, -qq off)")]
pub quiet: u8,
/// Workspace root for path jail validation.
#[arg(
long,
global = true, hide_short_help = true,
value_hint = ValueHint::DirPath,
help = "Workspace root for path jail validation"
)]
pub workspace: Option<PathBuf>,
/// Explicit config file path (overrides workspace `.atomwrite.toml` and XDG config).
#[arg(
long,
global = true, hide_short_help = true,
value_hint = ValueHint::FilePath,
help = "Path to .atomwrite.toml (explicit override; fails if missing/malformed)"
)]
pub config: Option<PathBuf>,
/// Color output mode.
#[arg(long, global = true, hide_short_help = true, value_enum, default_value_t = ColorChoice::Auto, help = "Control colored output")]
pub color: ColorChoice,
/// Disable colored output (equivalent to --color never).
#[arg(long, global = true, hide_short_help = true, help = "Disable colored output", action = clap::ArgAction::SetTrue)]
pub no_color: bool,
/// Write the process ID to this path once the signal handlers are installed.
///
/// Meant for test harnesses that must know when the process is ready to
/// receive signals. One-shot agents normally omit it.
#[arg(
long = "ready-file",
global = true, hide_short_help = true,
value_hint = ValueHint::FilePath,
help = "Write the PID after signal handlers are ready (test harness use)"
)]
pub ready_file: Option<PathBuf>,
/// Disable .gitignore filtering.
#[arg(long, global = true, hide_short_help = true, help = "Do not respect .gitignore files", action = clap::ArgAction::SetTrue)]
pub no_gitignore: bool,
/// Include hidden files and directories.
#[arg(long, global = true, hide_short_help = true, help = "Include hidden files and directories", action = clap::ArgAction::SetTrue)]
pub hidden: bool,
/// Follow symbolic links during traversal.
#[arg(long, global = true, hide_short_help = true, help = "Follow symbolic links", action = clap::ArgAction::SetTrue)]
pub follow_symlinks: bool,
/// Maximum number of parallel workers for multi-file commands.
///
/// The bound covers the directory walk and the shared worker pool used by
/// hashing, backup, recursive delete and copy, semantic-search scoring and
/// non-transactional batch. Omitted or `0` means automatic: all logical
/// CPUs up to a built-in ceiling of 16 workers, capped by available RAM.
/// `N` means exactly N workers, also above that ceiling.
///
/// Alias: `--max-concurrency`. The bound comes from this flag only, never
/// from a process environment variable.
#[arg(
short = 'j',
long = "threads",
visible_alias = "max-concurrency",
global = true, hide_short_help = true,
value_name = "N",
help = "Maximum parallel workers (0 or omitted = automatic: all cores up to 16, capped by RAM; N = exactly N)"
)]
pub threads: Option<usize>,
/// Maximum allowed file size in bytes.
#[arg(
long,
global = true, hide_short_help = true,
help = "Maximum file size in bytes (default: 1 GiB, larger files are rejected)"
)]
pub max_filesize: Option<u64>,
/// Global operation timeout in seconds.
///
/// When non-zero, a watchdog cancels the operation after N seconds and the
/// command exits with code 124. Pass `0` to disable the timeout, which is
/// not recommended for agents. Alias: `--timeout`.
#[arg(
long = "timeout-secs",
visible_alias = "timeout",
global = true, hide_short_help = true,
default_value_t = crate::constants::DEFAULT_TIMEOUT_SECS,
help = "Global operation timeout in seconds (0 disables, exit 124 on deadline)"
)]
pub timeout_secs: u64,
/// Suppress NDJSON `progress` heartbeats (batch/replace).
#[arg(
long,
global = true, hide_short_help = true,
help = "Disable NDJSON progress events on long operations",
action = clap::ArgAction::SetTrue
)]
pub no_progress: bool,
/// Emit JSON Schema for subcommand output and exit.
#[arg(
long,
global = true, hide_short_help = true,
help = "Emit JSON Schema for the subcommand output and exit",
action = clap::ArgAction::SetTrue
)]
pub json_schema: bool,
/// Discover the NDJSON schema surface, or emit one schema.
///
/// Without a value it lists every schema name as one NDJSON line per name.
/// With `NAME` it emits that schema on the data channel: `read`, `edit`,
/// `stream_diff`, `error`, and so on. An unknown name fails closed with a
/// typed error instead of emitting an empty document.
#[arg(
long = "print-schema",
global = true, hide_short_help = true,
value_name = "NAME",
num_args = 0..=1,
default_missing_value = "",
help = "List schema names, or emit the schema for NAME, on stdout"
)]
pub print_schema: Option<String>,
/// Declare agent-hosted (non-interactive) execution.
///
/// The process never reads a confirmation from stdin and never tries to
/// open a terminal prompt; a command that would need one fails with a
/// typed error instead of blocking. Not required for ordinary one-shot
/// runs — `/dev/null` stdin already behaves this way.
#[arg(
long = "non-interactive",
visible_alias = "no-input",
global = true, hide_short_help = true,
help = "Declare non-interactive execution: never prompt, never read confirmation from stdin",
action = clap::ArgAction::SetTrue
)]
pub non_interactive: bool,
/// Acknowledge the side effect of the command before it runs.
///
/// Only the commands whose contract requires an acknowledgement (see
/// `commands --include-globals`) accept it. Any other command rejects the
/// flag with a typed error rather than treating it as a no-op.
#[arg(
long = "confirm-side-effect",
global = true, hide_short_help = true,
help = "Acknowledge the command side effect (required by contract-gated commands)",
action = clap::ArgAction::SetTrue
)]
pub confirm_side_effect: bool,
/// Opt in to the value window used by `--max-output-bytes`.
///
/// Truncation is opt-in: the flag is rejected when
/// `--max-output-bytes` is absent, so a truncated payload is never the
/// default.
#[arg(
long = "allow-truncate",
global = true, hide_short_help = true,
requires = "max_output_bytes",
help = "Opt in to value truncation; required by --max-output-bytes",
action = clap::ArgAction::SetTrue
)]
pub allow_truncate: bool,
/// Accepted for compatibility but ignored — output is always NDJSON.
#[arg(long, global = true, hide_short_help = true, hide = true, action = clap::ArgAction::SetTrue)]
pub json: bool,
/// Override the UI locale for translated suggestions and human stderr (`en`, `pt-BR`).
///
/// Precedence: `--locale`, then the saved preference (`atomwrite locale
/// --set`), then the operating system locale, then `en`. Error codes and
/// messages always stay in English, only the suggestions follow the locale.
#[arg(
long = "locale",
global = true, hide_short_help = true,
value_parser = crate::locale::parse_cli_locale,
help = "Override locale (en, pt-BR)"
)]
pub lang: Option<String>,
/// Skip the on-startup `wal-heal` pass.
///
/// By default every command that modifies files first removes stale
/// completed write-ahead journals older than 3600 seconds from the
/// directories named on the command line, within a 100 ms wall-clock
/// budget. Set this flag in tight loops or in benchmarks that measure the
/// cost of one command in isolation.
#[arg(
long,
global = true, hide_short_help = true,
help = "Skip the startup wal-heal pass (default: remove journals older than 3600 s within a 100 ms budget)",
action = clap::ArgAction::SetTrue
)]
pub no_auto_heal: bool,
/// Project NDJSON events to top-level keys only (comma-separated).
///
/// Applied by the stdout writer for every subcommand. Missing keys are
/// omitted (no null filler). Alias: `--fields`.
#[arg(
long = "select",
visible_alias = "fields",
global = true, hide_short_help = true,
value_name = "KEYS",
help = "Project NDJSON to top-level keys (CSV); alias --fields"
)]
pub select: Option<String>,
/// Keep only NDJSON events matching top-level key equality (repeatable AND).
///
/// Expressions: `key=value`, `key==value`, or `key!=value`. Values may be
/// `true`/`false`/`null`/number/string. Applies to all events including errors.
#[arg(
long = "filter",
global = true, hide_short_help = true,
value_name = "EXPR",
action = clap::ArgAction::Append,
help = "Filter NDJSON events by top-level key=value (repeatable AND)"
)]
pub filter: Vec<String>,
/// Emit at most N NDJSON events after filtering.
///
/// Long-only: the short `-n` belongs to `replace --max-replacements`.
#[arg(
long = "limit",
global = true, hide_short_help = true,
value_name = "N",
help = "Max NDJSON events after --filter (long-only, -n belongs to replace)"
)]
pub limit: Option<u64>,
/// Order buffered NDJSON events before they reach the data channel.
///
/// `KEY` sorts by that top-level key, `path-line` by `path` then `line`,
/// `line-path` by `line` then `path`. Missing keys sort last, and every
/// comparison falls back to arrival order, so the ordering is total.
/// Implies buffering: events are emitted on flush, not as they arrive.
///
/// Not to be confused with the local `search --sort`.
// The flag is `--sort-by`, not `--sort`: `search` already owns a local
// `--sort`, and clap derives the argument id from the field name, so
// sharing `sort` with `SearchArgs` would collide.
#[arg(
long = "sort-by",
global = true, hide_short_help = true,
value_name = "KEY",
help = "Sort NDJSON events by key, path-line, or line-path (buffers until flush)"
)]
pub sort_by: Option<String>,
/// Keep the first event of each duplicate group and drop the rest.
///
/// `KEYS` is a CSV of top-level keys (or repeat the flag). Distinct from
/// `--sort-by`: dedupe keeps arrival order of the surviving events unless
/// a sort flag is also set.
#[arg(
long = "dedupe",
global = true, hide_short_help = true,
value_name = "KEYS",
action = clap::ArgAction::Append,
help = "Drop events whose CSV key tuple was already seen (repeatable)"
)]
pub dedupe: Vec<String>,
/// Collapse buffered events into one summary line per group.
///
/// Expression: `KEY[:OP[:FIELD]]` with `OP` one of `count` (default),
/// `sum`, `min`, `max`. `FIELD` is the numeric key the reduction reads.
/// Use with `--sort-by` to also order the surviving groups.
#[arg(
long = "aggregate",
global = true, hide_short_help = true,
value_name = "KEY:OP[:FIELD]",
action = clap::ArgAction::Append,
conflicts_with = "diff_against",
help = "Group buffered events by key and reduce (repeatable): count, sum, min, max"
)]
pub aggregate: Vec<String>,
/// Attach a table read from stdin to every event, by matching key.
///
/// Expression: `KEY:SUFFIX`. The whole NDJSON stdin is read as the table
/// (records grouped by `KEY`) and each event gains a top-level `SUFFIX`
/// array with its matching records. Events with no match pass through
/// unchanged. Contract: stdin carries the table, stdout carries results.
#[arg(
long = "join",
global = true, hide_short_help = true,
value_name = "KEY:SUFFIX",
action = clap::ArgAction::Append,
help = "Join the NDJSON table on stdin into each event (repeatable): key:suffix"
)]
pub join: Vec<String>,
/// Truncate string values of the given key to N characters.
///
/// Expression: `KEY:LEN`, counted in characters so multibyte text is cut
/// on a boundary. Non-string keys and shorter values pass through.
#[arg(
long = "truncate-content",
global = true, hide_short_help = true,
value_name = "KEY:LEN",
action = clap::ArgAction::Append,
help = "Truncate a string key to N characters (repeatable): key:len"
)]
pub truncate_content: Vec<String>,
/// Replace secret-shaped values with `[redacted]`.
///
/// Two rules: a top-level key whose name marks a credential always has
/// its string value replaced, and any string carrying a known credential
/// prefix is replaced whole. Keeps the agent from echoing tokens onward.
#[arg(
long = "redact",
global = true, hide_short_help = true,
help = "Replace secret-shaped values with [redacted] before emitting",
action = clap::ArgAction::SetTrue
)]
pub redact: bool,
/// Normalize every string value to Unicode NFC.
///
/// Use when two sources disagree on the byte form of the same text
/// (composed vs decomposed accents) and the agent needs one form.
#[arg(
long = "normalize-encoding",
global = true, hide_short_help = true,
help = "Normalize all string values to Unicode NFC",
action = clap::ArgAction::SetTrue
)]
pub normalize_encoding: bool,
/// Compare the emitted stream against a baseline NDJSON file.
///
/// On flush, one `stream_diff` line reports `added`, `removed` and
/// `unchanged` counts by event identity (duplicates counted as a
/// multiset) instead of the live events. The path must resolve inside the
/// workspace jail.
#[arg(
long = "diff-against",
global = true, hide_short_help = true,
value_name = "PATH",
value_hint = ValueHint::FilePath,
conflicts_with = "truncate_content",
help = "Report added/removed/unchanged against a baseline NDJSON file"
)]
pub diff_against: Option<PathBuf>,
/// Cap the byte length of every string value in the emitted events.
///
/// Cuts on a UTF-8 boundary, never mid-character. It is a value window,
/// not a line window: numbers and structure stay intact, so an agent still
/// reads the counts. Opt-in by contract — see `--allow-truncate`.
#[arg(
long = "max-output-bytes",
global = true, hide_short_help = true,
value_name = "BYTES",
requires = "allow_truncate",
help = "Cap the byte length of every string value (needs --allow-truncate)"
)]
pub max_output_bytes: Option<usize>,
}