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
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
/// Arguments for shell completion script generation.
#[derive(Args, Debug)]
pub struct CompletionsArgs {
/// Target shell for completion scripts.
#[arg(value_enum)]
pub shell: ShellType,
/// Install completion script to XDG data directory.
#[arg(
long,
help = "Install completion script to XDG data directory (Bash: ~/.local/share/bash-completion/completions/atomwrite)",
action = clap::ArgAction::SetTrue
)]
pub install: bool,
}
/// Arguments for the verify subcommand (alias for hash --verify).
#[derive(Args, Debug)]
pub struct VerifyArgs {
/// Path to the file to verify.
#[arg(value_hint = ValueHint::FilePath)]
pub path: PathBuf,
/// Expected BLAKE3 checksum.
pub checksum: String,
}
/// Arguments for the delete subcommand.
#[derive(Args, Debug)]
pub struct DeleteArgs {
/// File paths to delete.
#[arg(required = true, value_hint = ValueHint::AnyPath)]
pub paths: Vec<PathBuf>,
/// Shared backup flags.
#[command(flatten)]
pub backup_opts: BackupOpts,
/// Recurse into directories and own the whole named tree.
///
/// Without a narrowing filter the verb removes hidden, ignored, linked,
/// fifo, socket and device entries, and a link is unlinked without being
/// followed. `--hidden`, `--no-gitignore` and `--follow-symlinks` are
/// accepted as no-ops that emit one diagnostic line, and `--backup` is
/// refused with a directory root because the copy would outlive the tree.
#[arg(
short,
long,
help = "Recurse into directories and remove the whole named tree",
action = clap::ArgAction::SetTrue
)]
pub recursive: bool,
/// Glob patterns for file inclusion.
#[arg(short = 'g', long, action = clap::ArgAction::Append, help = "Include files matching glob")]
pub include: Vec<String>,
/// Glob patterns for file exclusion.
#[arg(long, action = clap::ArgAction::Append, help = "Exclude files matching glob")]
pub exclude: Vec<String>,
/// Only delete files older than this duration (e.g. 30d, 24h, 1w, 60s).
#[arg(
long,
help = "Only delete files older than duration (e.g. 30d, 24h, 1w)"
)]
pub older_than: Option<String>,
/// Plan-only: list files that would be deleted without mutating.
///
/// A one-shot CLI cannot prompt interactively; run again without `--plan`
/// or `--dry-run` to actually delete. `--confirm` is not an alias of this
/// flag: it is rejected (fail-closed).
#[arg(
long,
conflicts_with = "dry_run",
help = "Plan only: list targets without deleting (no interactive prompt; one-shot)",
action = clap::ArgAction::SetTrue
)]
pub plan: bool,
/// Preview without deleting.
#[arg(long, help = "Show what would be done without deleting", action = clap::ArgAction::SetTrue)]
pub dry_run: bool,
/// Rejected legacy flag: `delete` never asks for confirmation.
///
/// It was once an alias of `--plan`, which agents misread as "confirm and
/// delete". Use `--plan` to list targets or omit the flag to delete.
#[arg(
long,
help = "Rejected: use --plan to list targets, or omit flags to delete (one-shot; not write --confirm)",
action = clap::ArgAction::SetTrue
)]
pub confirm: bool,
/// Rejected legacy flag: there is no interactive confirmation to skip.
///
/// Use `--plan` to list targets or omit the flag to delete.
#[arg(
short = 'y',
long,
help = "Rejected: no interactive confirm in one-shot; use --plan to list, omit flags to delete",
action = clap::ArgAction::SetTrue
)]
pub yes: bool,
}
/// Arguments for the read subcommand.
#[derive(Args, Debug, Clone)]
pub struct ReadArgs {
/// One or more files to read.
///
/// Explicit paths are read in argv order. An operand that carries `*`, `?`
/// or `[` is a pattern expanded by the binary itself against ONE level of
/// its directory, in byte order (the `LC_ALL=C` shell order), skipping
/// hidden names. An absolute operand is judged by the part that sits under
/// `--workspace`, so `'/w/*.rs'` and `/w/a.rs` face the same one-level rule
/// and the same jail. `--format raw` concatenates every target with no
/// header, no separator and no added newline, the way `cat` does.
#[arg(value_hint = ValueHint::FilePath, num_args = 1.., required = true)]
pub paths: Vec<PathBuf>,
/// Line range to read (1-based, e.g. "1:50").
///
/// Repeat the flag for several ranges (`--lines 1:3 --lines 10:12`), or
/// separate them with commas (`--lines 1:3,10:12`); they are emitted in
/// ascending order, with no line repeated.
#[arg(
long,
action = clap::ArgAction::Append,
visible_alias = "range",
help = "Line range to read (1-based, e.g. 1:50; repeatable)"
)]
pub lines: Vec<String>,
/// Read the whole file, ignoring the byte ceiling.
#[arg(
long,
help = "Read the whole file, ignoring the byte ceiling",
action = clap::ArgAction::SetTrue
)]
pub all: bool,
/// Prefix every raw line with its 1-based number.
#[arg(
long,
help = "Prefix every raw line with its 1-based number",
action = clap::ArgAction::SetTrue
)]
pub number: bool,
/// Drop comment and blank lines before selecting.
///
/// The selector still counts the original line numbers, so `--lines` keeps
/// meaning the same lines it means without this flag.
#[arg(
long,
help = "Drop comment and blank lines before selecting",
action = clap::ArgAction::SetTrue
)]
pub no_comments: bool,
/// Read only the named symbol, resolved through tree-sitter.
///
/// The name is matched against the structural nodes of the file, so the
/// answer is the whole definition and never a text guess.
#[arg(
long,
value_name = "NAME",
help = "Read only the named symbol, resolved through tree-sitter"
)]
pub symbol: Option<String>,
/// Expand `--line` to the structural block that contains it.
#[arg(
long,
help = "Expand --line to the enclosing structural block",
action = clap::ArgAction::SetTrue
)]
pub enclosing: bool,
/// Single line number to read.
#[arg(long, help = "Single line number with optional context")]
pub line: Option<usize>,
/// Lines of context around --line.
#[arg(
short = 'C',
long,
default_value_t = 0,
help = "Lines of context around --line"
)]
pub context: usize,
/// Read first N lines.
#[arg(long, help = "Read first N lines")]
pub head: Option<usize>,
/// Read last N lines.
#[arg(long, help = "Read last N lines")]
pub tail: Option<usize>,
/// Return only metadata without content.
#[arg(long, help = "Return only metadata (no content)", action = clap::ArgAction::SetTrue)]
pub stat: bool,
/// Output format selection.
///
/// `raw` always writes the file bytes to stdout, on a terminal and in a
/// pipe alike. The default `ndjson` is the agent contract.
#[arg(
long,
value_enum,
default_value_t = OutputFormat::Ndjson,
help = "Output format (raw: always raw file bytes; default NDJSON for agents)"
)]
pub format: OutputFormat,
/// Redundant with `--format raw`, which always emits bytes.
///
/// Kept for compatibility with pipelines that already pass `--bytes`.
#[arg(
long,
help = "With --format raw: emit raw bytes (implied by --format raw; kept for compatibility)",
action = clap::ArgAction::SetTrue
)]
pub bytes: bool,
/// Expected BLAKE3 hash for verification.
#[arg(long, help = "Verify file checksum against expected BLAKE3 hash")]
pub verify_checksum: Option<String>,
/// Filter lines matching this regex (substring of file content).
#[arg(
long,
allow_hyphen_values = true,
help = "Filter returned lines to those matching this regex"
)]
pub grep: Option<String>,
/// Crop window `X,Y,W,H` in source pixels, applied before the resize.
///
/// A picture is delivered as a `data:` URI on stdout; cropping first means
/// the model pays for the region it asked about and not for the page
/// around it.
#[arg(long, value_name = "X,Y,W,H", help = "Crop a picture to X,Y,W,H before resizing")]
pub crop: Option<String>,
/// Hard ceiling on visual tokens, overrides the tier ceiling.
///
/// The picture is resized until the 28-pixel patch rule fits the number,
/// so the cost is chosen by the caller instead of discovered afterwards.
#[arg(long, value_name = "N", help = "Resize a picture to at most N visual tokens")]
pub max_tokens: Option<u32>,
/// Deliver a picture again after it was already delivered in this session.
///
/// The first read of a picture records its BLAKE3 under the agent session,
/// and a repeat answers with the facts instead of paying for the same
/// `data:` URI twice. This flag asks for the bytes again.
#[arg(
long,
help = "Deliver a picture again after it was already delivered in this session",
action = clap::ArgAction::SetTrue
)]
pub force: bool,
/// Pages of a PDF to read, as `N` or `A:B`.
///
/// Repeatable, so several windows arrive in one call. A page that carries
/// no text is reported with `needs_vision`, because a scanned page is not
/// an empty page.
#[arg(
long,
value_name = "PAGES",
action = clap::ArgAction::Append,
help = "PDF pages to read, as N or A:B (repeatable)"
)]
pub pages: Vec<String>,
/// Render one PDF page and deliver it as a picture.
///
/// A scanned page has no text to extract, so `--pages` reports it as
/// `needs_vision`. This flag renders the page instead, and the result
/// arrives through the same `data:` URI a picture uses.
#[arg(long, value_name = "N", help = "Render PDF page N and deliver it as a picture")]
pub page: Option<u32>,
/// Resolution a rendered PDF page comes out at, in dots per inch.
///
/// The default is the smallest one that keeps small print legible. Drop it
/// when the page would exceed the pixel ceiling, and raise it when the
/// text is too small to read.
#[arg(
long,
value_name = "DPI",
help = "Resolution of a rendered PDF page, in dots per inch"
)]
pub dpi: Option<f32>,
/// Read the page as text by recognizing the characters on it.
///
/// A scan has no text layer, so every text verb answers "nothing" and the
/// caller believes the page is empty. This runs the recognition models in
/// process and returns what the page actually says. The model files are
/// fetched once into the cache directory, so the first run needs a network.
#[arg(
long,
help = "Read a picture or a rendered page as text, by recognizing its characters"
)]
pub ocr: bool,
/// Use the high-resolution image tier.
#[arg(
long,
help = "Use the high-resolution image tier (2576 px, 4784 visual tokens)",
action = clap::ArgAction::SetTrue
)]
pub high_res: bool,
}
/// Fuzzy matching behavior for --old/--new edit mode.
///
/// `Off` is exact-only (no cascade). Exact miss
/// yields [`crate::error::AtomwriteError::MatchFailed`] (exit 65, code `MATCH_FAILED`).
/// Prefer `Auto` (default) or `Aggressive` for agent workflows.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
pub enum FuzzyMode {
/// Try exact match first, then fuzzy strategies automatically (default).
Auto,
/// Exact match only — no fuzzy cascade.
Off,
/// Try all fuzzy strategies including low-confidence block anchor.
Aggressive,
}
/// Arguments for the `edit-loop` subcommand.
#[derive(Args, Debug)]
pub struct EditLoopArgs {
/// Refuse the cross-device copy-then-rename fallback (exit 91).
#[command(flatten)]
pub strict: crate::cli_args::StrictAtomicArgs,
#[arg(value_hint = ValueHint::FilePath)]
/// File path to apply all pairs against.
pub path: PathBuf,
/// Accept `STATE_DRIFT` between iterations (default: reject). Useful
/// when chaining edits; for `edit-loop` this is informational since
/// the whole batch is computed in memory and a single atomic write
/// is performed at the end (no per-pair drift to validate).
#[arg(
long,
help = "Accept STATE_DRIFT (informational for edit-loop; default: reject)",
action = clap::ArgAction::SetTrue
)]
pub allow_sequential_drift: bool,
/// Write the pairs that match instead of aborting the whole batch.
/// Default (off) is all-or-nothing, like `edit`: any unmatched pair
/// aborts with exit 65 and nothing is written. With `--partial` the
/// matching pairs are written and the command exits 87
/// (`EDIT_LOOP_PARTIAL`) when at least one pair missed.
#[arg(
long,
help = "Write the matching pairs and exit 87 when some miss (default: all-or-nothing, exit 65)",
action = clap::ArgAction::SetTrue
)]
pub partial: bool,
/// Shared backup flags.
#[command(flatten)]
pub backup_opts: BackupOpts,
/// Validate syntax after writing. Pass a language name
/// (`rust`, `python`, `js`, etc.). When the file is invalid, the
/// write is aborted with `SyntaxError` (exit 88).
#[arg(
long,
value_name = "LANG",
help = "Validate syntax of the written file via tree-sitter (e.g. rust, python, js)"
)]
pub syntax_check: Option<String>,
/// Normalize line endings of the written file.
#[arg(
long,
value_enum,
default_value_t = crate::line_endings::LineEnding::Auto,
help = "Normalize line endings: lf, crlf, cr, auto (preserve original)"
)]
pub line_ending: crate::line_endings::LineEnding,
}
include!("part1b.inc.rs");