sediment-cli 0.3.0__tar.gz → 0.5.0__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 (90) hide show
  1. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/.gitignore +1 -3
  2. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/PKG-INFO +5 -5
  3. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/pyproject.toml +5 -5
  4. sediment_cli-0.5.0/sediment_cli/__init__.py +72 -0
  5. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/README.md +1 -1
  6. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/package.json +2 -2
  7. sediment_cli-0.5.0/sediment_cli/agent_guide.txt +142 -0
  8. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/attribution.py +38 -57
  9. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/cli.py +244 -55
  10. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/client.py +15 -14
  11. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/local_postgres.py +4 -1
  12. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/ui.py +5 -0
  13. sediment_cli-0.5.0/tests/test_agent_guide.py +102 -0
  14. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli.py +164 -12
  15. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_capture_authority.py +76 -30
  16. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_db.py +4 -1
  17. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_libpq_unavailable.py +13 -1
  18. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_remote.py +213 -1
  19. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_delivery_transport.py +7 -2
  20. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_e2e_quickstart.py +35 -10
  21. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_installed_wheel.py +17 -0
  22. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_local_postgres.py +3 -2
  23. sediment_cli-0.5.0/tests/test_server_migration.py +207 -0
  24. sediment_cli-0.5.0/tests/testdata/help/db-check.txt +9 -0
  25. sediment_cli-0.5.0/tests/testdata/help/db-provision.txt +14 -0
  26. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/db-upgrade.txt +2 -1
  27. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/db.txt +4 -1
  28. sediment_cli-0.5.0/tests/testdata/help/guide.txt +7 -0
  29. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/install.txt +2 -8
  30. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/sediment.txt +2 -1
  31. sediment_cli-0.3.0/sediment_cli/__init__.py +0 -22
  32. sediment_cli-0.3.0/tests/testdata/help/db-provision.txt +0 -9
  33. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/LICENSE +0 -0
  34. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/hatch_build.py +0 -0
  35. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/LICENSE +0 -0
  36. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/index.ts +0 -0
  37. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/lib/contract.ts +0 -0
  38. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/lib/process.ts +0 -0
  39. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/lib/provider.ts +0 -0
  40. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/lib/register.ts +0 -0
  41. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/_pi/lib/retrieval.ts +0 -0
  42. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/delivery.py +0 -0
  43. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/evidence.py +0 -0
  44. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/sediment_cli/transcript.py +0 -0
  45. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/conftest.py +0 -0
  46. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_evidence.py +0 -0
  47. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_evidence_integration.py +0 -0
  48. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_help.py +0 -0
  49. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_cli_ui.py +0 -0
  50. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_consumer_profile_cli.py +0 -0
  51. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/test_demo_verb.py +0 -0
  52. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/commit.txt +0 -0
  53. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/db-status.txt +0 -0
  54. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/delivery-enqueue.txt +0 -0
  55. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/delivery-replay.txt +0 -0
  56. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/delivery-status.txt +0 -0
  57. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/delivery.txt +0 -0
  58. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/demo.txt +0 -0
  59. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/derive.txt +0 -0
  60. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/doctor.txt +0 -0
  61. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/evidence-fetch.txt +0 -0
  62. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/evidence-inspect.txt +0 -0
  63. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/evidence-inventory.txt +0 -0
  64. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/evidence.txt +0 -0
  65. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export-diff-sft.txt +0 -0
  66. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export-dpo.txt +0 -0
  67. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export-recovery.txt +0 -0
  68. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export-rlvr.txt +0 -0
  69. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export-sft.txt +0 -0
  70. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/export.txt +0 -0
  71. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/facts.txt +0 -0
  72. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/login.txt +0 -0
  73. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/logout.txt +0 -0
  74. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/mirror-gc.txt +0 -0
  75. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/quarantine-inference-calls.txt +0 -0
  76. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/quarantine-log.txt +0 -0
  77. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/quarantine.txt +0 -0
  78. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/release.txt +0 -0
  79. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-abandonment.txt +0 -0
  80. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-attribution-share.txt +0 -0
  81. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-dataset-diagnostics.txt +0 -0
  82. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-label-confidence-inspection.txt +0 -0
  83. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-lifecycle.txt +0 -0
  84. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-merge-retention.txt +0 -0
  85. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-model.txt +0 -0
  86. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-precision.txt +0 -0
  87. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report-recovery-yield.txt +0 -0
  88. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/report.txt +0 -0
  89. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/server.txt +0 -0
  90. {sediment_cli-0.3.0 → sediment_cli-0.5.0}/tests/testdata/help/uninstall.txt +0 -0
@@ -13,8 +13,7 @@ exports/
13
13
  !docs/exports/
14
14
  .env
15
15
  .DS_Store
16
- .claude/settings.local.*
17
- .claude/worktrees/
16
+ .claude/
18
17
 
19
18
  # Deployment-local compose overrides (mirror credentials etc.) — never in PRs
20
19
  compose.override.yml
@@ -29,4 +28,3 @@ litellm/config.local.yaml
29
28
  !.env.example
30
29
  .agents/
31
30
  skills-lock.json
32
- .claude/skills/asd-ste100
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sediment-cli
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: The sediment command: login, install, server, facts, exports
5
5
  Project-URL: Source, https://github.com/sediment-ai/sediment
6
6
  Project-URL: Documentation, https://github.com/sediment-ai/sediment/tree/main/docs
@@ -10,7 +10,7 @@ License-Expression: AGPL-3.0-or-later
10
10
  License-File: LICENSE
11
11
  Requires-Python: >=3.12
12
12
  Requires-Dist: httpx>=0.27
13
- Requires-Dist: sediment-api==0.3.0
14
- Requires-Dist: sediment-core==0.3.0
15
- Requires-Dist: sediment-derive==0.3.0
16
- Requires-Dist: sediment-export==0.3.0
13
+ Requires-Dist: sediment-api==0.5.0
14
+ Requires-Dist: sediment-core==0.5.0
15
+ Requires-Dist: sediment-derive==0.5.0
16
+ Requires-Dist: sediment-export==0.5.0
@@ -5,7 +5,7 @@
5
5
  # Depends on sediment-api deliberately: the operator verbs are DB-local by
6
6
  # design (ADR 0001) and `server`/reports/mirror-gc forward into it.
7
7
  name = "sediment-cli"
8
- version = "0.3.0"
8
+ version = "0.5.0"
9
9
  description = "The sediment command: login, install, server, facts, exports"
10
10
  requires-python = ">=3.12"
11
11
  license = "AGPL-3.0-or-later"
@@ -14,10 +14,10 @@ license-files = ["LICENSE"]
14
14
  # so an install never mixes member versions (the release workflow asserts
15
15
  # the versions agree before publishing).
16
16
  dependencies = [
17
- "sediment-core==0.3.0",
18
- "sediment-derive==0.3.0",
19
- "sediment-export==0.3.0",
20
- "sediment-api==0.3.0",
17
+ "sediment-core==0.5.0",
18
+ "sediment-derive==0.5.0",
19
+ "sediment-export==0.5.0",
20
+ "sediment-api==0.5.0",
21
21
  "httpx>=0.27",
22
22
  ]
23
23
 
@@ -0,0 +1,72 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ """The sediment CLI — the top-level `cli/` workspace member.
3
+
4
+ Remote verbs speak HTTP through `.client`; operator verbs run where the
5
+ facts live (ADR 0001); `.attribution` is the stdlib-only stamper. The
6
+ version is single-sourced from ``sediment_api`` (the string ``/v1/me``
7
+ and ``/health`` report)."""
8
+
9
+ import re
10
+
11
+ from sediment_api import __version__ as __version__
12
+
13
+ # The release policy's two shapes, X.Y.Z and X.Y.ZrcN, in ASCII digits.
14
+ _RELEASE_VERSION = re.compile(r"[0-9]+\.[0-9]+\.[0-9]+(?:rc[0-9]+)?")
15
+ # A release candidate pins these at the same pre-release; uv admits a
16
+ # pre-release only when a direct requirement names it.
17
+ _FIRST_PARTY_DEPENDENCIES = (
18
+ "sediment-api",
19
+ "sediment-capture",
20
+ "sediment-core",
21
+ "sediment-derive",
22
+ "sediment-export",
23
+ )
24
+
25
+
26
+ def install_command(version: str) -> str:
27
+ """The installer's uv command pinned to *version*.
28
+
29
+ A pinned install replaces the tool in either direction. `uv tool upgrade`
30
+ keeps an earlier pin and otherwise moves only to the newest release.
31
+ """
32
+ command = f"uv tool install --python 3.12 --upgrade 'sediment-cli=={version}'"
33
+ if re.fullmatch(r"[0-9.]+rc[0-9]+", version):
34
+ command += "".join(
35
+ f" --with '{name}=={version}'" for name in _FIRST_PARTY_DEPENDENCIES
36
+ )
37
+ return command
38
+
39
+
40
+ def version_skew_advice(server_version: object, client_version: str) -> str | None:
41
+ """How to match the server's version, or None when it matches or is absent.
42
+
43
+ The server controls *server_version*; only a release version reaches the
44
+ copyable command, so a server can't put shell syntax or terminal control
45
+ characters into it.
46
+ """
47
+ if server_version is None or server_version == client_version:
48
+ return None
49
+ if not isinstance(server_version, str) or not _RELEASE_VERSION.fullmatch(
50
+ server_version
51
+ ):
52
+ return (
53
+ f"server reported an unrecognized version; client is {client_version}. "
54
+ f"Install your deployment's release: {install_command('<version>')}"
55
+ )
56
+ return (
57
+ f"server version {server_version} differs from client {client_version}; "
58
+ f"install the server's version: {install_command(server_version)}"
59
+ )
60
+
61
+
62
+ def main(argv: list[str] | None = None) -> int:
63
+ """Refuse unsupported capture installation before importing POSIX operators."""
64
+ import os
65
+ import sys
66
+
67
+ args = sys.argv[1:] if argv is None else argv
68
+ if os.name == "nt" and args[:1] == ["install"]:
69
+ from .attribution import main as run
70
+ else:
71
+ from .cli import main as run
72
+ return run(args)
@@ -195,7 +195,7 @@ delivery changes also trigger this workflow. To exercise the same tests locally,
195
195
  set `SEDIMENT_PI_TEST_PYTHON` to the installed environment's Python executable
196
196
  and `SEDIMENT_PI_TEST_INSTALLED_BIN` to its `bin` directory before `npm test`.
197
197
 
198
- The lockfile pins pi 0.86.1 (`@earendil-works/pi-coding-agent`) as a development
198
+ The lockfile pins pi 0.87.1 (`@earendil-works/pi-coding-agent`) as a development
199
199
  dependency. The native retrieval test exercises its extension registration,
200
200
  argument validation, agent loop, and model-visible tool result with a scripted
201
201
  model stream. It makes no paid inference request and downloads no package during
@@ -13,8 +13,8 @@
13
13
  "typecheck": "tsc --noEmit"
14
14
  },
15
15
  "devDependencies": {
16
- "@earendil-works/pi-coding-agent": "0.86.1",
17
- "@types/node": "^24.13.6",
16
+ "@earendil-works/pi-coding-agent": "0.87.1",
17
+ "@types/node": "^24.19.0",
18
18
  "typescript": "^7.0.2"
19
19
  }
20
20
  }
@@ -0,0 +1,142 @@
1
+ # Help operators and developers use Sediment
2
+
3
+ Use this guide when you help an operator run Sediment or help a developer
4
+ capture work from repositories and coding agents. Both roles install the
5
+ Sediment CLI. Developers connecting to a shared server use the installer's
6
+ `--capture-only` option; they don't need to run a local server.
7
+
8
+ `sediment guide` prints this bundled guide without reading configuration,
9
+ contacting a server, or changing the machine. It converts procedure links to
10
+ raw GitHub URLs pinned to the release it ships in, or to `main` from a source
11
+ checkout. Reading those pages requires network access. `sediment --help` lists
12
+ public commands; hooks invoke others.
13
+
14
+ ## Read the state before changing it
15
+
16
+ - Run `sediment --version`. For a shared deployment, compare it with the
17
+ server's `/health` version. Use the guide from the version you operate.
18
+ - Establish the task, server URL, deployment method, server account, and
19
+ repository. A developer's machine might only send capture to a remote server.
20
+ - Use `/health` to check server readiness and `sediment doctor <repo>` to
21
+ inspect capture. Neither proves that the server received agent evidence.
22
+ - Stay within the requested task. Don't start another server, reinstall hooks,
23
+ upgrade, rotate credentials, quarantine Facts, or remove data as a repair
24
+ unless that change is authorized. Follow the linked procedure for changes.
25
+
26
+ ## Help an operator
27
+
28
+ Run server commands as the account that owns the deployment. For database
29
+ commands, first set up the operator shell from the deployment procedure.
30
+ An API login doesn't configure database access. Keep operator credentials on
31
+ the operator machine; give developers capture credentials only.
32
+
33
+ | Task | First command | Procedure |
34
+ | --- | --- | --- |
35
+ | Try a local server | `sediment server` | [Quickstart](../quickstart.md) |
36
+ | Deploy on your own host | `sediment --version` | [Deploy Sediment on your own host](deploy.md) |
37
+ | Deploy on Amazon Elastic Compute Cloud (EC2) | `sediment --version` | [Deploy Sediment on EC2](deploy-ec2.md) |
38
+ | Enroll a team and connect repository webhooks | `sediment facts` | [Enroll your team](run-pilot.md) |
39
+ | Check schema and Fact counts | `sediment db status` | [Monitor the deployment](maintain.md#monitor-the-deployment) |
40
+ | Upgrade | `sediment --version` | [Upgrade Sediment](maintain.md#upgrade-sediment) |
41
+ | Back up or restore | `sediment db status` | [Back up and restore](maintain.md#back-up-and-restore) |
42
+ | Rotate credentials | `sediment --version` | [Rotate credentials](maintain.md#rotate-credentials) |
43
+ | Build a Derivation bundle | `sediment derive --help` | [Run derivations](run-derivations.md) |
44
+ | Export training data | `sediment export --help` | [Choose a training export](../exports/training-exports.md) |
45
+ | Quarantine captured data | `sediment quarantine-log` | [Quarantine captured data](maintain.md#quarantine-captured-data) |
46
+ | Tear down a deployment | `sediment --version` | [Tear down the deployment](maintain.md#tear-down-the-deployment) |
47
+
48
+ `sediment server` runs in the foreground. Without an external PostgreSQL
49
+ connection, it downloads PostgreSQL on first use and owns the local database.
50
+ It stores data, credentials, and mirrors under `~/.sediment/server` or `--root`.
51
+ Starting it provisions database roles and applies migrations. Preserve that
52
+ directory across restarts and upgrades. Plan backups before migrations or
53
+ teardown. Use the deployment's supervisor to manage a shared server.
54
+
55
+ ## Help a developer
56
+
57
+ Use the server URL and capture credential that the operator supplies.
58
+ Login saves credentials; installation changes Git hooks, agent configuration,
59
+ and shell configuration. Run these steps when the developer requests capture.
60
+
61
+ | Task | First command | Procedure |
62
+ | --- | --- | --- |
63
+ | Enroll with a shared server | `sediment login <url> --capture` | [Configure local capture](../capture/local-capture.md) |
64
+ | Install repository and agent capture | `sediment install <repo>` | [Install capture](../capture/local-capture.md#install-capture) |
65
+ | Select agent setup and verify delivery | `sediment doctor <repo>` | [Choose an agent integration](../capture/agent-integrations.md) |
66
+ | Check buffered delivery | `sediment delivery status` | [Preserve prepared payloads through outages](../capture/local-capture.md#preserve-prepared-payloads-through-outages) |
67
+ | Inspect evidence for a pushed commit | `sediment commit <full commit SHA>` | [Measure agent work](measure-agent-work.md) |
68
+ | Remove capture | `sediment uninstall <repo>` | [Uninstall capture](../capture/local-capture.md#uninstall-capture) |
69
+
70
+ Default `doctor` checks configuration and contacts the server and `origin`.
71
+ It changes nothing unless you pass `--fetch`, which updates a notes tracking
72
+ ref. Only `FAIL` lines make it exit 1. A sandbox network restriction or an
73
+ unloaded capture environment can explain a failure. Use the agent procedure's
74
+ Session checks to verify delivery. `commit` requires an operator credential;
75
+ `no attributions` before the push and notes reach the server isn't a failure.
76
+
77
+ ## Preserve capture while doing repository work
78
+
79
+ - Leave the marked `sediment-attribution` Git hook blocks, `notes.rewriteRef`,
80
+ and `refs/notes/sediment` in place. Don't bypass hooks or change
81
+ `core.hooksPath`. If hook files are tracked, leave Sediment's uncommitted
82
+ blocks for the developer; don't commit, stash, or revert them incidentally.
83
+ - Keep the `sediment` executable at its installed path. Hooks use that absolute
84
+ path; source installs also depend on their checkout and virtual environment.
85
+ - Preserve the `sediment env` blocks in shell profiles and Sediment's entries
86
+ in Claude Code settings, Codex hooks and profiles, Cursor hooks, and pi
87
+ settings. User-level agent hooks apply across repositories.
88
+ - Read hook output. Sediment commands end in `|| true`; an appended block can
89
+ also mask a repository check that relies on its last command's exit status.
90
+ A zero status alone doesn't prove that the repository's checks passed.
91
+
92
+ [What the installer changes](../capture/local-capture.md#what-the-installer-changes)
93
+ describes the hooks. [Roll out managed capture](../capture/managed-capture.md)
94
+ covers system settings, gateways, and fleet installation.
95
+
96
+ ## Remove capture within its installation scope
97
+
98
+ - `uninstall <repo>` removes that repository's hook blocks and Sediment's
99
+ `notes.rewriteRef` value. Require `removed git hooks from` in its output;
100
+ a silent exit can mean that the path isn't a Git repository.
101
+ - `uninstall <repo> --agents` also removes user-level agent hooks, including
102
+ pi, environment files, shell blocks, and verified Codex telemetry blocks.
103
+ These user-level removals affect capture across repositories. The command
104
+ doesn't remove Claude Code managed settings, fleet Git templates, or
105
+ externally distributed environment settings. Follow
106
+ [Remove managed capture](../capture/managed-capture.md#remove-managed-capture)
107
+ for those installations. Report skipped files; don't claim complete removal.
108
+ - `logout` removes stored CLI credentials, but environment files retain their
109
+ ingest token and hooks keep running. It doesn't stop capture.
110
+ - Neither uninstall form removes existing notes, remote notes, or
111
+ `~/.sediment/`. An `auto_install_remotes` allowlist can reinstall hooks at
112
+ the next Session. Remove that entry as part of an authorized teardown.
113
+ Restart running agents so they stop using the earlier environment.
114
+
115
+ ## Keep credentials and captured content private
116
+
117
+ Don't print credentials, paste them into chat, or commit them. Credential
118
+ locations include `~/.sediment/config.json`, `~/.sediment/env.sh`, the Fish
119
+ `sediment.fish` file, Codex telemetry profiles, and server environment files.
120
+ Custom server roots, shell profiles, and service settings can hold them too.
121
+ Don't dump the process environment: it can contain `SEDIMENT_INGEST_TOKEN`,
122
+ `OTEL_EXPORTER_OTLP_HEADERS`, database passwords, and gateway credentials.
123
+ Follow [Secure a deployment](security.md) when handling secrets.
124
+
125
+ Capture can leave the developer machine in these forms:
126
+
127
+ - The pre-push hook sends the whole notes ref to each push remote, including
128
+ forks, even during `git push --dry-run`. Notes contain commit SHAs, agent
129
+ names, Session identifiers, and timestamps, including other branches' notes.
130
+ They don't contain prompts, diffs, file paths, model names, or hostnames.
131
+ - Agent telemetry goes to the Sediment server. Claude Code tool details can
132
+ include paths, shell commands, edit text, and the Claude account email.
133
+ Codex decisions can include patch code and tool arguments; Sediment removes
134
+ the Codex account email and account identifier before storage. Cursor sends
135
+ accept decisions for applied writes and a configured user identifier.
136
+ - Opt-in transcript capture sends edit text and edited-file contents. Claude
137
+ Code transcripts also include refused edit text and non-agent change counts.
138
+ - If the operator routes model calls through a gateway, it captures full
139
+ prompts and responses.
140
+
141
+ [Privacy boundaries and ceilings](../explanation/how-capture-works.md#privacy-boundaries-and-ceilings)
142
+ describes these payloads and their limits.
@@ -55,8 +55,7 @@ Stdlib only, no daemon. Nine subcommands:
55
55
  + ``notes.rewriteRef`` (local config) + the agent
56
56
  telemetry env files generated from the ``sediment
57
57
  login`` config (``--no-env`` skips; ``--user-id``
58
- stamps per-developer attribution; ``--gateway-url``/
59
- ``--gateway-key`` add the completions routing);
58
+ stamps per-developer attribution);
60
59
  ``--transcripts`` opts in to the SessionEnd transcript
61
60
  extractor and its PreToolUse snapshot hook
62
61
  (sediment_transcript.py — ships edit text pairs, a
@@ -106,9 +105,12 @@ try:
106
105
  except ImportError: # pragma: no cover — run by path, not as a package
107
106
  # The checkout shim (scripts/sediment_attribution.py) and the fleet
108
107
  # bundle execute this file standalone, where no sibling ui module
109
- # exists. Those are hook/MDM piped contexts: plain text is the correct
110
- # output there, so a passthrough stand-in keeps the bytes identical.
108
+ # exists. Keep styling plain and match ui's server-text display filter.
111
109
  class ui: # type: ignore[no-redef] # ponytail: plain-text stand-in
110
+ @staticmethod
111
+ def printable_text(value: object) -> str:
112
+ return "".join(char for char in str(value) if char.isprintable())
113
+
112
114
  @staticmethod
113
115
  def style(text: str, *names: str, stream=None) -> str:
114
116
  return text
@@ -2852,8 +2854,6 @@ def _env_pairs(
2852
2854
  url: str,
2853
2855
  token: str,
2854
2856
  user_id: str | None,
2855
- gateway_url: str | None,
2856
- gateway_key: str | None,
2857
2857
  ) -> list[tuple[str, str]]:
2858
2858
  """The §5.1 block, generated. The exporter appends /v1/logs itself, so
2859
2859
  the endpoint is the bare server URL."""
@@ -2875,22 +2875,27 @@ def _env_pairs(
2875
2875
  _warn("capture helper unavailable; dedicated hook delivery is disabled")
2876
2876
  if user_id:
2877
2877
  pairs.append(("OTEL_RESOURCE_ATTRIBUTES", f"user.id={user_id}"))
2878
- if gateway_url:
2879
- pairs.append(("ANTHROPIC_BASE_URL", gateway_url))
2880
- if gateway_key:
2881
- pairs.append(("ANTHROPIC_AUTH_TOKEN", gateway_key))
2882
- pairs.append(("SEDIMENT_GATEWAY_KEY", gateway_key))
2883
2878
  return pairs
2884
2879
 
2885
2880
 
2886
2881
  def _write_0600(path: Path, content: str) -> None:
2887
- """0600 on create AND on rewrite — O_CREAT's mode applies only at
2888
- creation, and these files carry the bearer token."""
2882
+ """Publish a complete private file without truncating the previous version."""
2883
+ if path.is_symlink():
2884
+ raise ValueError(
2885
+ "refusing to replace a symbolic link with private configuration"
2886
+ )
2889
2887
  path.parent.mkdir(parents=True, exist_ok=True)
2890
- fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
2891
- os.fchmod(fd, 0o600)
2892
- with os.fdopen(fd, "w", encoding="utf-8") as fh:
2893
- fh.write(content)
2888
+ temporary = path.with_name(f".{path.name}.{uuid4().hex}.tmp")
2889
+ fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
2890
+ try:
2891
+ with os.fdopen(fd, "w", encoding="utf-8") as fh:
2892
+ os.fchmod(fh.fileno(), 0o600)
2893
+ fh.write(content)
2894
+ fh.flush()
2895
+ os.fsync(fh.fileno())
2896
+ os.replace(temporary, path)
2897
+ finally:
2898
+ temporary.unlink(missing_ok=True)
2894
2899
 
2895
2900
 
2896
2901
  def _write_env_files(pairs: list[tuple[str, str]]) -> None:
@@ -2956,19 +2961,14 @@ def _unwire_env() -> list[str]:
2956
2961
  return removed
2957
2962
 
2958
2963
 
2959
- def cmd_install_env(
2960
- user_id: str | None,
2961
- gateway_url: str | None,
2962
- gateway_key: str | None,
2963
- transcripts: bool = False,
2964
- ) -> str:
2964
+ def cmd_install_env(user_id: str | None, transcripts: bool = False) -> str:
2965
2965
  """Wire the agent telemetry env from the login config; the install
2966
2966
  summary string says what happened."""
2967
2967
  cfg = _cli_config(capture=True)
2968
2968
  if cfg is None:
2969
2969
  return f"skipped (capture credential is absent; {_CAPTURE_LOGIN})"
2970
2970
  url, token = cfg
2971
- pairs = _env_pairs(url, token, user_id, gateway_url, gateway_key)
2971
+ pairs = _env_pairs(url, token, user_id)
2972
2972
  try:
2973
2973
  previous = _sh_env_file().read_text(encoding="utf-8").splitlines()
2974
2974
  except FileNotFoundError:
@@ -3222,11 +3222,13 @@ def _uninstall_codex_profiles() -> bool:
3222
3222
  complete = True
3223
3223
  for path in profiles:
3224
3224
  try:
3225
- if path.is_symlink() or not path.is_file():
3225
+ if not path.is_file():
3226
3226
  raise ValueError
3227
3227
  original = path.read_text(encoding="utf-8")
3228
3228
  if _CODEX_OTEL_BEGIN not in original and _CODEX_OTEL_END not in original:
3229
3229
  continue
3230
+ if path.is_symlink():
3231
+ raise ValueError
3230
3232
  remaining = _without_codex_telemetry(original)
3231
3233
  if remaining.strip():
3232
3234
  _write_0600(path, remaining)
@@ -3436,21 +3438,17 @@ def _doctor_server(findings: list[Finding]) -> None:
3436
3438
  )
3437
3439
  )
3438
3440
  return
3439
- detail = f"reachable, {authority} token valid (org {body.get('org_id')})"
3441
+ org_id = ui.printable_text(body.get("org_id"))
3442
+ detail = f"reachable, {authority} token valid (org {org_id})"
3440
3443
  try:
3441
- from sediment_api import __version__ as client_version
3444
+ from sediment_cli import __version__ as client_version
3445
+ from sediment_cli import version_skew_advice
3442
3446
  except ImportError: # standalone copied-file run — skew unknowable
3443
- client_version = None
3444
- server_version = body.get("version")
3445
- if server_version and client_version and server_version != client_version:
3446
- findings.append(
3447
- (
3448
- DOCTOR_INFO,
3449
- check,
3450
- f"{detail}; version skew: server {server_version}, client "
3451
- f"{client_version} — uv tool upgrade sediment-cli",
3452
- )
3453
- )
3447
+ advice = None
3448
+ else:
3449
+ advice = version_skew_advice(body.get("version"), client_version)
3450
+ if advice:
3451
+ findings.append((DOCTOR_INFO, check, f"{detail}; {advice}"))
3454
3452
  else:
3455
3453
  findings.append((DOCTOR_OK, check, detail))
3456
3454
 
@@ -3461,8 +3459,6 @@ def cmd_install(
3461
3459
  transcripts: bool = False,
3462
3460
  env: bool = True,
3463
3461
  user_id: str | None = None,
3464
- gateway_url: str | None = None,
3465
- gateway_key: str | None = None,
3466
3462
  codex_profile: str | None = None,
3467
3463
  ) -> int:
3468
3464
  repo_path = Path(repo).resolve()
@@ -3511,7 +3507,7 @@ def cmd_install(
3511
3507
  # same flag pair: --no-agents implies no env wiring.
3512
3508
  if env:
3513
3509
  try:
3514
- detail = cmd_install_env(user_id, gateway_url, gateway_key, transcripts)
3510
+ detail = cmd_install_env(user_id, transcripts)
3515
3511
  except ValueError as exc:
3516
3512
  _error(str(exc))
3517
3513
  return 1
@@ -3854,17 +3850,6 @@ def build_parser() -> argparse.ArgumentParser:
3854
3850
  metavar="NAME",
3855
3851
  help="write a Codex telemetry profile with resolved login credentials (0600); preserve its model and unrelated settings",
3856
3852
  )
3857
- p_install.add_argument(
3858
- "--gateway-url",
3859
- default=None,
3860
- help="also wire ANTHROPIC_BASE_URL to this LLM gateway",
3861
- )
3862
- p_install.add_argument(
3863
- "--gateway-key",
3864
- default=None,
3865
- help="also wire ANTHROPIC_AUTH_TOKEN and SEDIMENT_GATEWAY_KEY "
3866
- "(the key agents present to the gateway)",
3867
- )
3868
3853
  p_install.add_argument(
3869
3854
  "--fleet",
3870
3855
  action="store_true",
@@ -3954,15 +3939,13 @@ def main(argv: list[str] | None = None) -> int:
3954
3939
  or args.transcripts
3955
3940
  or args.no_env
3956
3941
  or args.user_id is not None
3957
- or args.gateway_url is not None
3958
- or args.gateway_key is not None
3959
3942
  or args.codex_profile is not None
3960
3943
  )
3961
3944
  if args.fleet:
3962
3945
  if per_user_flags:
3963
3946
  _error(
3964
3947
  "REPO, --no-agents, --transcripts, --codex-profile, and the env "
3965
- "flags (--no-env/--user-id/--gateway-*) do not apply "
3948
+ "flags (--no-env/--user-id) do not apply "
3966
3949
  "to --fleet"
3967
3950
  )
3968
3951
  return 2
@@ -3983,8 +3966,6 @@ def main(argv: list[str] | None = None) -> int:
3983
3966
  transcripts=args.transcripts,
3984
3967
  env=not args.no_env,
3985
3968
  user_id=args.user_id,
3986
- gateway_url=args.gateway_url,
3987
- gateway_key=args.gateway_key,
3988
3969
  codex_profile=args.codex_profile,
3989
3970
  )
3990
3971
  if args.command == "uninstall":