agent-dump 1.2.2__tar.gz → 1.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. {agent_dump-1.2.2 → agent_dump-1.2.4}/Cargo.lock +7 -7
  2. {agent_dump-1.2.2 → agent_dump-1.2.4}/Cargo.toml +1 -1
  3. {agent_dump-1.2.2 → agent_dump-1.2.4}/PKG-INFO +21 -3
  4. {agent_dump-1.2.2 → agent_dump-1.2.4}/README.md +20 -2
  5. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/compat/json/diagnostic.rs +22 -3
  6. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/compat/json.rs +12 -0
  7. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/config.rs +14 -0
  8. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/export.rs +9 -38
  9. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/claude/mod.rs +19 -20
  10. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/claude/transcript.rs +5 -1
  11. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/codex/enrichment.rs +4 -1
  12. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/codex/mod.rs +10 -4
  13. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/codex/transcript.rs +11 -3
  14. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/contract.rs +1 -0
  15. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/cursor/mod.rs +5 -0
  16. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/desktop/mod.rs +4 -0
  17. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/desktop/tests.rs +33 -0
  18. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/files.rs +64 -22
  19. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/jsonl.rs +60 -15
  20. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/kimi/mod.rs +10 -4
  21. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/kimi/transcript.rs +13 -1
  22. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/kimi/wire.rs +17 -14
  23. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/pi/mod.rs +10 -4
  24. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/registry.rs +21 -0
  25. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/sqlite/mod.rs +43 -0
  26. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/filter.rs +10 -6
  27. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/index/tests.rs +17 -0
  28. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/index.rs +33 -18
  29. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/scanner.rs +5 -4
  30. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/session/cache/tests.rs +5 -0
  31. {agent_dump-1.2.2 → agent_dump-1.2.4}/pyproject.toml +1 -1
  32. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/locales/en.json +1 -0
  33. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/locales/zh.json +1 -0
  34. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/insight-handoff.txt +1 -1
  35. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/pm-handoff.txt +1 -1
  36. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/llm.rs +111 -29
  37. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/collect.rs +9 -20
  38. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/config.rs +3 -0
  39. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/interactive.rs +23 -20
  40. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/list.rs +19 -27
  41. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/machine.rs +17 -29
  42. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/maintenance.rs +13 -8
  43. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/reader.rs +1 -0
  44. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/uri.rs +3 -4
  45. {agent_dump-1.2.2 → agent_dump-1.2.4}/LICENSE +0 -0
  46. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/Cargo.toml +0 -0
  47. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/compat/json/encoding.rs +0 -0
  48. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/compat/mod.rs +0 -0
  49. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/compat/value.rs +0 -0
  50. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/lib.rs +0 -0
  51. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/diagnostics.rs +0 -0
  52. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/formats.rs +0 -0
  53. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/i18n.rs +0 -0
  54. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/mod.rs +0 -0
  55. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/output/render.rs +0 -0
  56. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/parallel.rs +0 -0
  57. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/codex/patch.rs +0 -0
  58. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/cursor/transcript.rs +0 -0
  59. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/desktop/cherry.rs +0 -0
  60. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/desktop/deepchat.rs +0 -0
  61. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/desktop/minimax.rs +6 -6
  62. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/error.rs +0 -0
  63. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/mod.rs +0 -0
  64. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/pi/transcript.rs +0 -0
  65. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/source_tests.rs +0 -0
  66. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/sqlite/connection.rs +0 -0
  67. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/sqlite/legacy.rs +0 -0
  68. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/sqlite/v2.rs +0 -0
  69. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/providers/title.rs +0 -0
  70. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/context.rs +0 -0
  71. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/mod.rs +0 -0
  72. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/read.rs +0 -0
  73. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/text.rs +0 -0
  74. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/query/transcript.rs +0 -0
  75. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/session/assembly.rs +0 -0
  76. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/session/cache.rs +0 -0
  77. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/session/mod.rs +0 -0
  78. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/session/timestamp.rs +0 -0
  79. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/storage/mod.rs +0 -0
  80. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/storage/private_files.rs +0 -0
  81. {agent_dump-1.2.2 → agent_dump-1.2.4}/crates/agent-dump-core/src/storage/source_io.rs +0 -0
  82. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/.gitattributes +0 -0
  83. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/README.md +0 -0
  84. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/insight-chunk.txt +0 -0
  85. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/insight-merge.txt +0 -0
  86. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/insight-report.txt +0 -0
  87. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/insight-retry.txt +0 -0
  88. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/pm-chunk.txt +0 -0
  89. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/pm-merge.txt +0 -0
  90. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/pm-report.txt +0 -0
  91. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/pm-retry.txt +0 -0
  92. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/read-session-en.txt +0 -0
  93. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/read-session-zh.txt +0 -0
  94. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/system.txt +0 -0
  95. {agent_dump-1.2.2 → agent_dump-1.2.4}/resources/prompts/uri.txt +0 -0
  96. {agent_dump-1.2.2 → agent_dump-1.2.4}/rust-toolchain.toml +0 -0
  97. {agent_dump-1.2.2 → agent_dump-1.2.4}/rustfmt.toml +0 -0
  98. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/cli_args.rs +0 -0
  99. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/events.rs +0 -0
  100. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/handoff.rs +0 -0
  101. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/log.rs +0 -0
  102. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/mod.rs +0 -0
  103. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/model.rs +0 -0
  104. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/progress.rs +0 -0
  105. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/prompts.rs +0 -0
  106. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/reduction.rs +0 -0
  107. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/sessions.rs +0 -0
  108. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/collect/summary.rs +0 -0
  109. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/command.rs +0 -0
  110. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/date_input.rs +0 -0
  111. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/main.rs +0 -0
  112. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/shortcut.rs +0 -0
  113. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/terminal/mod.rs +0 -0
  114. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/terminal/reader.rs +0 -0
  115. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/terminal/selector.rs +0 -0
  116. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/terminal/tui.rs +0 -0
  117. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/mod.rs +0 -0
  118. {agent_dump-1.2.2 → agent_dump-1.2.4}/src/workflows/read.rs +0 -0
@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
10
10
 
11
11
  [[package]]
12
12
  name = "agent-dump"
13
- version = "1.2.2"
13
+ version = "1.2.4"
14
14
  dependencies = [
15
15
  "agent-dump-core",
16
16
  "caseless",
@@ -837,9 +837,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
837
837
 
838
838
  [[package]]
839
839
  name = "jiff"
840
- version = "0.2.37"
840
+ version = "0.2.38"
841
841
  source = "registry+https://github.com/rust-lang/crates.io-index"
842
- checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
842
+ checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383"
843
843
  dependencies = [
844
844
  "defmt",
845
845
  "jiff-core",
@@ -864,9 +864,9 @@ dependencies = [
864
864
 
865
865
  [[package]]
866
866
  name = "jiff-static"
867
- version = "0.2.37"
867
+ version = "0.2.38"
868
868
  source = "registry+https://github.com/rust-lang/crates.io-index"
869
- checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
869
+ checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c"
870
870
  dependencies = [
871
871
  "jiff-core",
872
872
  "proc-macro2",
@@ -1859,9 +1859,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
1859
1859
 
1860
1860
  [[package]]
1861
1861
  name = "uuid"
1862
- version = "1.26.1"
1862
+ version = "1.28.0"
1863
1863
  source = "registry+https://github.com/rust-lang/crates.io-index"
1864
- checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
1864
+ checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606"
1865
1865
  dependencies = [
1866
1866
  "getrandom 0.4.3",
1867
1867
  "js-sys",
@@ -46,7 +46,7 @@ nix = { version = "0.31", default-features = false, features = ["fs", "user"] }
46
46
 
47
47
  [package]
48
48
  name = "agent-dump"
49
- version = "1.2.2"
49
+ version = "1.2.4"
50
50
  edition = "2024"
51
51
  rust-version = "1.90"
52
52
  publish = false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-dump
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Classifier: Environment :: Console
5
5
  Classifier: License :: OSI Approved :: MIT License
6
6
  Classifier: Programming Language :: Rust
@@ -462,7 +462,7 @@ File discovery failures include partially unreadable providers. Valid sessions r
462
462
 
463
463
  When some session reads or summaries fail, collection continues with successful sessions. The saved Markdown includes a fixed incomplete-report notice with failure and included-session counts; an entirely failed run still fails.
464
464
 
465
- Collect discovers candidate sessions without a creation-time cutoff and processes user/assistant visible text only within the requested local dates. A session spanning several days is split into separate daily units before summarization; session counts remain unique URI counts. Text without a reliable timestamp (including undated Kimi context and inferred Cursor times) belongs to no date and is excluded without being reported as a coverage gap. No session creation/update time is substituted. Query filters select candidates; limits apply to unique sessions after text-date filtering. Events, including long individual messages, are split into chunks of at most 3,200 Unicode characters including labels. Chunk summaries are merged in groups of at most eight; large final reports are rendered in separate source-preserving groups. Model inputs are limited to 64,000 characters per request. Oversized metadata or derived summaries fail explicitly; source text is never silently truncated. Partial reports list omitted session URIs. Full input coverage does not mean a summary preserves every detail.
465
+ Collect discovers candidate sessions without a creation-time cutoff and processes user/assistant visible text only within the requested local dates. A session spanning several days is split into separate daily units before summarization; session counts remain unique URI counts. Text without a reliable timestamp (including undated Kimi context and inferred Cursor times) belongs to no date and is excluded without being reported as a coverage gap. No session creation/update time is substituted. Query filters select candidates; limits apply to unique sessions after text-date filtering. Events, including long individual messages, are split into chunks of at most 3,200 Unicode characters including labels. Chunk summaries are merged in groups of at most eight; large final reports are rendered in separate source-preserving groups. Model inputs are limited to 64,000 characters per request. Oversized metadata or derived summaries fail explicitly; source text is never silently truncated. A final report or `--summary` reply that stops at the model output token limit is treated as a failed request, not saved as complete. Partial reports list omitted session URIs. Full input coverage does not mean a summary preserves every detail.
466
466
 
467
467
  PM summaries merge sessions only within the same date and known working directory. Sessions without a working directory retain separate attribution.
468
468
 
@@ -479,6 +479,7 @@ provider = "openai" # openai | anthropic
479
479
  base_url = "https://api.openai.com/v1"
480
480
  model = "gpt-4.1-mini"
481
481
  api_key = "sk-..."
482
+ reasoning = "off" # off | default
482
483
 
483
484
  [collect]
484
485
  summary_concurrency = 4 # 1-32
@@ -501,6 +502,23 @@ deny = [
501
502
  ]
502
503
  ```
503
504
 
505
+ `[ai].reasoning` controls the model's thinking mode. `off` (the default) sends `enable_thinking: false` to OpenAI-compatible services and `thinking: {type: "disabled"}` to Anthropic. `default` sends neither and leaves the service default in place. Enabling reasoning or choosing an effort level uses vendor-specific parameters, so set them in `[ai.extra_body]`. The table is deep-merged into every request body: nested tables merge, other values replace.
506
+
507
+ ```toml
508
+ # OpenAI reasoning models
509
+ [ai.extra_body]
510
+ reasoning_effort = "low"
511
+ ```
512
+
513
+ ```toml
514
+ # Anthropic extended thinking; max_tokens must exceed budget_tokens
515
+ [ai.extra_body]
516
+ max_tokens = 8192
517
+ thinking = { type = "enabled", budget_tokens = 2048 }
518
+ ```
519
+
520
+ When an OpenAI-compatible service rejects the default `enable_thinking`, `temperature`, or `max_tokens`, agent-dump retries without it; `max_tokens` is renamed to `max_completion_tokens`. Parameters set in `extra_body` are never removed. `--config edit` does not prompt for `reasoning` or `extra_body` and keeps their values.
521
+
504
522
  `[agent.<name>].deny` only applies to `--collect`. When a session `cwd` matches one of the configured paths, or is inside that path, the session is ignored during collect.
505
523
 
506
524
  Collect, `--collect --dry-run`, and `--collect --emit-prompt` require valid TOML and exclusion arrays containing only nonempty path strings. Invalid configuration stops the command before session discovery or AI requests; compatibility parsing never disables exclusions for collect.
@@ -559,7 +577,7 @@ Pass `locations[].locator` from search unchanged to `--message`. Positions are o
559
577
 
560
578
  `--locate` requires `--search`. It returns messages containing any search term, respecting role filters, while all terms must still match the session. Title-only matches have empty locations; failed location reads have null locations and partial status. Search without --locate is unchanged.
561
579
 
562
- `--message` cannot combine with `--head` or `--summary`. Without `--format`, it prints context; `--json` writes a context envelope to stdout with kind=context and data containing uri, locator, total_messages, start/end and messages (position plus normalized message). Invalid or stale locators exit nonzero; JSON diagnostics go only to stderr.
580
+ `--message` cannot combine with `--head` or `--summary`. Without `--format`, it prints context; `--json` writes a context envelope to stdout with kind=context, status (partial when recoverable source diagnostics occur) and data containing uri, locator, total_messages, start/end and messages (position plus normalized message). Invalid or stale locators exit nonzero; JSON diagnostics go only to stderr.
563
581
 
564
582
  Use `--format json,markdown` to export the selected context. Files are named `<id>.messages-<start>-<end>.json` / `.md` under the Provider's output directory, separate from full-session exports. Both formats retain the canonical Session URI, the locator (revision plus target position), the absolute message range, and status. JSON retains normalized message records; Markdown renders their searchable text. Recoverable source diagnostics mark the file `partial` and are reported on stderr. Stale locators are rejected before file creation. Provider format restrictions still apply; raw/print file formats and `--json` with file-export options are rejected. `--output` requires an explicit `--format`; output-directory defaults follow full-session export rules.
565
583
 
@@ -438,7 +438,7 @@ File discovery failures include partially unreadable providers. Valid sessions r
438
438
 
439
439
  When some session reads or summaries fail, collection continues with successful sessions. The saved Markdown includes a fixed incomplete-report notice with failure and included-session counts; an entirely failed run still fails.
440
440
 
441
- Collect discovers candidate sessions without a creation-time cutoff and processes user/assistant visible text only within the requested local dates. A session spanning several days is split into separate daily units before summarization; session counts remain unique URI counts. Text without a reliable timestamp (including undated Kimi context and inferred Cursor times) belongs to no date and is excluded without being reported as a coverage gap. No session creation/update time is substituted. Query filters select candidates; limits apply to unique sessions after text-date filtering. Events, including long individual messages, are split into chunks of at most 3,200 Unicode characters including labels. Chunk summaries are merged in groups of at most eight; large final reports are rendered in separate source-preserving groups. Model inputs are limited to 64,000 characters per request. Oversized metadata or derived summaries fail explicitly; source text is never silently truncated. Partial reports list omitted session URIs. Full input coverage does not mean a summary preserves every detail.
441
+ Collect discovers candidate sessions without a creation-time cutoff and processes user/assistant visible text only within the requested local dates. A session spanning several days is split into separate daily units before summarization; session counts remain unique URI counts. Text without a reliable timestamp (including undated Kimi context and inferred Cursor times) belongs to no date and is excluded without being reported as a coverage gap. No session creation/update time is substituted. Query filters select candidates; limits apply to unique sessions after text-date filtering. Events, including long individual messages, are split into chunks of at most 3,200 Unicode characters including labels. Chunk summaries are merged in groups of at most eight; large final reports are rendered in separate source-preserving groups. Model inputs are limited to 64,000 characters per request. Oversized metadata or derived summaries fail explicitly; source text is never silently truncated. A final report or `--summary` reply that stops at the model output token limit is treated as a failed request, not saved as complete. Partial reports list omitted session URIs. Full input coverage does not mean a summary preserves every detail.
442
442
 
443
443
  PM summaries merge sessions only within the same date and known working directory. Sessions without a working directory retain separate attribution.
444
444
 
@@ -455,6 +455,7 @@ provider = "openai" # openai | anthropic
455
455
  base_url = "https://api.openai.com/v1"
456
456
  model = "gpt-4.1-mini"
457
457
  api_key = "sk-..."
458
+ reasoning = "off" # off | default
458
459
 
459
460
  [collect]
460
461
  summary_concurrency = 4 # 1-32
@@ -477,6 +478,23 @@ deny = [
477
478
  ]
478
479
  ```
479
480
 
481
+ `[ai].reasoning` controls the model's thinking mode. `off` (the default) sends `enable_thinking: false` to OpenAI-compatible services and `thinking: {type: "disabled"}` to Anthropic. `default` sends neither and leaves the service default in place. Enabling reasoning or choosing an effort level uses vendor-specific parameters, so set them in `[ai.extra_body]`. The table is deep-merged into every request body: nested tables merge, other values replace.
482
+
483
+ ```toml
484
+ # OpenAI reasoning models
485
+ [ai.extra_body]
486
+ reasoning_effort = "low"
487
+ ```
488
+
489
+ ```toml
490
+ # Anthropic extended thinking; max_tokens must exceed budget_tokens
491
+ [ai.extra_body]
492
+ max_tokens = 8192
493
+ thinking = { type = "enabled", budget_tokens = 2048 }
494
+ ```
495
+
496
+ When an OpenAI-compatible service rejects the default `enable_thinking`, `temperature`, or `max_tokens`, agent-dump retries without it; `max_tokens` is renamed to `max_completion_tokens`. Parameters set in `extra_body` are never removed. `--config edit` does not prompt for `reasoning` or `extra_body` and keeps their values.
497
+
480
498
  `[agent.<name>].deny` only applies to `--collect`. When a session `cwd` matches one of the configured paths, or is inside that path, the session is ignored during collect.
481
499
 
482
500
  Collect, `--collect --dry-run`, and `--collect --emit-prompt` require valid TOML and exclusion arrays containing only nonempty path strings. Invalid configuration stops the command before session discovery or AI requests; compatibility parsing never disables exclusions for collect.
@@ -535,7 +553,7 @@ Pass `locations[].locator` from search unchanged to `--message`. Positions are o
535
553
 
536
554
  `--locate` requires `--search`. It returns messages containing any search term, respecting role filters, while all terms must still match the session. Title-only matches have empty locations; failed location reads have null locations and partial status. Search without --locate is unchanged.
537
555
 
538
- `--message` cannot combine with `--head` or `--summary`. Without `--format`, it prints context; `--json` writes a context envelope to stdout with kind=context and data containing uri, locator, total_messages, start/end and messages (position plus normalized message). Invalid or stale locators exit nonzero; JSON diagnostics go only to stderr.
556
+ `--message` cannot combine with `--head` or `--summary`. Without `--format`, it prints context; `--json` writes a context envelope to stdout with kind=context, status (partial when recoverable source diagnostics occur) and data containing uri, locator, total_messages, start/end and messages (position plus normalized message). Invalid or stale locators exit nonzero; JSON diagnostics go only to stderr.
539
557
 
540
558
  Use `--format json,markdown` to export the selected context. Files are named `<id>.messages-<start>-<end>.json` / `.md` under the Provider's output directory, separate from full-session exports. Both formats retain the canonical Session URI, the locator (revision plus target position), the absolute message range, and status. JSON retains normalized message records; Markdown renders their searchable text. Recoverable source diagnostics mark the file `partial` and are reported on stderr. Stale locators are rejected before file creation. Provider format restrictions still apply; raw/print file formats and `--json` with file-export options are rejected. `--output` requires an explicit `--format`; output-directory defaults follow full-session export rules.
541
559
 
@@ -39,6 +39,7 @@ pub fn syntax(text: &str, original: &serde_json::Error) -> Error {
39
39
  let mut parser = Parser {
40
40
  chars: text.chars().collect(),
41
41
  index: 0,
42
+ depth: 0,
42
43
  };
43
44
  let failure = if text.starts_with('\u{feff}') {
44
45
  Some(("Unexpected UTF-8 BOM (decode using utf-8-sig)", 0))
@@ -49,7 +50,9 @@ pub fn syntax(text: &str, original: &serde_json::Error) -> Error {
49
50
  .then_some(("Extra data", parser.index))
50
51
  })
51
52
  };
52
- let message = if let Some((message, position)) = failure {
53
+ let message = if let Some((message, position)) =
54
+ failure.filter(|(message, _)| *message != TOO_DEEP)
55
+ {
53
56
  let before = &parser.chars[..position];
54
57
  let line = before.iter().filter(|c| **c == '\n').count() + 1;
55
58
  let column = position
@@ -70,9 +73,14 @@ pub fn syntax(text: &str, original: &serde_json::Error) -> Error {
70
73
 
71
74
  type Failure = (&'static str, usize);
72
75
 
76
+ // Matches serde_json's recursion limit so deep input cannot overflow the stack.
77
+ const MAX_DEPTH: usize = 128;
78
+ const TOO_DEEP: &str = "recursion limit exceeded";
79
+
73
80
  struct Parser {
74
81
  chars: Vec<char>,
75
82
  index: usize,
83
+ depth: usize,
76
84
  }
77
85
 
78
86
  impl Parser {
@@ -107,8 +115,19 @@ impl Parser {
107
115
  self.whitespace();
108
116
  match self.chars.get(self.index).copied() {
109
117
  Some('"') => self.string(),
110
- Some('{') => self.object(),
111
- Some('[') => self.array(),
118
+ Some('{' | '[') if self.depth == MAX_DEPTH => {
119
+ Err((TOO_DEEP, self.index))
120
+ }
121
+ Some(open @ ('{' | '[')) => {
122
+ self.depth += 1;
123
+ let result = if open == '{' {
124
+ self.object()
125
+ } else {
126
+ self.array()
127
+ };
128
+ self.depth -= 1;
129
+ result
130
+ }
112
131
  _ => {
113
132
  for word in
114
133
  ["null", "true", "false", "NaN", "Infinity", "-Infinity"]
@@ -156,3 +156,15 @@ impl serde_json::ser::Formatter for Formatter {
156
156
  writer.write_all(output.as_bytes())
157
157
  }
158
158
  }
159
+
160
+ #[cfg(test)]
161
+ mod tests {
162
+ use super::*;
163
+
164
+ #[test]
165
+ fn deep_nesting_reports_error_without_overflow() {
166
+ let error = from_str(&"[".repeat(100_000)).unwrap_err();
167
+
168
+ assert!(error.to_string().starts_with("recursion limit exceeded"));
169
+ }
170
+ }
@@ -10,6 +10,8 @@ pub struct AiConfig {
10
10
  pub base_url: String,
11
11
  pub model: String,
12
12
  pub api_key: String,
13
+ pub reasoning: String,
14
+ pub extra_body: Value,
13
15
  }
14
16
 
15
17
  pub struct CollectConfig {
@@ -183,6 +185,12 @@ impl Config {
183
185
  base_url: text(&self.data["ai"]["base_url"]),
184
186
  model: text(&self.data["ai"]["model"]),
185
187
  api_key: text(&self.data["ai"]["api_key"]),
188
+ reasoning: if self.data["ai"]["reasoning"].is_null() {
189
+ "off".into()
190
+ } else {
191
+ text(&self.data["ai"]["reasoning"])
192
+ },
193
+ extra_body: self.data["ai"]["extra_body"].clone(),
186
194
  })
187
195
  }
188
196
  pub fn output(&self) -> String {
@@ -436,6 +444,12 @@ pub fn validate_ai(
436
444
  if config.api_key.is_empty() {
437
445
  errors.push("api_key");
438
446
  }
447
+ if !matches!(config.reasoning.as_str(), "off" | "default") {
448
+ errors.push("reasoning");
449
+ }
450
+ if !config.extra_body.is_null() && !config.extra_body.is_object() {
451
+ errors.push("extra_body");
452
+ }
439
453
  if !config.base_url.is_empty() {
440
454
  let scheme = config
441
455
  .base_url
@@ -59,25 +59,14 @@ fn filename(id: &str) -> crate::Result<String> {
59
59
  }
60
60
  }
61
61
 
62
- fn absolute_existing_ancestor(path: &Path) -> crate::Result<PathBuf> {
63
- if path.exists() {
64
- return Ok(path.canonicalize()?);
65
- }
66
- let parent = path.parent().ok_or("Cannot resolve output path")?;
67
- Ok(absolute_existing_ancestor(parent)?
68
- .join(path.file_name().ok_or("Invalid output path")?))
69
- }
70
-
71
62
  fn write(
72
63
  output: &Path,
73
64
  session_id: &str,
74
65
  suffix: &str,
75
- source_root: &Path,
76
66
  contents: impl FnOnce(&mut fs::File) -> crate::Result<()>,
77
67
  ) -> crate::Result<PathBuf> {
78
68
  let directory = std::path::absolute(output)?;
79
- let resolved = absolute_existing_ancestor(&directory)?;
80
- if resolved.starts_with(source_root.canonicalize()?) {
69
+ if crate::providers::registry::inside_source(&directory)? {
81
70
  return Err(
82
71
  "Export output must be outside the Provider source directory"
83
72
  .into(),
@@ -114,10 +103,9 @@ pub fn json(
114
103
  session_id: &str,
115
104
  data: &impl serde::Serialize,
116
105
  output: &Path,
117
- source_root: &Path,
118
106
  suffix: &str,
119
107
  ) -> crate::Result<PathBuf> {
120
- write(output, session_id, suffix, source_root, |file| {
108
+ write(output, session_id, suffix, |file| {
121
109
  let mut writer = io::BufWriter::new(file);
122
110
  data.serialize(&mut serde_json::Serializer::with_formatter(
123
111
  &mut writer,
@@ -132,19 +120,17 @@ pub fn markdown(
132
120
  session_id: &str,
133
121
  text: &str,
134
122
  output: &Path,
135
- source_root: &Path,
136
123
  ) -> crate::Result<PathBuf> {
137
- markdown_with_suffix(session_id, text, output, source_root, ".md")
124
+ markdown_with_suffix(session_id, text, output, ".md")
138
125
  }
139
126
 
140
127
  fn markdown_with_suffix(
141
128
  session_id: &str,
142
129
  text: &str,
143
130
  output: &Path,
144
- source_root: &Path,
145
131
  suffix: &str,
146
132
  ) -> crate::Result<PathBuf> {
147
- write(output, session_id, suffix, source_root, |file| {
133
+ write(output, session_id, suffix, |file| {
148
134
  if cfg!(windows) {
149
135
  file.write_all(text.replace('\n', "\r\n").as_bytes())?;
150
136
  } else {
@@ -158,9 +144,8 @@ pub fn raw(
158
144
  session_id: &str,
159
145
  source: &Path,
160
146
  output: &Path,
161
- source_root: &Path,
162
147
  ) -> crate::Result<PathBuf> {
163
- write(output, session_id, ".raw.jsonl", source_root, |file| {
148
+ write(output, session_id, ".raw.jsonl", |file| {
164
149
  io::copy(&mut fs::File::open(source)?, file)?;
165
150
  Ok(())
166
151
  })
@@ -179,7 +164,6 @@ impl Excerpt<'_> {
179
164
  &self,
180
165
  format: crate::output::formats::OutputFormat,
181
166
  output: &Path,
182
- source_root: &Path,
183
167
  ) -> crate::Result<PathBuf> {
184
168
  use crate::output::formats::OutputFormat;
185
169
  let suffix =
@@ -197,7 +181,6 @@ impl Excerpt<'_> {
197
181
  &self.data.id,
198
182
  &document,
199
183
  output,
200
- source_root,
201
184
  &format!("{suffix}.json"),
202
185
  )
203
186
  }
@@ -211,7 +194,6 @@ impl Excerpt<'_> {
211
194
  self.incomplete,
212
195
  ),
213
196
  output,
214
- source_root,
215
197
  &format!("{suffix}.md"),
216
198
  ),
217
199
  OutputFormat::Raw | OutputFormat::Print => {
@@ -239,17 +221,13 @@ impl SessionExport<'_> {
239
221
  use crate::providers::contract::RawExport;
240
222
  match format {
241
223
  OutputFormat::Raw => match self.raw.as_ref().unwrap() {
242
- RawExport::File(source) => raw(
243
- &self.session.id,
244
- source,
245
- output,
246
- self.provider.source_root(),
247
- ),
224
+ RawExport::File(source) => {
225
+ raw(&self.session.id, source, output)
226
+ }
248
227
  RawExport::Session => json(
249
228
  &self.session.id,
250
229
  self.data.unwrap(),
251
230
  output,
252
- self.provider.source_root(),
253
231
  ".raw.json",
254
232
  ),
255
233
  },
@@ -261,13 +239,7 @@ impl SessionExport<'_> {
261
239
  {
262
240
  object.insert("summary".into(), summary.into());
263
241
  }
264
- json(
265
- &self.session.id,
266
- &payload,
267
- output,
268
- self.provider.source_root(),
269
- ".json",
270
- )
242
+ json(&self.session.id, &payload, output, ".json")
271
243
  }
272
244
  OutputFormat::Markdown => markdown(
273
245
  &self.session.id,
@@ -276,7 +248,6 @@ impl SessionExport<'_> {
276
248
  self.data.unwrap(),
277
249
  ),
278
250
  output,
279
- self.provider.source_root(),
280
251
  ),
281
252
  OutputFormat::Print => unreachable!(),
282
253
  }
@@ -1,10 +1,12 @@
1
1
  pub(super) mod transcript;
2
2
 
3
- use crate::compat::value::{field, text, truthy};
3
+ use crate::compat::value::{field, text};
4
4
  use crate::providers::contract::{
5
5
  DiagnosticSink, Provider, RecoverableDiagnostic,
6
6
  };
7
- use crate::providers::files::{self as file_sessions, SourceRoots};
7
+ use crate::providers::files::{
8
+ self as file_sessions, SourceFiles, SourceRoots,
9
+ };
8
10
  use crate::providers::jsonl;
9
11
  use crate::providers::title::{basename, normalize_title};
10
12
  use crate::session::timestamp::Timestamp;
@@ -51,7 +53,7 @@ impl Claude {
51
53
  })
52
54
  }
53
55
 
54
- fn files(&mut self) -> crate::Result<Vec<PathBuf>> {
56
+ fn files(&mut self) -> crate::Result<SourceFiles> {
55
57
  let files = self.roots.files(Some(2), |path, base| {
56
58
  path.extension().is_some_and(|ext| ext == "jsonl")
57
59
  && path.parent() != Some(base)
@@ -112,11 +114,14 @@ impl Claude {
112
114
  let mut skipped = 0;
113
115
  for entry in value["entries"].as_array().into_iter().flatten() {
114
116
  let id = text(&entry["sessionId"]);
115
- if id.trim().is_empty() {
117
+ let summary = &entry["summary"];
118
+ if id.trim().is_empty()
119
+ || !(summary.is_null() || summary.is_string())
120
+ {
116
121
  skipped += 1;
117
122
  continue;
118
123
  }
119
- titles.insert(id.to_owned(), entry["summary"].clone());
124
+ titles.insert(id.to_owned(), summary.clone());
120
125
  }
121
126
  if skipped > 0 {
122
127
  diagnostics(RecoverableDiagnostic::TitleCacheEntriesSkipped {
@@ -149,21 +154,11 @@ impl Claude {
149
154
  .into_owned();
150
155
  let created_at = parse_timestamp(text(&header["timestamp"]))
151
156
  .unwrap_or(Timestamp::try_from(path.metadata()?.modified()?)?);
152
- let explicit = match self
157
+ let explicit = self
153
158
  .titles(project, diagnostics)?
154
159
  .get(&id)
155
- .filter(|value| truthy(value))
156
- {
157
- Some(Value::String(title)) => normalize_title(title),
158
- Some(value) => {
159
- let kind = crate::compat::value::type_name(value);
160
- return Err(format!(
161
- "expected string or bytes-like object, got '{kind}'"
162
- )
163
- .into());
164
- }
165
- None => None,
166
- };
160
+ .and_then(Value::as_str)
161
+ .and_then(normalize_title);
167
162
  let first_user = scan
168
163
  .records
169
164
  .iter()
@@ -244,7 +239,7 @@ impl Provider for Claude {
244
239
  ) -> crate::Result<crate::providers::contract::Discovery> {
245
240
  self.titles.clear();
246
241
  file_sessions::discover(
247
- &self.files()?,
242
+ self.files()?,
248
243
  days,
249
244
  true,
250
245
  Self::scan,
@@ -261,7 +256,7 @@ impl Provider for Claude {
261
256
  let files = self.files()?;
262
257
  file_sessions::find(
263
258
  self.roots.base.clone().as_deref(),
264
- &files,
259
+ files,
265
260
  id,
266
261
  |path| path.file_stem().is_some_and(|name| name == id),
267
262
  |path| self.parse(path, Self::scan(path)?, diagnostics),
@@ -296,6 +291,10 @@ impl Provider for Claude {
296
291
  fn source_root(&self) -> &Path {
297
292
  self.roots.owned()
298
293
  }
294
+
295
+ fn select_source(&mut self) -> crate::Result<Option<&Path>> {
296
+ self.roots.select()
297
+ }
299
298
  }
300
299
 
301
300
  #[cfg(test)]
@@ -255,12 +255,16 @@ impl Decoder {
255
255
  continue;
256
256
  }
257
257
  let parts = output_parts(&item["content"], timestamp);
258
+ let mut updates = updates.clone();
259
+ if item["is_error"] == true {
260
+ updates.insert("status".into(), "error".into());
261
+ }
258
262
  if let Some(tool) = backfill(
259
263
  &mut self.messages,
260
264
  &self.pending,
261
265
  &call_id,
262
266
  &parts,
263
- Some(updates.clone()),
267
+ Some(updates),
264
268
  )? {
265
269
  if !parts.is_empty() {
266
270
  tool.state
@@ -79,7 +79,10 @@ pub fn subagent_notification(
79
79
  if !payload.is_object() {
80
80
  return None;
81
81
  }
82
- let agent_id = field(&payload, "agent_id").trim().to_owned();
82
+ let agent_id = match field(&payload, "agent_id").trim() {
83
+ "" => field(&payload, "agent_path").trim().to_owned(),
84
+ agent_id => agent_id.to_owned(),
85
+ };
83
86
  let nickname = field(&payload, "nickname").trim().to_owned();
84
87
  let completed = if payload["status"].is_object() {
85
88
  field(&payload["status"], "completed").trim().to_owned()
@@ -6,7 +6,9 @@ use crate::compat::value::text;
6
6
  use crate::providers::contract::{
7
7
  DiagnosticSink, Provider, RecoverableDiagnostic,
8
8
  };
9
- use crate::providers::files::{self as file_sessions, SourceRoots};
9
+ use crate::providers::files::{
10
+ self as file_sessions, SourceFiles, SourceRoots,
11
+ };
10
12
  use crate::providers::jsonl;
11
13
  use crate::providers::title::{basename, normalize_title};
12
14
  use crate::session::timestamp::Timestamp;
@@ -87,7 +89,7 @@ impl Codex {
87
89
  .cloned())
88
90
  }
89
91
 
90
- fn files(&mut self) -> crate::Result<Vec<PathBuf>> {
92
+ fn files(&mut self) -> crate::Result<SourceFiles> {
91
93
  self.roots.files(None, |path, _| {
92
94
  path.extension().is_some_and(|ext| ext == "jsonl")
93
95
  })
@@ -219,7 +221,7 @@ impl Provider for Codex {
219
221
  ) -> crate::Result<crate::providers::contract::Discovery> {
220
222
  self.titles = None;
221
223
  file_sessions::discover(
222
- &self.files()?,
224
+ self.files()?,
223
225
  days,
224
226
  true,
225
227
  Self::scan,
@@ -237,7 +239,7 @@ impl Provider for Codex {
237
239
  let files = self.files()?;
238
240
  file_sessions::find(
239
241
  self.roots.base.clone().as_deref(),
240
- &files,
242
+ files,
241
243
  id,
242
244
  |path| {
243
245
  path.file_name().is_some_and(|name| {
@@ -280,6 +282,10 @@ impl Provider for Codex {
280
282
  fn source_root(&self) -> &Path {
281
283
  self.roots.owned()
282
284
  }
285
+
286
+ fn select_source(&mut self) -> crate::Result<Option<&Path>> {
287
+ self.roots.select()
288
+ }
283
289
  }
284
290
 
285
291
  #[cfg(test)]
@@ -154,13 +154,17 @@ impl Decoder {
154
154
  })
155
155
  .collect::<Vec<_>>()
156
156
  .join("\n\n");
157
+ let is_notification = user_text.starts_with("<subagent_notification>")
158
+ && user_text.ends_with("</subagent_notification>");
157
159
  if self.pending_plan.is_some()
158
160
  && role == "user"
159
161
  && !user_text.is_empty()
162
+ && !is_notification
160
163
  {
161
164
  if user_text
162
165
  .trim_start()
163
166
  .starts_with("PLEASE IMPLEMENT THIS PLAN")
167
+ || user_text == "Implement the plan."
164
168
  {
165
169
  self.finish_plan("success", None);
166
170
  } else {
@@ -343,10 +347,13 @@ pub fn read(
343
347
  ) -> crate::Result<SessionData> {
344
348
  let mut decoder = Decoder::default();
345
349
  let mut stats = Stats::default();
350
+ let mut usage = Value::Null;
346
351
  jsonl::scan(&session.source_path, diagnostics, |record, diagnostics| {
347
- let result = decoder.record(&record, zh).and_then(|()| {
348
- let usage = &record["payload"]["info"]["total_token_usage"];
349
- stats.add_tokens(&usage["input_tokens"], &usage["output_tokens"])
352
+ let result = decoder.record(&record, zh).map(|()| {
353
+ let snapshot = &record["payload"]["info"]["total_token_usage"];
354
+ if snapshot.is_object() {
355
+ usage = snapshot.clone();
356
+ }
350
357
  });
351
358
  if let Err(error) = result {
352
359
  diagnostics(RecoverableDiagnostic::MessageConvertFailed(
@@ -356,6 +363,7 @@ pub fn read(
356
363
  Ok(())
357
364
  })?;
358
365
  decoder.finish_plan("fail", None);
366
+ stats.add_tokens(&usage["input_tokens"], &usage["output_tokens"])?;
359
367
  stats.message_count = decoder.messages.len();
360
368
  let mut data = session.payload(decoder.messages, stats);
361
369
  data.directory = session.source_metadata["cwd"].clone();
@@ -98,6 +98,7 @@ pub trait Provider: Send + Sync {
98
98
  diagnostics: &mut DiagnosticSink<'_>,
99
99
  ) -> crate::Result<SessionData>;
100
100
  fn source_root(&self) -> &Path;
101
+ fn select_source(&mut self) -> crate::Result<Option<&Path>>;
101
102
  fn search_roots(&self) -> crate::Result<Vec<(&'static str, PathBuf)>>;
102
103
 
103
104
  fn change_sources(&self, session: &Session) -> Vec<PathBuf> {
@@ -163,6 +163,11 @@ impl Provider for Cursor {
163
163
  fn source_root(&self) -> &Path {
164
164
  self.database.parent().unwrap()
165
165
  }
166
+
167
+ fn select_source(&mut self) -> crate::Result<Option<&Path>> {
168
+ self.database = (self.resolve_database)()?;
169
+ Ok(self.database.exists().then(|| self.source_root()))
170
+ }
166
171
  fn supports_format(&self, format: OutputFormat) -> bool {
167
172
  matches!(format, OutputFormat::Json | OutputFormat::Print)
168
173
  }