backdraft 0.4.0__tar.gz → 0.4.1__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 (146) hide show
  1. backdraft-0.4.1/.claude-plugin/marketplace.json +16 -0
  2. backdraft-0.4.1/.claude-plugin/plugin.json +14 -0
  3. {backdraft-0.4.0 → backdraft-0.4.1}/PKG-INFO +47 -9
  4. {backdraft-0.4.0 → backdraft-0.4.1}/README.md +46 -8
  5. {backdraft-0.4.0 → backdraft-0.4.1}/ROADMAP.md +6 -2
  6. {backdraft-0.4.0 → backdraft-0.4.1}/pyproject.toml +1 -1
  7. {backdraft-0.4.0 → backdraft-0.4.1}/site/docs.html +33 -6
  8. {backdraft-0.4.0 → backdraft-0.4.1}/site/llms.txt +9 -2
  9. {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft/SKILL.md +7 -1
  10. {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft-artifact/SKILL.md +1 -1
  11. {backdraft-0.4.0 → backdraft-0.4.1}/skills/backdraft-backfill/SKILL.md +7 -1
  12. backdraft-0.4.1/src/backdraft/__main__.py +6 -0
  13. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/cli.py +33 -15
  14. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_cli.py +89 -0
  15. {backdraft-0.4.0 → backdraft-0.4.1}/uv.lock +1 -1
  16. {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/ci.yml +0 -0
  17. {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/release.yml +0 -0
  18. {backdraft-0.4.0 → backdraft-0.4.1}/.github/workflows/site.yml +0 -0
  19. {backdraft-0.4.0 → backdraft-0.4.1}/.gitignore +0 -0
  20. {backdraft-0.4.0 → backdraft-0.4.1}/.python-version +0 -0
  21. {backdraft-0.4.0 → backdraft-0.4.1}/DESIGN.md +0 -0
  22. {backdraft-0.4.0 → backdraft-0.4.1}/LICENSE +0 -0
  23. {backdraft-0.4.0 → backdraft-0.4.1}/SPEC.md +0 -0
  24. {backdraft-0.4.0 → backdraft-0.4.1}/assets/backdraft-mark.svg +0 -0
  25. {backdraft-0.4.0 → backdraft-0.4.1}/demo/generate_sources.py +0 -0
  26. {backdraft-0.4.0 → backdraft-0.4.1}/demo/memo.backdraft.html +0 -0
  27. {backdraft-0.4.0 → backdraft-0.4.1}/demo/memo.md +0 -0
  28. {backdraft-0.4.0 → backdraft-0.4.1}/demo/sources/t12-summary.pdf +0 -0
  29. {backdraft-0.4.0 → backdraft-0.4.1}/demo/sources/underwriting-model.xlsx +0 -0
  30. {backdraft-0.4.0 → backdraft-0.4.1}/demo/walkthrough.md +0 -0
  31. {backdraft-0.4.0 → backdraft-0.4.1}/site/demo.html +0 -0
  32. {backdraft-0.4.0 → backdraft-0.4.1}/site/index.html +0 -0
  33. {backdraft-0.4.0 → backdraft-0.4.1}/spec/artifact.md +0 -0
  34. {backdraft-0.4.0 → backdraft-0.4.1}/spec/chunking.md +0 -0
  35. {backdraft-0.4.0 → backdraft-0.4.1}/spec/tokens.md +0 -0
  36. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/__init__.py +0 -0
  37. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/__init__.py +0 -0
  38. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/binder.py +0 -0
  39. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/cli.py +0 -0
  40. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/evidence.py +0 -0
  41. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/__init__.py +0 -0
  42. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/base.py +0 -0
  43. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/entail.py +0 -0
  44. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/overlap.py +0 -0
  45. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/recompute.py +0 -0
  46. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/bind/verify/value_trace.py +0 -0
  47. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/cli_context.py +0 -0
  48. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/credentials.py +0 -0
  49. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/__init__.py +0 -0
  50. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/base.py +0 -0
  51. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/csv.py +0 -0
  52. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/docx.py +0 -0
  53. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/image.py +0 -0
  54. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/pdf_text.py +0 -0
  55. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/pptx.py +0 -0
  56. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/sheet.py +0 -0
  57. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/text.py +0 -0
  58. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/vlm.py +0 -0
  59. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/vlm_settings.py +0 -0
  60. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/xls.py +0 -0
  61. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/extract/xlsx.py +0 -0
  62. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/__init__.py +0 -0
  63. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/cli.py +0 -0
  64. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/reader.py +0 -0
  65. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/gate/searcher.py +0 -0
  66. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/__init__.py +0 -0
  67. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/artifact.py +0 -0
  68. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/chunking.py +0 -0
  69. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/claims.py +0 -0
  70. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/errors.py +0 -0
  71. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/hashing.py +0 -0
  72. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/model.py +0 -0
  73. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/kernel/tokens.py +0 -0
  74. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/__init__.py +0 -0
  75. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/ledger.py +0 -0
  76. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/schema.sql +0 -0
  77. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/registry/store.py +0 -0
  78. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/__init__.py +0 -0
  79. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/_text.py +0 -0
  80. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/cli.py +0 -0
  81. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/footnotes.py +0 -0
  82. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/__init__.py +0 -0
  83. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/assets.py +0 -0
  84. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/components.py +0 -0
  85. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/fmt.py +0 -0
  86. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/fmt_vectors.py +0 -0
  87. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/page.py +0 -0
  88. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/html/text.py +0 -0
  89. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/markdown.py +0 -0
  90. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/placement.py +0 -0
  91. {backdraft-0.4.0 → backdraft-0.4.1}/src/backdraft/render/sidecar.py +0 -0
  92. {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest.py +0 -0
  93. {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest_registry.py +0 -0
  94. {backdraft-0.4.0 → backdraft-0.4.1}/tests/conftest_render.py +0 -0
  95. {backdraft-0.4.0 → backdraft-0.4.1}/tests/fakes.py +0 -0
  96. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/conftest.py +0 -0
  97. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/fake_registry.py +0 -0
  98. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_cli.py +0 -0
  99. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_ledger.py +0 -0
  100. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_reader.py +0 -0
  101. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_gate_searcher.py +0 -0
  102. {backdraft-0.4.0 → backdraft-0.4.1}/tests/gate/test_reader_cells.py +0 -0
  103. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/bind/backfill.bound.md +0 -0
  104. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/bind/frontwalk.bound.md +0 -0
  105. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/long_tail.json +0 -0
  106. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/long_tail.txt +0 -0
  107. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/many_small.json +0 -0
  108. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/many_small.txt +0 -0
  109. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/mixed.json +0 -0
  110. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/mixed.txt +0 -0
  111. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/one_giant.json +0 -0
  112. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/one_giant.txt +0 -0
  113. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/short_page.json +0 -0
  114. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/short_page.txt +0 -0
  115. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/unsplittable.json +0 -0
  116. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/chunking/unsplittable.txt +0 -0
  117. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/backfill.footnotes.md +0 -0
  118. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/backfill.sidecar.json +0 -0
  119. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/demo.footnotes.md +0 -0
  120. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden/render/demo.sidecar.json +0 -0
  121. {backdraft-0.4.0 → backdraft-0.4.1}/tests/golden_util.py +0 -0
  122. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_binder.py +0 -0
  123. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_cli.py +0 -0
  124. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_evidence.py +0 -0
  125. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_bind_verify.py +0 -0
  126. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_chunking.py +0 -0
  127. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_claims.py +0 -0
  128. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_credentials.py +0 -0
  129. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_extract.py +0 -0
  130. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_fmt_parity.py +0 -0
  131. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_footnotes.py +0 -0
  132. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_gate_integration.py +0 -0
  133. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_hashing.py +0 -0
  134. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_html.py +0 -0
  135. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_invariants.py +0 -0
  136. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_model.py +0 -0
  137. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_placement.py +0 -0
  138. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_records_location.py +0 -0
  139. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_registry.py +0 -0
  140. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_reingest.py +0 -0
  141. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_render_cli.py +0 -0
  142. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_render_markdown.py +0 -0
  143. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_sidecar.py +0 -0
  144. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_snapshot_settings.py +0 -0
  145. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_tokens.py +0 -0
  146. {backdraft-0.4.0 → backdraft-0.4.1}/tests/test_value_trace.py +0 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "backdraft",
3
+ "owner": {
4
+ "name": "Spencer Braun",
5
+ "url": "https://github.com/spencerbraun"
6
+ },
7
+ "description": "Self-hosting marketplace for the backdraft plugin: agent skills for writing documents whose factual claims carry verifiable citations.",
8
+ "plugins": [
9
+ {
10
+ "name": "backdraft",
11
+ "source": "./",
12
+ "description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
13
+ "category": "productivity"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "backdraft",
3
+ "displayName": "Backdraft",
4
+ "version": "0.4.1",
5
+ "description": "Drop-in provenance for factual claims: one click from any claim to the evidence behind it. Ships the backdraft writing, backfill, and artifact-reading skills; the CLI installs separately (uv tool install backdraft).",
6
+ "author": {
7
+ "name": "Spencer Braun",
8
+ "url": "https://github.com/spencerbraun"
9
+ },
10
+ "homepage": "https://backdraft.dev",
11
+ "repository": "https://github.com/spencerbraun/backdraft",
12
+ "license": "MIT",
13
+ "keywords": ["citations", "provenance", "sources", "evidence", "documents", "pdf", "xlsx", "writing"]
14
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: backdraft
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Drop-in provenance for factual claims: one click from any claim to the evidence behind it.
5
5
  Project-URL: Homepage, https://backdraft.dev
6
6
  Project-URL: Documentation, https://backdraft.dev/docs.html
@@ -232,13 +232,7 @@ uv run pytest
232
232
 
233
233
  ## Skills
234
234
 
235
- Three agent skills ship inside the package, and the CLI installs them:
236
-
237
- ```bash
238
- backdraft skill install # the writing skill, into ~/.claude/skills/
239
- backdraft skill install --all # plus backfill and artifact-reading
240
- backdraft skill install --project # into this repo's .claude/skills/
241
- ```
235
+ Three agent skills ship inside the package and in this repo:
242
236
 
243
237
  | Skill | For |
244
238
  |---|---|
@@ -247,7 +241,51 @@ backdraft skill install --project # into this repo's .claude/skills/
247
241
  | [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
248
242
 
249
243
  The CLI is the system; a skill is one page telling an agent to use it. Nothing in
250
- the skills is required to use backdraft by hand.
244
+ the skills is required to use backdraft by hand. How the skills reach your agent
245
+ depends on the harness:
246
+
247
+ **Claude Code.** The repo is its own plugin marketplace, so the plugin route
248
+ tracks releases:
249
+
250
+ ```text
251
+ /plugin marketplace add spencerbraun/backdraft
252
+ /plugin install backdraft@backdraft
253
+ ```
254
+
255
+ Or have the CLI copy the skills into your skills directory:
256
+
257
+ ```bash
258
+ backdraft skill install # the writing skill, into ~/.claude/skills/
259
+ backdraft skill install --all # plus backfill and artifact-reading
260
+ backdraft skill install --project # into this repo's .claude/skills/
261
+ ```
262
+
263
+ **Claude Cowork.** Once the plugin is listed in Anthropic's community
264
+ directory, it installs from Cowork's built-in skills directory. Until then,
265
+ zip a skill folder from `skills/` and upload it under Customize > Skills.
266
+ Inside a Cowork session the CLI itself runs per-command as
267
+ `uvx backdraft ...`, since installs do not persist between sessions.
268
+
269
+ **Codex, Cursor, Copilot.** Agents in this family read skills from
270
+ `~/.agents/skills`:
271
+
272
+ ```bash
273
+ backdraft skill install --agent codex # into ~/.agents/skills/
274
+ ```
275
+
276
+ or commit the skill folders to your repo under `.agents/skills/` so every
277
+ checkout carries them.
278
+
279
+ **Standing context, any harness.** Paste this into your repo's AGENTS.md:
280
+
281
+ ```markdown
282
+ ## backdraft (cited writing)
283
+ When a document must cite its sources, write it through the backdraft CLI:
284
+ it shows source text with a citation token over every span, and only shown
285
+ spans are citable. In a sandboxed session run every command as
286
+ `uvx backdraft ...` (no install, no PATH edits). Start from
287
+ `uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
288
+ ```
251
289
 
252
290
  ## Documentation
253
291
 
@@ -207,13 +207,7 @@ uv run pytest
207
207
 
208
208
  ## Skills
209
209
 
210
- Three agent skills ship inside the package, and the CLI installs them:
211
-
212
- ```bash
213
- backdraft skill install # the writing skill, into ~/.claude/skills/
214
- backdraft skill install --all # plus backfill and artifact-reading
215
- backdraft skill install --project # into this repo's .claude/skills/
216
- ```
210
+ Three agent skills ship inside the package and in this repo:
217
211
 
218
212
  | Skill | For |
219
213
  |---|---|
@@ -222,7 +216,51 @@ backdraft skill install --project # into this repo's .claude/skills/
222
216
  | [`backdraft-artifact`](skills/backdraft-artifact/SKILL.md) | reading someone else's artifact cold |
223
217
 
224
218
  The CLI is the system; a skill is one page telling an agent to use it. Nothing in
225
- the skills is required to use backdraft by hand.
219
+ the skills is required to use backdraft by hand. How the skills reach your agent
220
+ depends on the harness:
221
+
222
+ **Claude Code.** The repo is its own plugin marketplace, so the plugin route
223
+ tracks releases:
224
+
225
+ ```text
226
+ /plugin marketplace add spencerbraun/backdraft
227
+ /plugin install backdraft@backdraft
228
+ ```
229
+
230
+ Or have the CLI copy the skills into your skills directory:
231
+
232
+ ```bash
233
+ backdraft skill install # the writing skill, into ~/.claude/skills/
234
+ backdraft skill install --all # plus backfill and artifact-reading
235
+ backdraft skill install --project # into this repo's .claude/skills/
236
+ ```
237
+
238
+ **Claude Cowork.** Once the plugin is listed in Anthropic's community
239
+ directory, it installs from Cowork's built-in skills directory. Until then,
240
+ zip a skill folder from `skills/` and upload it under Customize > Skills.
241
+ Inside a Cowork session the CLI itself runs per-command as
242
+ `uvx backdraft ...`, since installs do not persist between sessions.
243
+
244
+ **Codex, Cursor, Copilot.** Agents in this family read skills from
245
+ `~/.agents/skills`:
246
+
247
+ ```bash
248
+ backdraft skill install --agent codex # into ~/.agents/skills/
249
+ ```
250
+
251
+ or commit the skill folders to your repo under `.agents/skills/` so every
252
+ checkout carries them.
253
+
254
+ **Standing context, any harness.** Paste this into your repo's AGENTS.md:
255
+
256
+ ```markdown
257
+ ## backdraft (cited writing)
258
+ When a document must cite its sources, write it through the backdraft CLI:
259
+ it shows source text with a citation token over every span, and only shown
260
+ spans are citable. In a sandboxed session run every command as
261
+ `uvx backdraft ...` (no install, no PATH edits). Start from
262
+ `uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.
263
+ ```
226
264
 
227
265
  ## Documentation
228
266
 
@@ -46,8 +46,12 @@ the objection rather than rediscovering it.
46
46
  - **HTML pages and email (.eml)** — both arrive in diligence folders, both
47
47
  have messy identity questions (what is "the source" of a web page?) that
48
48
  deserve a decision row before code.
49
- - **Distribution** — published on PyPI; still queued: a skills-registry
50
- listing when one becomes the standard channel.
49
+ - **Distribution** — published on PyPI; the repo now ships
50
+ `.claude-plugin/plugin.json` and `marketplace.json`, so it installs as a
51
+ Claude Code plugin and self-hosts its marketplace, and the AGENTS.md
52
+ snippet is published in the README and site docs. Still queued: the
53
+ community-directory listing (submission form is a maintainer action,
54
+ pending), which is also what surfaces the skills in Cowork's directory.
51
55
 
52
56
  ## Someday
53
57
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "backdraft"
3
- version = "0.4.0"
3
+ version = "0.4.1"
4
4
  description = "Drop-in provenance for factual claims: one click from any claim to the evidence behind it."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -109,7 +109,7 @@ footer{text-align:center;font-family:var(--sans);font-size:.74rem;color:var(--mu
109
109
  <a href="#writing">Writing rules</a>
110
110
  <a href="#verification">Verification</a>
111
111
  <a href="#files">Files &amp; the artifact</a>
112
- <a href="#agents">Agents</a>
112
+ <a href="#agents">Agents &amp; harnesses</a>
113
113
  <a href="#security">Security &amp; privacy</a>
114
114
  <a href="#faq">FAQ</a>
115
115
  </aside>
@@ -277,14 +277,41 @@ of a gigabyte corpus is a ~2&nbsp;MB file. Recipients need the one
277
277
  </section>
278
278
 
279
279
  <section id="agents">
280
- <h2>Agents</h2>
281
- <pre>backdraft skill install <span class="dim"># the writing skill, into ~/.claude/skills/</span>
282
- backdraft skill install --all <span class="dim"># plus backfill and artifact-reading</span>
283
- backdraft skill install --project<span class="dim"> # into this repo's .claude/skills/</span></pre>
280
+ <h2>Agents and harnesses</h2>
284
281
  <p>The skill's core instruction is a substitution: for source documents, use
285
282
  <code>backdraft read</code>/<code>search</code>, never raw file reads. The
286
283
  token-efficient reference for any agent is <a href="llms.txt">llms.txt</a>
287
- (~800 tokens).</p>
284
+ (~800 tokens). How the skill reaches your agent depends on the harness.</p>
285
+ <h3>Claude Code</h3>
286
+ <p>The repo is its own plugin marketplace, so the plugin route tracks
287
+ releases:</p>
288
+ <pre>/plugin marketplace add spencerbraun/backdraft
289
+ /plugin install backdraft@backdraft</pre>
290
+ <p>Or have the CLI copy the skills into your skills directory:</p>
291
+ <pre>backdraft skill install <span class="dim"># the writing skill, into ~/.claude/skills/</span>
292
+ backdraft skill install --all <span class="dim"># plus backfill and artifact-reading</span>
293
+ backdraft skill install --project<span class="dim"> # into this repo's .claude/skills/</span></pre>
294
+ <h3>Claude Cowork</h3>
295
+ <p>Once the plugin is listed in Anthropic's community directory, it installs
296
+ from Cowork's built-in skills directory; until then, zip a skill folder from
297
+ the repo's <code>skills/</code> and upload it under Customize &gt; Skills.
298
+ Inside a session, run the CLI per-command as <code>uvx backdraft ...</code>,
299
+ since installs do not persist between sessions.</p>
300
+ <h3>Codex, Cursor, Copilot</h3>
301
+ <p>Agents in this family read skills from <code>~/.agents/skills</code>:</p>
302
+ <pre>backdraft skill install --agent codex <span class="dim"># into ~/.agents/skills/</span></pre>
303
+ <p>Or commit the skill folders to your repo under <code>.agents/skills/</code>
304
+ so every checkout carries them. For Codex cloud environments, add
305
+ <code>pip install backdraft</code> to the setup script, which runs while the
306
+ network is still on.</p>
307
+ <h3>Standing context</h3>
308
+ <p>For any harness that reads AGENTS.md, paste this into your repo's file:</p>
309
+ <pre>## backdraft (cited writing)
310
+ When a document must cite its sources, write it through the backdraft CLI:
311
+ it shows source text with a citation token over every span, and only shown
312
+ spans are citable. In a sandboxed session run every command as
313
+ `uvx backdraft ...` (no install, no PATH edits). Start from
314
+ `uvx backdraft --help`; ground truth is https://backdraft.dev/llms.txt.</pre>
288
315
  </section>
289
316
 
290
317
  <section id="security">
@@ -10,8 +10,15 @@ https://github.com/spencerbraun/backdraft/blob/main/skills/backdraft/SKILL.md
10
10
 
11
11
  ## Install
12
12
 
13
- uv tool install backdraft
14
- backdraft skill install # copies the writing skill into ~/.claude/skills/
13
+ # Sandboxed session (Cowork, Codex cloud, CI): do NOT install. Run every
14
+ # command through uvx instead: `uvx backdraft init`, `uvx backdraft read` …
15
+ # No PATH edits, never run `backdraft skill install`, never modify agent
16
+ # config from inside a session.
17
+ # Owned machine:
18
+ uv tool install backdraft # pip install backdraft where uv is absent
19
+ # Human-run setup step (not for sessions): `backdraft skill install` copies
20
+ # the writing skill into ~/.claude/skills/ (Claude Code); add
21
+ # `--agent codex` to target ~/.agents/skills for the Codex family.
15
22
  # Vision-model extraction for PDFs and images ships by default (needs poppler
16
23
  # for PDFs + BACKDRAFT_VLM_API_KEY). Ambient provider keys (OPENAI_API_KEY
17
24
  # etc.) are NEVER read; only BACKDRAFT_*.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: backdraft
3
- description: Write a document whose factual claims are each traceable to the source text behind them. Use whenever the task is to produce a memo, report, summary, analysis, brief, or answer grounded in specific source files (PDFs, spreadsheets, docs) and the reader will need to check where a number or statement came from. Triggers on "cite your sources", "with citations", "show where this came from", "grounded in these documents", "provenance", "backdraft", or any request to read source files and write findings from them.
3
+ description: Write documents whose factual claims each carry a checkable citation into source files. Use when producing a memo, report, or analysis from PDFs, spreadsheets, or docs with citations or provenance.
4
4
  ---
5
5
 
6
6
  # backdraft — writing with citations that resolve
@@ -13,6 +13,12 @@ fact you obtained any other way has no receipt and cannot be cited.
13
13
 
14
14
  ## Workflow
15
15
 
16
+ If `backdraft` is not on PATH (sandboxed sessions start fresh), run every
17
+ command through uvx instead: `uvx backdraft init`, `uvx backdraft read`, and so
18
+ on. Where uv is absent, `pip install backdraft` once per session, then use
19
+ `backdraft` or `python -m backdraft`. Never modify PATH, shell rc files, or
20
+ agent config directories; installs from PyPI need no special permissions.
21
+
16
22
  ```bash
17
23
  backdraft init # once per project
18
24
  backdraft ingest report.pdf model.xlsx notes.md # every source, up front
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: backdraft-artifact
3
- description: Read, check, or summarize a backdraft artifact — a `*.backdraft.json` sidecar or `*.backdraft.html` file — encountered with no other context. Use when handed a file with either extension, when a document mentions claims with tokens shaped `bd:slug:locator:hash`, or when asked to "check the citations in this", "verify this artifact", "what does this sidecar say", "audit these claims", or "is this document's evidence any good". Applies to reviewing someone else's bound document, not to producing one.
3
+ description: Read and check a backdraft artifact (*.backdraft.json or *.backdraft.html), verifying receipts and summarizing claim statuses. Use when handed such a file or asked to audit its citations.
4
4
  ---
5
5
 
6
6
  # backdraft-artifact — reading a bound document cold
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: backdraft-backfill
3
- description: Attribute an existing document to its sources after the fact — attach a citation to each factual claim in a memo, report, or draft that was written without them, and produce an explicit list of the claims no source supports. Use when asked to "add citations to this", "source these claims", "where did these numbers come from", "check this against the source documents", "fact-check this memo against these files", or to audit an existing document for unsupported statements. Not for writing a new document — use the `backdraft` skill for that.
3
+ description: Attach citations to an existing document after the fact, tracing each factual claim to source files and listing unsupported claims. Use for add-citations or fact-check-against-these-files requests.
4
4
  ---
5
5
 
6
6
  # backdraft-backfill — attributing a document that already exists
@@ -14,6 +14,12 @@ what the claim says — a plausible-looking hit is not evidence.**
14
14
 
15
15
  ## 1. Ingest the sources
16
16
 
17
+ If `backdraft` is not on PATH (sandboxed sessions start fresh), run every
18
+ command through uvx instead: `uvx backdraft init`, `uvx backdraft read`, and so
19
+ on. Where uv is absent, `pip install backdraft` once per session, then use
20
+ `backdraft` or `python -m backdraft`. Never modify PATH, shell rc files, or
21
+ agent config directories; installs from PyPI need no special permissions.
22
+
17
23
  ```bash
18
24
  backdraft init
19
25
  backdraft ingest sources/*.pdf sources/*.xlsx
@@ -0,0 +1,6 @@
1
+ """`python -m backdraft` — the CLI for environments without a console script."""
2
+
3
+ from backdraft.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -154,6 +154,11 @@ def ingest(
154
154
 
155
155
  SKILLS = ("backdraft", "backdraft-backfill", "backdraft-artifact")
156
156
 
157
+ # Skills directories per agent family: Claude Code reads `.claude/skills/`;
158
+ # `.agents/skills/` is the Agent Skills standard path read by OpenAI Codex,
159
+ # Cursor, Copilot and others.
160
+ AGENT_DIRS = {"claude": ".claude", "codex": ".agents"}
161
+
157
162
 
158
163
  def _skills_source() -> Path:
159
164
  """Where the bundled skills live: package data in a wheel, repo in a checkout."""
@@ -171,37 +176,50 @@ def skill(
171
176
  action: Annotated[str, typer.Argument(help="`install` is the only action.")] = "install",
172
177
  project: Annotated[
173
178
  bool,
174
- typer.Option("--project", help="Install into ./.claude/skills instead of ~/.claude/skills."),
179
+ typer.Option("--project", help="Install into the project's skills directory instead of the home one."),
175
180
  ] = False,
176
181
  all_: Annotated[
177
182
  bool,
178
183
  typer.Option("--all", help="Also install the backfill and artifact-reading skills."),
179
184
  ] = False,
185
+ agent: Annotated[
186
+ str,
187
+ typer.Option("--agent", help="Target agent layout: `claude`, `codex`, or `all`."),
188
+ ] = "claude",
180
189
  ) -> None:
181
190
  """Install the agent skill: `backdraft skill install`, then ask for cited work.
182
191
 
183
- Copies the writing skill into your agent's skills directory (Claude Code
184
- layout: `~/.claude/skills/`, or the project's `.claude/skills/` with
185
- `--project`). `--all` adds the backfill and artifact-reading skills.
192
+ Copies the writing skill into your agent's skills directory. `--agent claude`
193
+ (the default) targets Claude Code's `~/.claude/skills/`; `--agent codex`
194
+ targets `~/.agents/skills/`, the Agent Skills standard path read by OpenAI
195
+ Codex, Cursor, Copilot and others; `--agent all` targets both. `--project`
196
+ installs under the current directory (`.claude/skills/`, `.agents/skills/`)
197
+ instead of the home directory. `--all` adds the backfill and
198
+ artifact-reading skills.
186
199
  """
187
200
  import shutil
188
201
 
189
202
  with guard():
190
203
  if action != "install":
191
204
  raise UsageError(f"unknown action {action!r}; try: backdraft skill install")
205
+ if agent not in (*AGENT_DIRS, "all"):
206
+ raise UsageError(f"unknown agent {agent!r}; try: claude, codex, or all")
192
207
  source = _skills_source()
193
- target_root = (Path.cwd() / ".claude" if project else Path.home() / ".claude") / "skills"
208
+ agents = tuple(AGENT_DIRS) if agent == "all" else (agent,)
209
+ base = Path.cwd() if project else Path.home()
194
210
  names = SKILLS if all_ else SKILLS[:1]
195
- for name in names:
196
- src = source / name
197
- if not src.is_dir():
198
- raise UsageError(f"bundled skill missing: {name}")
199
- dst = target_root / name
200
- dst.mkdir(parents=True, exist_ok=True)
201
- for item in src.iterdir():
202
- if item.is_file():
203
- shutil.copy2(item, dst / item.name)
204
- typer.echo(f"installed {name} -> {dst}")
211
+ for agent_name in agents:
212
+ target_root = base / AGENT_DIRS[agent_name] / "skills"
213
+ for name in names:
214
+ src = source / name
215
+ if not src.is_dir():
216
+ raise UsageError(f"bundled skill missing: {name}")
217
+ dst = target_root / name
218
+ dst.mkdir(parents=True, exist_ok=True)
219
+ for item in src.iterdir():
220
+ if item.is_file():
221
+ shutil.copy2(item, dst / item.name)
222
+ typer.echo(f"installed {name} -> {dst}")
205
223
  typer.echo('next: ask your agent for cited work, e.g. "Write me a memo from ./docs, with citations."')
206
224
 
207
225
 
@@ -217,3 +217,92 @@ def test_skill_install_copies_the_bundled_skill(tmp_path, monkeypatch) -> None:
217
217
  result = CliRunner().invoke(app, ["skill", "install", "--all"])
218
218
  assert result.exit_code == 0, result.output
219
219
  assert (tmp_path / ".claude" / "skills" / "backdraft-backfill" / "SKILL.md").is_file()
220
+
221
+
222
+ def test_skill_install_default_agent_touches_only_claude(tmp_path, monkeypatch) -> None:
223
+ from typer.testing import CliRunner
224
+
225
+ from backdraft.cli import app
226
+
227
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
228
+ result = CliRunner().invoke(app, ["skill", "install"])
229
+ assert result.exit_code == 0, result.output
230
+ assert (tmp_path / ".claude" / "skills" / "backdraft" / "SKILL.md").is_file()
231
+ assert not (tmp_path / ".agents").exists()
232
+
233
+
234
+ def test_skill_install_agent_codex_uses_the_standard_path(tmp_path, monkeypatch) -> None:
235
+ from typer.testing import CliRunner
236
+
237
+ from backdraft.cli import app
238
+
239
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
240
+ result = CliRunner().invoke(app, ["skill", "install", "--agent", "codex"])
241
+ assert result.exit_code == 0, result.output
242
+ assert (tmp_path / ".agents" / "skills" / "backdraft" / "SKILL.md").is_file()
243
+ assert not (tmp_path / ".claude").exists()
244
+
245
+
246
+ def test_skill_install_agent_all_lands_in_both_layouts(tmp_path, monkeypatch) -> None:
247
+ from typer.testing import CliRunner
248
+
249
+ from backdraft.cli import app
250
+
251
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
252
+ result = CliRunner().invoke(app, ["skill", "install", "--agent", "all", "--all"])
253
+ assert result.exit_code == 0, result.output
254
+ for root in (".claude", ".agents"):
255
+ for name in ("backdraft", "backdraft-backfill", "backdraft-artifact"):
256
+ assert (tmp_path / root / "skills" / name / "SKILL.md").is_file()
257
+
258
+
259
+ def test_skill_install_agent_codex_project_lands_in_cwd(tmp_path, monkeypatch) -> None:
260
+ from typer.testing import CliRunner
261
+
262
+ from backdraft.cli import app
263
+
264
+ monkeypatch.chdir(tmp_path)
265
+ result = CliRunner().invoke(app, ["skill", "install", "--agent", "codex", "--project"])
266
+ assert result.exit_code == 0, result.output
267
+ assert (tmp_path / ".agents" / "skills" / "backdraft" / "SKILL.md").is_file()
268
+
269
+
270
+ def test_skill_install_rejects_an_unknown_agent(tmp_path, monkeypatch) -> None:
271
+ from typer.testing import CliRunner
272
+
273
+ from backdraft.cli import app
274
+
275
+ monkeypatch.setattr("pathlib.Path.home", lambda: tmp_path)
276
+ result = CliRunner().invoke(app, ["skill", "install", "--agent", "cursor"])
277
+ assert result.exit_code == 1
278
+ assert "unknown agent" in result.output
279
+
280
+
281
+ def test_python_dash_m_backdraft_runs_the_cli() -> None:
282
+ import subprocess
283
+ import sys
284
+
285
+ result = subprocess.run(
286
+ [sys.executable, "-m", "backdraft", "--help"],
287
+ capture_output=True,
288
+ text=True,
289
+ )
290
+ assert result.returncode == 0, result.stderr
291
+ assert "ingest" in result.stdout
292
+
293
+
294
+ def test_skill_descriptions_fit_the_upload_cap() -> None:
295
+ """claude.ai's skill upload rejects descriptions over 200 characters."""
296
+ import re
297
+
298
+ from backdraft.cli import SKILLS, _skills_source
299
+
300
+ for name in SKILLS:
301
+ text = (_skills_source() / name / "SKILL.md").read_text(encoding="utf-8")
302
+ match = re.search(r"^description: (.+)$", text, flags=re.MULTILINE)
303
+ assert match, name
304
+ assert len(match.group(1)) <= 200, (name, len(match.group(1)))
305
+ # An unquoted YAML scalar dies on ": " — keep descriptions parseable.
306
+ assert ": " not in match.group(1), name
307
+ named = re.search(r"^name: (.+)$", text, flags=re.MULTILINE)
308
+ assert named and named.group(1) == name
@@ -53,7 +53,7 @@ wheels = [
53
53
 
54
54
  [[package]]
55
55
  name = "backdraft"
56
- version = "0.4.0"
56
+ version = "0.4.1"
57
57
  source = { editable = "." }
58
58
  dependencies = [
59
59
  { name = "openai" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes